(原)从mp4,flv文件中解析出h264和aac,送解码器解码失败
转载请注明出处:http://www.cnblogs.com/lihaiping/p/5285166.html
今天在做本地文件解码测试,发现从mp4,flv文件中读出来的帧数据,h264和aac帧直接送解码器解码,发现解码失败,但文件放在pc上用ffplay和vlc却都能播放,而且这个测试的视频文件是用ffmpeg.exe进行转码出来的,所以应该不存在解码不了的问题,那问题在哪呢?
百度了下,网上有人说mp4文件里面封装的h264有两种格式:h264和avc1:
而这两种格式的差别是:
AVC1 描述:H.264 bitstream without start codes.一般通过ffmpeg转码生成的视频,是不带起始码0×00000001的。
H264 描述:H.264 bitstream with start codes.一般对于一下HDVD等电影的压制格式,是带有起始码0×00000001的。
所以我又用vlc播放查看了下,原来真的是avc1,这才发现原来自己接触了这么久的流媒体,连avc1都没听过,哎,悲哀。
那要如何才能解码呢?同时我查看了一下,ffmpeg中对h264的avc1并没有设置单独的解码器,只有一个h264的解码器codeid,那它是怎么实现解码avc1的?又是如何区分的呢?
问题1:如何解码?
在这里其实有人遇到了和我一样的问题:http://stackoverflow.com/questions/11330764/ffmpeg-cant-decode-h264-stream-frame-data
同时还有人在论坛上讨论该如何解决:http://bbs.csdn.net/topics/390538510
有人说avc1是原始的NAL打包格式,就是开始的若干字节(1,2,4字节)是NAL的长度,而不是start_code,此时必须借助某个全局的数据来获得编码器的profile,level,PPS,SPS等信息才可以解码。
既然这样,那我要如何才能获得pps,sps等这些信息呢?有人写过:http://blog.csdn.net/gavinr/article/details/7183499,在这篇文章里面,需要注意几个之前没认真了解的新地方:
1)pps及sps不能从packet获得,而是保存在AVCodecContext的extradata数据域中
2)如何从extradata中解析出sps及pps呢?ffmpeg中提供了一个流过滤器"h264_mp4toannexb"可以完成
解决方法为:使用ffmpeg提供的h264_mp4toannexb流过滤器进行解决:具体方法可以参考:http://blog.csdn.net/leixiaohua1020/article/details/11800877
问题2:ffmpeg中没有对avc1使用单独的解码器,而是和h264同样使用同一个解码器?那它是如何区分的呢?
这个问题,需要仔细翻看一下ffmpeg的源代码了,在ff_h264_decode_init函数中有这样的一段代码:
if (avctx->extradata_size > 0 && avctx->extradata) { ret = ff_h264_decode_extradata(h, avctx->extradata, avctx->extradata_size); if (ret < 0) { ff_h264_free_context(h); return ret; } }
继续往下看,看ff_h264_decode_extradata函数中做了些什么?
int ff_h264_decode_extradata(H264Context *h, const uint8_t *buf, int size) { AVCodecContext *avctx = h->avctx; int ret; if (!buf || size <= 0) return -1; if (buf[0] == 1) { int i, cnt, nalsize; const unsigned char *p = buf; h->is_avc = 1; if (size < 7) { av_log(avctx, AV_LOG_ERROR, "avcC %d too short\n", size); return AVERROR_INVALIDDATA; } /* sps and pps in the avcC always have length coded with 2 bytes, * so put a fake nal_length_size = 2 while parsing them */ h->nal_length_size = 2; // Decode sps from avcC cnt = *(p + 5) & 0x1f; // Number of sps p += 6; for (i = 0; i < cnt; i++) { nalsize = AV_RB16(p) + 2; if(nalsize > size - (p-buf)) return AVERROR_INVALIDDATA; ret = decode_nal_units(h, p, nalsize, 1); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "Decoding sps %d from avcC failed\n", i); return ret; } p += nalsize; } // Decode pps from avcC cnt = *(p++); // Number of pps for (i = 0; i < cnt; i++) { nalsize = AV_RB16(p) + 2; if(nalsize > size - (p-buf)) return AVERROR_INVALIDDATA; ret = decode_nal_units(h, p, nalsize, 1); if (ret < 0) { av_log(avctx, AV_LOG_ERROR, "Decoding pps %d from avcC failed\n", i); return ret; } p += nalsize; } // Store right nal length size that will be used to parse all other nals h->nal_length_size = (buf[4] & 0x03) + 1; } else { h->is_avc = 0; ret = decode_nal_units(h, buf, size, 1); if (ret < 0) return ret; } return size; }
所以再这里h264的解码器是通过AVCodecContext的extradata在ff_h264_decode_init的时候,进行了区分,同时也进行初始化解码。
=================================================================
aac解码失败的问题:
http://blog.csdn.net/leixiaohua1020/article/details/39767055 这篇文章说了,视音频分离器(Demuxer),并不适用于一些格式。对于MP3编码的音频是没有问题的。但是在分离MP4/FLV/MKV等一些格式中的AAC编码的码流的时候,得到的AAC码流是不能播放的。原因是存储AAC数据的AVPacket的data字段中的数据是不包含7字节ADTS文件头的“砍头”的数据,是无法直接解码播放的(当然如果在这些数据前面手工加上7字节的ADTS文件头的话,就可以播放了)。
adts?又是一个新概念,怎么解决?谷歌不在问度娘,http://blog.csdn.net/tx3344/article/details/7414543,这篇文章介绍了adts的概念。
本来是想通过和h264的avc1方案一样来解决,但发现使用aac_adtstoasc流过滤器是行不通的,因为他一直是返回0,于是我看了一下ffmpeg中这个函数的源码,原来这个函数的源码就是返回0的.测试结果也是解码不了。
后面找到了一篇文章:http://blog.chinaunix.net/uid-24922718-id-3692670.html,本来想参考着这里面的方法实现:
char bits[7] = {0}; int sample_index = 0 , channel = 0; char temp = 0; int length = 7 + audiopack.size; sample_index = (audioCodecCtx->extradata[0] & 0x07) << 1; temp = (audioCodecCtx->extradata[1]&0x80); switch(audioCodecCtx->sample_rate) { case 44100: { sample_index = 0x7; }break; default: { sample_index = sample_index + (temp>>7); }break; } channel = ((audioCodecCtx->extradata[1] - temp) & 0xff) >> 3; bits[0] = 0xff; bits[1] = 0xf1; bits[2] = 0x40 | (sample_index<<2) | (channel>>2); bits[3] = ((channel&0x3)<<6) | (length >>11); bits[4] = (length>>3) & 0xff; bits[5] = ((length<<5) & 0xff) | 0x1f; bits[6] = 0xfc; fwrite(bits,1,7,f);
结果失败了,添加这几个自己的adts头还是一样解码不出数据。
最后参考http://blog.itpub.net/30168498/viewspace-1576794/这个文章的代码,进行应用,顺利解码aac。