1:简介
如下图所示,解封装就是将Flv、MP4等文件解封装为视频H264或H265压缩数据,音频解封装为AAC压缩数据。
2:H264结构分析
视频序列由若干时间连续的图像构成,在对其进行压缩时,先将该视频序列分割为若干个小的图像组(Group of Picture,GOP)。H.264中图像以序列为单位进行组织,一个序列是一段图像编码后的数据流,以 I 帧开始,到下一个 I 帧结束。
GOP: 形容⼀个IDR帧 到下⼀个IDR帧之间的间隔了多少个帧。
- 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模式,传统模式,有startcode,SPS和PPS是在ES中。
-
mp4 模式,⼀般 mp4 mkv 都是 mp4 模式,没有 startcode , SPS 和 PPS 以及其它信息被封装在 container 中,每⼀个 frame 前⾯ 4 个字节是这个 frame 的⻓度。
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");
}