ffmpeg解碼API通過了好幾個版本的迭代,上一個版本的API是html
咱們如今能看到的不少解碼例子用的都是這兩個,不過如今ffmpeg更推薦用新一代的APIide
一般來講,一個packet會被解碼出一個frame,不過也存在一個packet被解碼出多個frame或者多個packet才能解碼出一個frame的狀況,甚至也有些解碼器在輸入以及輸出端上可能會有延遲。所以原來的API在某種程度上存在對調用者誤導的可能,使得調用者認爲輸入的一個或者多個Packet就對應着解碼器所輸出的一個frame,但實際上可能並不是如此。函數
新的API徹底隱藏了「解碼」這一律念,只提供一個輸入packet的接口以及輸出frame的接口,如此一來調用者能夠沒必要了解解碼器的具體細節,只須要了解這兩個接口的調用規則就能寫出適用於全部解碼器的代碼。編碼
新一代API是一個狀態機。調用API是一種動做,API的返回值就是一種狀態,經過動做能夠進行狀態的轉換。正常狀況下,狀態機有6種狀態:spa
如上圖所示,儘管狀態轉換稍微有些繁瑣,但該狀態轉換圖實際上包含了兩種策略,對兩種策略分別進行分析能對狀態機有一個更爲清晰的瞭解。3d
雖然咱們前面說過輸入的packet並不必定對應於所輸出的frame,不過在這裏爲了方便語言上的描述,在這裏咱們能夠認爲receive_frame是對輸入的packet的一種消耗,當receive_frame返回EAGAIN時就認爲所輸入的packet被徹底消耗。這裏的策略就是對每次所輸入的一個packet,都循環調用receive_frame對該packet進行消耗,直到所輸入的packet消耗完成。code
在消耗完一個packet後輸入下一個packet視頻
當全部的packet都消耗完成後,調用send_packet輸入NULL,把狀態轉換爲send EOF,最後調用receive_frame把狀態轉換爲receive EOF即完成全部解碼任務。htm
本策略是先循環調用send_packet直到返回EAGAIN,此時確定能夠輸出frame了blog
而後調用receive_frame輸出一幀
當全部的packet都輸入完成後,調用send_packet輸入NULL,把狀態轉換爲send EOF,最後調用receive_frame把狀態轉換爲receive EOF即完成全部解碼任務。
avcodec_send_packet有以下結構:
首先粗略瞭解一下bsf,即bitstream filter。音頻與視頻編碼後數據會以必定的語法結構進行構建,除了編碼後的數據以外還有一些並不是解碼所必須的語法元素,這些語法元素一般只是在解碼、顯示等過程起到輔助做用,這些語法元素不多使用到,它們的位置通常是位於在編碼後的數據以前,如h264中的SEI。bitstream filter就是對這些語法元素進行調整。
av_bsf_send_packet會把packet輸送到bitstream filter中,在av_bsf_send_packet當中,會判斷用於暫存輸入packet的buffer_pkt是否爲有效packet,若是是有效packet,則代表上次傳入的packet仍未被解碼器消耗,所以沒法接收此次傳入的packet,返回EAGAIN。
if (ctx->internal->buffer_pkt->data || ctx->internal->buffer_pkt->side_data_elems) return AVERROR(EAGAIN);
不然就把當前packet移動到用於暫存的buffer_pkt
av_packet_move_ref(ctx->internal->buffer_pkt, pkt);
decode_receive_frame_internal是實際的解碼入口,它有以下結構
decode_receive_frame_internal須要先從用於暫存的buffer_pkt中取出輸入的packet,這是調用bsfs_poll來實現的。bsfs_poll會執行全部的bitstream filter,最終會調用到ff_bsf_get_packet_ref,在該函數內,會先判斷用於暫存packet的buffer_pkt是否爲有效packet,不是則返回EAGAIN
if (!ctx->internal->buffer_pkt->data && !ctx->internal->buffer_pkt->side_data_elems) return AVERROR(EAGAIN);
有效則取出該packet
av_packet_move_ref(pkt, ctx->internal->buffer_pkt);
取出該packet後就能夠調用codec的decode函數來進行解碼。
整體來看avcodec_send_packet經歷了以下流程。
avcodec_receive_frame有以下結構:
avcodec_receive_frame會先進行判斷,若是解碼器解碼出了一幀,則會調用av_frame_move_ref輸出這一幀,不然繼續調用decode_receive_frame_internal繼續進行解碼。
if (avci->buffer_frame->buf[0]) { av_frame_move_ref(frame, avci->buffer_frame); } else { ret = decode_receive_frame_internal(avctx, frame); if (ret < 0) return ret; }
整體來講avcodec_receive_frame經歷了以下流程。
咱們前面討論過EAGAIN狀態:
通常來講,在實際的實現中,EAGAIN是由bsf相關的函數返回的。
不過咱們注意到avcodec_send_packet中也調用了decode_receive_frame_internal,不過avcodec_send_packet會忽視decode_receive_frame_internal所返回的EAGAIN。
ret = decode_receive_frame_internal(avctx, avci->buffer_frame); if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) return ret;