2013年6月6日 星期四

FFMPEG -- 將 H.264 的資料封裝成 MP4 檔案

在開發 iphone 下的串流app時,需要一個將串流資料存成mp4檔案的功能,以下會紀錄此次實驗過程中遇到的問題與解決方法。

基本概念

透過下列的 3 個 FFMPEG API,將資料封裝成 mp4 格式。
  • av_write_header 
  • av_interleaved_write_frame 
  • av_write_trailer
想直接看程式的人,可以參考這個範例,想直接在 iphone下測試,可以下載我寫的一個簡單範例。 



實作過程中的故障排除


第一次測試 (ffmpeg)
測試方式:撥放本地端檔案,並且將檔案儲存在本地。
測試結果:VLC可以辨識檔案,可以知道影像檔長度,但撥放時並沒有影像。 
問題分析: 
1. 使用 mediainfo 檢視檔案封裝,比較其不同處。 
原始檔案為 ftypmp42, 儲存後的檔案為ftypisom。此部分沒有關係。
2. 使用 VLC 進行播放,強制印出除錯訊息,指令如下
"C:\Program Files (x86)\VideoLAN\VLC\vlc" -vvv test.mp4 
接著可以透過下列操作來觀看錯誤訊息
[工具] -> [訊息] 或是
[檢視] -> [增加介面] -> [除錯紀錄] 
分析log,由下圖可知,VLC已經正確解開 mp4 內的各個box

參考下圖,推測無法正確撥放的原因應與除錯訊息中的 "insane cropping not completely supported" 有關。
 

初步推測是 mp4 header 的資料不足,導致無法正確解碼,比對原始檔案,發現新產生的檔案中沒有正確的 avcC box (放置 SPS, PPS),使用 ultraedit 手動加入 avcC box的內容之後,檔案便可以使用 VLC與 QuickTime 播放。 
剩下的問題就是如何透過程式碼,讓avcC box 正確的加入到 mp4 檔案內。
參考 ffmpeg mp4toannexb 原始碼,可以得知 ffmpeg 透過 extradata 欄位取得 SPS 與 PPS,因此只要正確的設定此欄位, avcC box 就可以正確的產生。

第二次測試 (live555+ffmpeg)
這次測試使用 live555 所擷取的封包當成輸入資料,透過 ffmpeg 的3個API,將資料封裝成 mp4 檔案。
使用 live555 作法也是類似的,不同點在於需要自行取得 SPS/PPS,並且放入到 extradata內。 
An example of CodecCtx->extradata


注意1:每次儲存時,應該從iFrame開始存,畫面初期才會正常,否則播放初期可能會呈現馬賽克,
  • 若使用 ffmpeg 讀取網路封包,則需要判斷 packet.flags 中是否存在 AV_PKT_FLAG_KEY
  • 若使用 live555 讀取網路封包,則需要判斷 nal_unit_type&0x1f=0x06


注意2:關於pts,dts的設定
當呼叫avformat_write_header()時要記住此時的pts與dts(將其稱之為pts_init, dts_init),而當呼叫 av_interleaved_write_frame(fc, &pkt)時,記得要將此時的 pkt.pts, pkt.dts 的值減去pts_init, dts_init,這樣才是正確的設定值。