04 - FFmpeg 提取 H264 数据 -- Annexb

----------------------------------------------------------------- H264 简介 ----------------------------------------------------------------
H264,是ISO和ITU共同开发的一个数字视频编码标准,目前最常用的视频编码格式之一。
1、低码率:和MPEG2和MPEG4 ASP等压缩技术相比,在同等图像质量下,采用H.264技术压缩后的数据量只有MPEG2的1/8,MPEG4的1/3;
2、高编码效率:通H.263等标准编码效率相比,能够平均节省大于50%的码率;高图像质量:H.264能提供连续、流畅的高质量图像;
3、容错能力强:H.264提供了解决在不稳定网络环境下容易发生的丢包等错误的必要工具;
4、网络适应性强:H.264提供了网络抽象层(Network Abstraction Layer),使得H.264的文件能容易地在不同网络上传输;

ffmpeg命令:
    ffmpeg -i test.flv -an -vcodec copy out.h264  // 不要音频
    ffmpeg -i test.mp4 -an -vcodec copy -bsf: h264_mp4toannexb out.h264   // 直播需要 AnnexB 格式
ffplay命令:
    ffplay out.h264

----------------------------------------------------------------- H.264 编码原理 -----------------------------------------------------------------
在音视频传输过程中,视频文件的传输是一个极大的问题:
一段分辨率为1920*1080,每个像素点为RGB占用3个字节,帧率是25的视频,
对于传输带宽的要求是:1920*1080*3*25/1024/1024=148.315MB/s
换成bps则意味着视频每秒带宽为1186.523Mbps,这样的速率对于网络存储是不可接受的。
因此视频压缩和编码技术应运而生。
对于视频文件来说,视频由单张图片帧所组成,比如每秒25帧,但是图片帧的像素块之间存在相似性,因此视频帧图像可以进行图像压缩;
H264采用了16*16的分块大小对,视频帧图像进行相似比较和压缩编码。 

----------------------------------------------------------------- H.264 帧的分类 -----------------------------------------------------------------
I帧 ------ 帧内编码帧 intra picture
    I帧通常是每个 GOP (MPEG 所使用的一种视频压缩技术) 的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图像。
    I帧可以看成是一个图像经过压缩后的产物。自身可以通过视频解压算法解压成一张单独的完整的图片

P帧 ------ 前向预测编码帧 predictive-frame
    通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。
    需要参考其前面的一个 I帧 或者 P帧 来生成一张完整的图片。
      
B帧 ------ 双向预测帧 bi-directional interpolated prediction frame
    既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像,也叫双向预测帧。
    则要参考其前一个 I帧 或者 P帧 及其后面的一个 P帧 来生成一张完整的图片。

----------------------------------------------------------------- ffmpeg - 提取流程 ----------------------------------------------------------------

1、打开媒体文件 - avformat_open_input
2、获得码流信息 - avformat_find_stream_info -- 填充“信息”结构体
3、获得视频流 - av_find_best_stream
4、初始化packet - av_init_packet
5、读取packet数据 - av_read_frame
6、释放packet资源 - av_packet_unref
7、关闭媒体文件 - avformat_close_input

----------------------------------------------------------------- H264封装格式介绍 -----------------------------------------------------------------
H264封装分两种形式:AnnexB和AVCC
AnnexB: StartCode(Ox000001或Ox00000001) + NALU数据,常用于实时流传输。 -- 一直不断地发给拉流端
        StartCode 三个字节或者四个字节
AVCC:又称AVC1,NALU长度+NALU数据,适合存储,如MP4、MKV等。
一个NALU(Network Abstract Layer [网络抽象层的数据单元] ) = 
一组对应于视频编码的NALU头部信息 + 一个原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。
SPS: Sequence Paramater Set,序列参数集,包含了解码器配置和帧率等信息。
PPS: Picture Paramater Set,图像参数集,包含了嫡编码模式, slice groups, motion prediction和去块滤波器控制等信息。
为什么需要SPS和PPS?
·解码器需要在码流中间开始解码;
·编码器在编码的过程中改变了码流的参数(如图像分辨率等)

