0、问题
遇到的问题:使用ffmpeg直接读取avc1编码的mp4视频,将读取到的帧写下来(H264码流),播放失败。
原因: ffmpeg解码获取的AVPacket只包含视频压缩数据,并没有包含相关的解码信息(比如:h264的sps,pps头信息),这些解码信息包括编码的profile,level,图像的宽和高,deblock滤波器等。没有这些编码头信息解码器就不能进行解码。
1、mp4封装的avc1编码
mp4封装的avc1编码(不带起始码的H264编码格式)视频如果直接用av_read_frame接口读取然后播放是不能播放成功的。因为读取出来的数据不带PPS/SPS、起始码这三种信息。
必须添加上后才能播放。
sps,pps之后就是I帧的数据起始码为00 00 00 01或00 00 01
上图中黑框内就是sps和psp数据,蓝色框为起始码(00 00 00 01)及I帧标志码(06 50)
2、SPS,PPS在ffmpeg
H.264码流的SPS和pps信息存储在AVCidecContext结构体的extradata中,添加这些信息需要使用ffmpeg中名称为”h264_mp4toannexb”的bitstream filter处理。
查看ffmpeg工具支持的Bitstream Filter类型命令
ffmpeg -bsfs
3、新旧接口
《1》、旧接口
int ParseH264ExtraDataInMp4(int stream_id, AVPacket* packet)
{
uint8_t *dummy = NULL; int dummy_size;
AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("h264_mp4toannexb");
if (bsfc == NULL)
{
envir() << "cannot open the h264_mp4toannexb\n";
return -1;
}
av_bitstream_filter_filter(bsfc, format_ctx_->streams[stream_id]->codec,
NULL, &dummy, &dummy_size, NULL, 0, 0);
av_bitstream_filter_close(bsfc);
}
旧接口使用时需要特别注意,否则很容易导致内存泄漏。
《2》、新接口
int ParseH264ExtraDataInMp4(int stream_id, AVPacket* packet)
{
const AVBitStreamFilter * absFilter = NULL;
AVBSFContext *absCtx = NULL;
AVCodecParameters *codecpar = NULL;
absFilter = av_bsf_get_by_name("h264_mp4toannexb");
//过滤器分配内存
av_bsf_alloc(absFilter, &absCtx);
//添加解码器属性
codecpar = format_ctx_->streams[stream_id]->codecpar;
avcodec_parameters_copy(absCtx->par_in, codecpar);
absCtx->time_base_in = format_ctx_->streams[stream_id]->time_base;
//初始化过滤器上下文
av_bsf_init(absCtx);
//AVPacket处理
if (av_bsf_send_packet(absCtx, packet) < 0)
{
printf("av_bsf_send_packet faile \n");
av_bsf_free(&absCtx);
absCtx = NULL;
return -1;
}
if (av_bsf_receive_packet(absCtx, packet) == 0)
{
//printf("av_bsf_receive_packet faile \n");
//av_bsf_free(&absCtx);
//absCtx = NULL;
return 0;
}
av_bsf_free(&absCtx);
absCtx = NULL;
}
《3》、使用伪代码
int main()
{
//ffmpeg的open接口打开MP4封装的avc1码流视频 , AVFormatContext *format_ctx_
//读取一帧av_read_frame,读取到AVPacket packet中
if(视频帧) //只处理视频帧,音频不处理
{
int stream_id = packet->stream_index;
AVCodecContext *codec = NULL;
codec = format_ctx_->streams[stream_id]->codec;
if (codec->codec_id == AV_CODEC_ID_H264)
{
//pps and sps
//const char start_code[4] = { 0, 0, 0, 1 };
//memcpy(packet->data, start_code, 4);
if ((codec->extradata[0] != 0) &&
(ParseH264ExtraDataInMp4(stream_id, packet) == 0))
{
has_extra_data = True;
}
#if 0
{
FILE* wfd = fopen("out.h264", "ab+");
if (wfd)
{
if (has_extra_data)
{
fwrite(codec->extradata, 1, codec->extradata_size, wfd);
}
fwrite(packet->data, 1, packet->size, wfd);
fflush(wfd);
fclose(wfd);
wfd = NULL;
}
}
#endif
}
}
}
4、参考
《1》、https://www.jianshu.com/p/e5e021ccc980
《2》、https://blogs.gentoo.org/lu_zero/2016/03/21/bitstream-filtering/
《3》、http://www.xuhj.top/2018/06/26/ffmpeg-convert-to-ts-stream/
《4》、https://cloud.tencent.com/developer/article/1333501
《5》、sps/pps数据结构
《6》、avc1余h264区别
最新评论