FFMPEG H264

发布于:2025-09-04 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、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(⽚)的时
候,包含这些sliceNALU 使⽤3字节起始码。其余场合都是4字节0x00000001的。前面的I帧就是分别存储在两个NALU中,这两个NALU的开头就是00 00 01

2、NLU Header第一个字节

其中:
T为负荷数据类型,占5bit
nal_unit_type:这个NALU单元的类型,112H.264使⽤,2431H.264以外的应⽤
使⽤
R为重要性指示位,占2bit
nal_ref_idc.:取00~11,似乎指示这个NALU的重要性,00NALU解码器可以丢弃它⽽不
影响图像的回放,03,取值越⼤,表示当前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模式,传统模式,有startcodeSPSPPS是在ES
⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcodeSPSPPS以及其它信息
被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度。很多解码器只⽀持Annex B这种模式,因此需要将mp4做转换:ffmpeg中⽤ h264_mp4toannexb_filter可以做转换
实现:
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视频文件

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

二、MP4->H.264代码

#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;
}