音视频解封装--解封装分离H264文件

1:简介

 如下图所示,解封装就是将Flv、MP4等文件解封装为视频H264或H265压缩数据,音频解封装为AAC压缩数据。


2:H264结构分析

        视频序列由若干时间连续的图像构成,在对其进行压缩时,先将该视频序列分割为若干个小的图像组(Group of Picture,GOP)。H.264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以 I 帧开始,到下一个 I 帧结束。

GOP: 形容⼀个IDR帧 到下⼀个IDR帧之间的间隔了多少个帧。

IDR (Instantaneous Decoding Refresh,即时解码刷新 ):
         ⼀个序列的第⼀个图像叫做 IDR 图像 (⽴即刷新图像), IDR 图像都是 I 帧图像。
  • IDR帧的作用是立即刷新,防止错误传播,当解码器解码到IDR图象时,立即将参考帧队列清空,重新查找参数集,开始一个新的序列。
  • IDR帧一定是I帧,当I帧不一定是IDR帧;对于IDR帧来说,其后的帧不能参考引用IDR帧之前的帧内容,但是对于不同I帧来说位于其后的I帧和P帧可以参考I帧之前的帧。

         H264原始码流(裸流)是由一个接一个的NALU组成,其功能分为两层,vcl(视频编码层)和NAL(网络提取层):

  • VCL:包括核⼼压缩引擎和块,宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹
    络进⾏⾼效的编码;
  • NAL:负责将VCL产⽣的⽐特字符串适配到各种各样的⽹络和多元环境中,覆盖了所有⽚级
    以上的语法级别

         在VCL进行数据传输或存储之前,VCL数据会被封装到NAL单元中。

3:一帧数据和NALU的关联

        经过前面介绍,我们知道一帧图片经过H264编码器后,被编码为一个或多个片(slice)。而装载这些片的载体就是NALU(所以一帧数据会出现多个NALU单元)。

         片又作为宏块(Macroblock)的载体.

         一个NALU = NALU头部信息 + 原始字节序列负荷(RBSP,Raw Byte Sequence Payload)。

         原始的H264 NALU单元由[StartCode][NALU Header][NALU Payload]三部分组成,其中Start Code用于表示一个NALU单元的开始,必须是“00 00 00 01”或“00 00 01”。(对于FFMPEG解复用后,MP4文件读取出来的packet是不带StartCode,但TS文件是带StartCode,所以对于MP4

文件解复用需加上StartCode信息)

4:H264 annexb模式

H264有两种封装

  • nnexb模式,传统模式,有startcodeSPSPPS是在ES中。
  • mp4 模式,⼀般 mp4 mkv 都是 mp4 模式,没有 startcode SPS PPS 以及其它信息
    被封装在 container 中,每⼀个 frame 前⾯ 4 个字节是这个 frame 的⻓度。
        很多解码器只⽀持annexb 这种模式,因此需要将 mp4 做转换: ffmpeg 中⽤
h264_mp4toannexb_filter 可以做转换。
实现:
1 const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
2 AVBSFContext *bsf_ctx = NULL; 
3 av_bsf_alloc(bsfilter, &bsf_ctx); 
4 添加解码器属性 
    avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->cod ecpar); 
5 初始化
    av_bsf_init(bsf_ctx);

5:代码实现

#include <iostream>
#include <stdio.h>

extern "C"
{
#include <libavformat/avformat.h>
}
using namespace std;

int main()
{
    const char *default_filename = "believe.mp4";
    FILE *outfp=fopen("mp4ToH264.h264","wb");
    //1:分配接复用器上下文
   AVFormatContext *ctx = NULL;

   int vedio_index = -1;
   //2:打开文件
   int ret = avformat_open_input(&ctx,default_filename,NULL,NULL);
   if(ret != 0)
   {
      printf("[error]avformat_open_input\n");
       if(ctx)
       {
           avformat_close_input(&ctx);
       }
       getchar();
   }
   //3:获取信息
   ret = avformat_find_stream_info(ctx,NULL);
   if(ret < 0)
   {
       char buf[1024] = {0};
       av_strerror(ret,buf,sizeof(buf));
       printf("open %s failed:%s\n",default_filename,buf);
       if(ctx)
       {
           avformat_close_input(&ctx);
       }
       getchar();
   }
   //4:找到视频流
   vedio_index = av_find_best_stream(ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
   if(vedio_index < 0)
   {
       printf("av_find_best_Stream failed");
       return 0;
   }
   //5:读取一帧数据
   AVPacket *pkt = NULL;
   pkt = av_packet_alloc();
   av_init_packet(pkt);
   //ffmpeg -bsfs命令可以查看ffmpeg工具支持的Bitstream Filter类型。
   const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");

   //6:初始化过滤器上下文
   AVBSFContext *bsf_ctx = NULL;
   av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;

   //7:添加解码器属性,从AVFormatContext中获取
   avcodec_parameters_copy(bsf_ctx->par_in,ctx->streams[vedio_index]->codecpar);
   //av_bsf_init(bsf_ctx);
   //8:读取一帧数据
   while(av_read_frame(ctx,pkt) >= 0)
   {
        if (pkt->stream_index == vedio_index)
        {
            int input_size = pkt->size;
            int out_pkt_count = 0;
            if(av_bsf_send_packet(bsf_ctx,pkt) != 0)
            {
                av_packet_unref(pkt);
                continue;
            }
            av_packet_unref(pkt);   // 释放资源
            while(av_bsf_receive_packet(bsf_ctx,pkt) == 0)
            {
                out_pkt_count++;
                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);
            }
            // TS流可以直接写入
            /*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);        // 释放内存
        }
   }
   printf("success");

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值