1. SPS PPS头的重要性
SPS PPS 头在编解码H264时非常重要,里面不仅包含了许多关于视频流的相关信息:比如宽高等。还标志着一个视频流的开头。
一般视频流前开头第一帧是SPS 第二帧是PPS 第三帧才是I帧。目前在网上看到加入SPS PPS头都是在解码时加入,解码时加入ffmpeg有个特别的过滤方法av_bitstream_filter_filter()。编码目前只能深入了解编码底层,找到I帧并且在I帧前插入SPS PPS头。否则即使你是使用ffmpeg编码的视频流,用ffplay播放也是缺少SPS PPS头的。
对于是应该在编码加入SPS PPS头还是解码加入,哪一边兼容其实不重要,我认为最标准的是两边都做这样的兼容,这样程序才更加强壮。作为推流端在推流时加入SPS PPS 头,拉流时判断是否找得到SPS PPS 头,若没有,则加上SPS PPS头。这样就程序就会健壮许多。
2.如何在判断推流时H264文件是否含有SPS PPS头。
使用ffmpeg编码,目前新版的ffmpeg编码接口是avcodec_send_frame()和avcodec_receive_packet() ,前者将存放YUV的AVFrame放入,后者将存放编好的H264 AVPacket取出。然后我们可以用fwrite在av_interleaved_write_frame()封装并发送前写二进制,然后推荐用Binary Viewer查看写的h264二进制文件。
FILE *file = fopen("C:\\Users\\admin\\Desktop\\123456.h264", "wb");//(放在编码和播放的循环外面)
fwrite(enc_packet.data, sizeof(enc_packet), 1, file);
但是不一定开头就是SPS和PPS或者I帧,而且这时一般是没有SPS 和 PPS头的,查看二进制文件时搜索一下00000001来查找。
SPS 十六进制值为:0x67
PPS 十六进制值为:0x68
I帧 十六进制值为:0x65
但是在I帧和SPS开头都会有00000001(PPS接在SPS后,是一体的)
如果找到00 00 00 01 67,则这一帧为I帧。
3.如何插入SPS PPS头
SPS和PPS头在你用ffmpeg编码时已经获取到了,存放在编码器上下文AVCodecContext的extradata中,可以从中取出并插入H264中。
前面说到了SPS和PPS是在I帧之前
所以我们就应该在每一个AVPacket的h264中查找i帧
AVPacket中有个重要的参数可以用来判断是否含有i帧:flags
用flag与AV_PKT_FLAG_KEY进行与操作就可以判断当前这一帧是否为I帧。
找到I帧后,在I帧前面插入extradata就完成插帧,记得扩大指针的大小。
//声明尽量不要在推流的while循环里做,避免内存泄漏
AVPacket enc_packet;
char* cPacketData = new char(oCodecCtx->extradata_size * sizeof(char*));
uint8_t* cExtradata = (uint8_t *)malloc((100000) * sizeof(uint8_t));
...
...
...
...
...
...
if (enc_packet.flags &AV_PKT_FLAG_KEY)//找到带I帧的AVPacket
{
cPacketData = (char*)enc_packet.data;
int n = 0;
while ((char*)enc_packet.data)
{
//printf("%c\n", cPacketData[n]);
if (cPacketData[n] == 0 && cPacketData[n + 1] == 0 && cPacketData[n + 2] == 0 && cPacketData[n + 3] == 1 && cPacketData[n + 4] == 101)
{
//找到I帧,插入SPS和PPS
memcpy(cExtradata, oCodecCtx->extradata, oCodecCtx->extradata_size);
memcpy(cExtradata + oCodecCtx->extradata_size, enc_packet.data, enc_packet.size);//&enc_packet.data[0]
enc_packet.size += oCodecCtx->extradata_size;
enc_packet.data = cExtradata;
break;
}
if (n == enc_packet.size)
{
break;
}
cPacketData++;
n++;
}
}
基本到这里都完成了插帧。