轉載請注明出處: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。