----------------------------------------------------------------- IDR(Instantaneous Decoding Refresh,即时解码刷新) -----------------------------------------------------------------
一个序列的第一个图像叫做 IDR 图像(立即刷新图像),IDR 图像都是I帧图像。
I帧和IDR帧都使用帧内预测。I帧不用参考任何帧,但是之后的 P帧 和 B帧 是有可能参考这个I帧之前的帧的。IDR就不允许这样。
其核心作用是,是为了解码的重同步,当解码器解码到 IDR 图像时,立即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始一个新的序列。
这样,如果前一个序列出现重大错误,在这里可以获得重新同步的机会。IDR图像之后的图像永远不会使用IDR之前的图像的数据来解码。

int DemuxingVideo(const char *inFileName, const char *h264FileName)
{
    FILE *h264_fp = fopen(h264FileName, "wb");
    if (h264_fp == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "open %s file failed\n", h264FileName);
        goto fail;
    }

    AVFormatContext *inFmtCtx = NULL;
    int ret = avformat_open_input(&inFmtCtx, inFileName, NULL, NULL);
    if (ret != 0)
    {
        av_log(NULL, AV_LOG_ERROR, "open input file:%s failed:%s\n", inFileName, av_err2str(ret));
        return -1;
    }
    // 该函数将读取媒体文件的音视频包去获取流信息
    ret = avformat_find_stream_info(inFmtCtx, NULL); // 填充stream 结构体 - 获得码流信息
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "find input stream info failed:%s\n", av_err2str(ret));
        ret = -1;
        goto fail;
    }
    int VideoIndex = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 获得视频流
    if (VideoIndex < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "find best stream failed,index is %d\n", ret);
        ret = -1;
        goto fail;
    }
    av_log(NULL, AV_LOG_INFO, "the VideoIndex is %d\n", ret);

    AVPacket *packet = NULL;
    packet = av_packet_alloc();
    av_init_packet(packet);

    const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
    if (bsf == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, "get h264_mp4toannexb bsf failed\n");
        return -1;
        goto fail;
    }

    AVBSFContext *bsfCtx = NULL;                                                      // 用于存储比特流过滤器的上下文信息
    av_bsf_alloc(bsf, &bsfCtx);                                                       // 比特流过滤器分配内存并初始化上下文结构体。
    avcodec_parameters_copy(bsfCtx->par_in, inFmtCtx->streams[VideoIndex]->codecpar); /* 输入流的参数复制到比特流过滤器的输入参数中,以便过滤器能够正确处理输入数据。*/
    av_bsf_init(bsfCtx);                                                              // 初始化比特流过滤器,准备对输入数据进行处理。
    while (av_read_frame(inFmtCtx, packet) == 0)
    {
        if (packet->stream_index == VideoIndex)
        {
            // 发送数据包到过滤器,然后接收处理后的数据包并将其写入到文件中
            if (av_bsf_send_packet(bsfCtx, packet) == 0) // 这行代码将数据包packet发送给比特流过滤器bsfCtx进行处理。如果返回值为0,表示成功发送数据包到过滤器。
            {
                int outPacketCount = 0;
                while (av_bsf_receive_packet(bsfCtx, packet) == 0)
                {
                    outPacketCount++;
                    int writeSize = fwrite(packet->data, 1, packet->size, h264_fp); // 接收到的数据包写入到文件中
                    {
                        av_log(NULL, AV_LOG_ERROR, "wirte file failed!\n");
                        ret = -1;
                        av_packet_unref(packet); // 释放packet资源
                        break;
                    }
                    av_log(NULL, AV_LOG_INFO, "outPacketCount %d,packet->size:%u -- line:%d\n", outPacketCount, packet->size, __LINE__);
                }
            }
        }
        av_packet_unref(packet); // 释放packet资源
    }

fail: // 释放资源
    if (inFmtCtx != NULL)
        avformat_close_input(&inFmtCtx);
    if (h264_fp != NULL)
        fclose(h264_fp);
    if (bsfCtx)
        av_bsf_free(&bsfCtx);
    if (packet)
        av_packet_free(&packet);
    return ret;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值