FFMPEG H264

一、H264压缩编码

1.1 H264 中的 I 帧、P帧和 B帧

H264 使用帧内压缩和帧间压缩的方式提高编码压缩率;H264 采用了独特的 I 帧、P 帧和 B 帧策略来实现,连续帧之间的压缩;

1.2 其他概念

**GOP(图像组):**一个IDR帧到下一个IDR帧之间间隔了多少个帧

**IDR:**一个序列的第一个图像叫做IDR图像(立即刷新图像),IDR图像都是I帧图像。I帧不用参考任何帧,但之后的P帧和B帧是有可能参考这个I帧之前的帧。IDR帧不允许后面PB参考前面的帧。IDR的核心作用是为了序列出现重大错误后重新同步,不用参考IDR之前的图像的数据来解码

1.3 NLU


视频流至少发从一帧SPS和一帧PPS来告诉客户端视频流信息

H.264原始码流(裸流)是由⼀个接⼀个NALU组成。它的功能分为两层,VCL (视频编码层) 和

NAL (⽹络提取层)。VCL:负责压缩视频;NAL:把VCL压缩的视频封装成NLU帧

1、NLU帧包括三个部分

[StartCode] [NALU****Header] [NALU Payload]
StartCode必须是 "00 00 00 01" 或 "00 00 01"

NALU Header是NALU的头部

RBSP是I、P、B帧的数据。

NLU帧的结束也是根据00 00 00 01或00 00 01来判断的
3字节的0x000001只有⼀种场合下使⽤,就是⼀个完整的帧被编为多个slice(⽚)的时
候,包含这些slice的NALU 使⽤3字节起始码。其余场合都是4字节0x00000001的。前面的I帧就是分别存储在两个NALU中,这两个NALU的开头就是00 00 01

2、NLU Header第一个字节


其中:
T为负荷数据类型,占5****bit
nal_unit_type:这个NALU单元的类型,1~12由H.264使⽤,24~31由H.264以外的应⽤
使⽤
R为重要性指示位,占2bit
nal_ref_idc.:取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它⽽不
影响图像的回放,0~3,取值越⼤,表示当前NAL越重要,需要优先受到保护。如果当前
NAL是属于参考帧的⽚,或是序列参数集,或是图像参数集这些重要的单位时,本句法元
素必需⼤于0。
最后的F为禁⽌位,占1bit
forbidden_zero_bit: 在 H.264 规范中规定了这⼀位必须为 0.

3、NLU的类型
0x00 00 00 01 67 ...
67:
⼆进制:0110 0111
00111 = 7(⼗进制)

nal_unit_type NAL单元和RBSP语法结构的内容
0 未指定
1 一个非IDR图像的编码条带slice_layer_without_partitioning_rbsp( )
2 编码条带数据分割块A slice_data_partition_a_layer_rbsp( )
3 编码条带数据分割块B slice_data_partition_b_layer_rbsp( )
4 编码条带数据分割块C slice_data_partition_c_layer_rbsp( )
5 IDR图像的编码条带(⽚) slice_layer_without_partitioning_rbsp ( )
6 辅助增强信息 (SEI) sei_rbsp( )
7 序列参数集 seq_parameter_set_rbsp( )
8 图像参数集 pic_parameter_set_rbsp( )
9 访问单元分隔符 access_unit_delimiter_rbsp( )
10 序列结尾 end_of_seq_rbsp( )
11 流结尾 end_of_stream_rbsp( )
12 填充数据 filler_data_rbsp( )
13 序列参数集扩展9 seq_parameter_set_extension_rbsp( )
14...18 保留
19 未分割的辅助编码图像的编码条带 slice_layer_without_partitioning_rbsp( )
20...23 保留
24...31 未指定

1.4 H264 annexb模式

H264有两种封装
⼀种是Annex B模式,传统模式,有startcode,SPS和PPS是在ES中
⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息
被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度。很多解码器只⽀持Annex B这种模式,因此需要将mp4做转换:在ffmpeg中⽤ h264_mp4toannexb_filter可以做转换
实现:

cpp 复制代码
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext *bsf_ctx = NULL;
// 2 初始化过滤器上下⽂
av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解码器属性
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);
  1. Annex B内部数据,对应的了前面讲解的内容

    [4字节起始码] + [SPS NALU数据]
    [4字节起始码] + [PPS NALU数据]
    [3字节起始码] + [I帧NALU数据]
    [3字节起始码] + [P帧NALU数据]
    [3字节起始码] + [B帧NALU数据]
    ...(后续NALU以此类推)

输出的文件是H.264视频文件

  1. MP4直接的码流对应下图右边,开头记录的是NALU帧的长度,都是4字节

