-
H264
H264是视频压缩编码标准。视频⽂件的传输是⼀个极⼤的问题:⼀段分辨率为19201080的视频,每个像素点为RGB占⽤3个字节,帧率是25的视频,对于传输带宽的要求是:192010803*25/1024/1024=148.315MB/s,换成bps则意味着视频每秒带宽为1186.523Mbps,这样的速率对于⽹络存储是不可接受的。因此视频压缩和编码技术应运⽽⽣。 -
I帧、P帧、B帧
视频文件是由单帧图像组合而来,H264利用帧内像素块的相似性以及帧间图像的相似性对视频文件进行压缩。对于H264文件,I帧、P帧、B帧是一个绕不开的话题;I帧:帧内编码帧,可以看作是1帧图像经过压缩后的产物;P帧:前向预测编码帧,必须参考前1帧才能完成解码;B帧:双向预测帧,必须参考前后帧才能完成解码。在解码器中会缓存一些帧:P帧、B帧,当碰到IDR帧时,会清空缓存器中的帧;对于解码器一开始要收到SPS、PPS初始化解码器,否则是解不出正常帧。 -
H264码流分析
通常我们使用H264码流分析工具时,我们会发现H264码流是由连续的帧组成的,一组连续的帧成为1个GOP即1组,对于1个GOP来说,通常包括SPS(序列参数集,编码视频序列的全局参数)PPS(图像参数集,⼀个序列中某⼀幅图像或者某⼏幅图像的参数),IDR帧(第一个出现的I帧成为IDR帧,1个GOP中通常只包含1个IDR帧),P帧(GOP中剩余帧基本都是P帧);GOP描述如下:
H264码流结构如图:
-
NALU
H264码流是由一个个NALU组成,NALU分为两层:VCL(视频编码层)、NAL(网络提取层)。VCL:核心数据压缩块,NAL:将VCL封装在NAL进行传输。NAL认为是NALU的基本数据单元。通常1帧图像可以看作1个或多个NALU。
H264有两种封装:⼀种是annexb模式(重点)传统模式,有startcode(0xFFF),SPS和PPS是在ES中;⼀种是mp4模式,⼀般mp4、mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度。MP4⽂件读取出来的packet是不带startcode,对于MP4格式使用filter将Frame前面4字节去掉,加上startcode;TS⽂件读取出来的packet带了startcode。
NALU结构单元的主体结构如所示;⼀个原始的H.264NALU单元通常由[StartCode][NALUHeader][NALUPayload]三部分组成,其中StartCode⽤于标示这是⼀个NALU单元的开始,必须是"00000001"或"000001",除此之外基本相当于⼀个NAL header+RBSP,RBSP认为是包含真正的数据。
使用mp4文件提取H264码流并进行封装,步骤如下:
(1)avformat_open_input将输入媒体文件与解复用器相关联;
(2)av_find_best_stream在媒体流中找到视频流标签;
(3)av_bsf_init初始化过滤器,过滤器用于将无startcode的H264码流转为有startcode的码流;
(4)循环读取视频文件中的视频包av_read_frame(in_file_ctx,pkt),将视频包发到过滤器,从过滤器中接收H264包,并将包写到输出文件。
完整代码入下:
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
void mp4_h264()
{
char* in_filename = "/home/yx/media_file/believe.mp4"; // 输入文件路径
char* out_filename = "/home/yx/media_file/believe.h264"; // 输出文件路径
AVFormatContext* in_file_ctx = NULL; // 解复用器上下文
FILE* out_h264_fd = NULL; // 输出文件句柄
int video_index = -1; // 视频标签
AVPacket* pkt = av_packet_alloc(); // 保存视频包
out_h264_fd = fopen(out_filename,"w"); // 打开输出文件
avformat_open_input(&in_file_ctx,in_filename,NULL,NULL); // 将输入媒体流与输入文件上下文进行关联
avformat_find_stream_info(in_file_ctx, NULL); // 查找媒体流信息
video_index = av_find_best_stream(in_file_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0); // 查找视频流对应的标签
printf("video_index:%d\n",video_index);
AVBitStreamFilter* bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext* bsf_ctx = NULL; // 过滤器上下文
av_bsf_alloc(bsfilter,&bsf_ctx); // 分配过滤器上下文
avcodec_parameters_copy(bsf_ctx->par_in, in_file_ctx->streams[video_index]->codecpar); // 将输入流中的信息复制到过滤器
av_bsf_init(bsf_ctx); // 初始化过滤器
while(1)
{
if(av_read_frame(in_file_ctx,pkt)<0) // 输入文件中读取包
{
printf("read frame fail\n");
break;
}
if(pkt->stream_index == video_index)
{
if(av_bsf_send_packet(bsf_ctx,pkt) != 0) // 将视频包发到过滤器,过滤器为其添加startcode
{
av_packet_unref(pkt);
continue;
}
av_packet_unref(pkt);
while(1)
{
if(av_bsf_receive_packet(bsf_ctx,pkt) != 0) // 从过滤器接收包,此时包包含startcode
break;
if(fwrite(pkt->data,1,pkt->size,out_h264_fd) != pkt->size) // 将包写到H264文件中
printf("fwrite data fail to out_h264_fd\n");
av_packet_unref(pkt); // 写完就释放包
}
}
}
if(out_h264_fd)
fclose(out_h264_fd);
if(bsf_ctx)
av_bsf_free(&bsf_ctx);
if(pkt)
av_packet_free(&pkt);
if(in_file_ctx)
avformat_close_input(&in_file_ctx);
printf("finish\n");
}
int main()
{
mp4_h264();
printf("Hello World!\n");
return 0;
}