二、MP4->H.264代码

cpp 复制代码
#include <iostream>

extern "C"{
    #include <libavutil/log.h>
    #include <libavformat/avio.h>
    #include <libavformat/avformat.h>
    #include <libavcodec/bsf.h> 
}

using namespace std;

static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{
    av_strerror(errnum, err_buf, 128);
    return err_buf;
}

int main(int argc, char **argv) {
    string inputFile = "believe.mp4";
    string outputFile = "believe.h264"; 

    FILE * outfp = fopen(outputFile.c_str(),"wb");
    printf("in:%s out:%s\n", inputFile.c_str(), outputFile.c_str());

    // 创建AVFormatContext上下文
    AVFormatContext * ifmt_ctx = avformat_alloc_context();
    if (!ifmt_ctx) {
        printf("[error] Could not allocate context.\n");
        return -1;
    }

    int ret = avformat_open_input(&ifmt_ctx, inputFile.c_str(), NULL, NULL);
    if (ret != 0) {
        printf("[error] avformat_open_input: %s\n", av_get_err(ret));
        return -1;
    }

    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if (ret < 0) {
        printf("[error] avformat_find_stream_info: %s\n", av_get_err(ret));
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 查找出哪个码流是video/audio/subtitles
    int videoindex = -1;
    videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(videoindex == -1) {
        printf("[error] Didn't find a video stream.\n");
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 创建AVPacket包
    AVPacket * pkt = av_packet_alloc();
    av_init_packet(pkt);

    // 1 获取一个名为"h264_mp4toannexb"的比特流过滤器
    // 用于将MP4容器中的H.264视频流转换为Annex B格式的H.264流
    const AVBitStreamFilter * bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext * bsf_ctx = NULL;
    // 2 初始化过滤器上下文
    av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
    // 3 复制视频流的编解码参数到过滤器上下文
    avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
    av_bsf_init(bsf_ctx);

    int file_end = 0;
    while (0 == file_end) {
        if((ret = av_read_frame(ifmt_ctx, pkt)) < 0) {
            // 没有更多包可读
            file_end = 1;
            printf("read file end: ret:%d\n", ret);
        }
        if(ret == 0 && pkt->stream_index == videoindex) {
            int input_size = pkt->size;
            int out_pkt_count = 0;
            if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间
            {
                av_packet_unref(pkt);   // 你不用了就把资源释放掉
                continue;       // 继续送
            }
            av_packet_unref(pkt);   // 释放资源
            while(av_bsf_receive_packet(bsf_ctx, pkt) == 0) {
                out_pkt_count++;
                // printf("fwrite size:%d\n", pkt->size);
                size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
                if(size != pkt->size)
                {
                    printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
                }
                av_packet_unref(pkt);
            }
            if(out_pkt_count >= 2)
            {
                printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n", input_size, out_pkt_count);
            }

            // 注释掉前面代码,可以直接保存mp4流
            // size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
            // if(size != pkt->size)
            // {
            //     printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
            // }
            // av_packet_unref(pkt);
        }
        else {
            if(ret == 0) av_packet_unref(pkt); // 释放内存
        }
    }

    if (outfp) fclose(outfp);
    if (bsf_ctx) av_bsf_free(&bsf_ctx);
    if (pkt) av_packet_free(&pkt);
    if (ifmt_ctx) avformat_close_input(&ifmt_ctx);
    printf("finish\n");

    return 0;
}
相关推荐
问道飞鱼2 小时前
【工具介绍】Ffmpeg工具介绍与简单使用
ffmpeg·视频工具
l***77527 小时前
从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
ffmpeg
ZouZou老师13 小时前
FFmpeg性能优化经典案例
性能优化·ffmpeg
aqi0016 小时前
FFmpeg开发笔记(九十)采用FFmpeg套壳的音视频转码百宝箱FFBox
ffmpeg·音视频·直播·流媒体
齐齐大魔王17 小时前
FFmpeg
ffmpeg
你好音视频19 小时前
FFmpeg RTSP拉流流程深度解析
ffmpeg
IFTICing1 天前
【环境配置】ffmpeg下载、安装、配置(Windows环境)
windows·ffmpeg
haiy20111 天前
FFmpeg 编译
ffmpeg
aqi001 天前
FFmpeg开发笔记(八十九)基于FFmpeg的直播视频录制工具StreamCap
ffmpeg·音视频·直播·流媒体
八月的雨季 最後的冰吻2 天前
FFmepg--28- 滤镜处理 YUV 视频帧:实现上下镜像效果
ffmpeg·音视频