[ffmpeg] 解碼API

版本迭代

ffmpeg解碼API通過了好幾個版本的迭代,上一個版本的API是html

咱們如今能看到的不少解碼例子用的都是這兩個,不過如今ffmpeg更推薦用新一代的APIide

 

一般來講,一個packet會被解碼出一個frame,不過也存在一個packet被解碼出多個frame或者多個packet才能解碼出一個frame的狀況,甚至也有些解碼器在輸入以及輸出端上可能會有延遲。所以原來的API在某種程度上存在對調用者誤導的可能,使得調用者認爲輸入的一個或者多個Packet就對應着解碼器所輸出的一個frame,但實際上可能並不是如此。函數

新的API徹底隱藏了「解碼」這一律念,只提供一個輸入packet的接口以及輸出frame的接口,如此一來調用者能夠沒必要了解解碼器的具體細節,只須要了解這兩個接口的調用規則就能寫出適用於全部解碼器的代碼。編碼

 

 

狀態機

新一代API是一個狀態機。調用API是一種動做,API的返回值就是一種狀態,經過動做能夠進行狀態的轉換。正常狀況下,狀態機有6種狀態:spa

  • send 0                :send_packet返回值爲0,正常狀態,意味着輸入的packet被解碼器正常接收。
  • send EAGAIN    :send_packet返回值爲EAGAIN,輸入的packet未被接收,須要輸出一個或多個的frame後才能從新輸入當前packet。
  • send EOF           :send_packet返回值爲EOF,當send_packet輸入爲NULL時纔會觸發該狀態,用於通知解碼器輸入packet已結束。
  • receive 0            :receive_frame返回值爲0,正常狀態,意味着已經輸出一幀。
  • receive EAGAIN:receive_frame返回值爲EAGAIN,未能輸出frame,須要輸入更多的packet才能輸出當前frame。
  • receive EOF       :receive_frame返回值爲EOF,當處於send EOF狀態後,調用一次或者屢次receive_frame後就能獲得該狀態,表示全部的幀已經被輸出。

image

如上圖所示,儘管狀態轉換稍微有些繁瑣,但該狀態轉換圖實際上包含了兩種策略,對兩種策略分別進行分析能對狀態機有一個更爲清晰的瞭解。3d

 

以消耗packet爲主的策略

雖然咱們前面說過輸入的packet並不必定對應於所輸出的frame,不過在這裏爲了方便語言上的描述,在這裏咱們能夠認爲receive_frame是對輸入的packet的一種消耗,當receive_frame返回EAGAIN時就認爲所輸入的packet被徹底消耗。這裏的策略就是對每次所輸入的一個packet,都循環調用receive_frame對該packet進行消耗,直到所輸入的packet消耗完成。code

image

在消耗完一個packet後輸入下一個packet視頻

image

當全部的packet都消耗完成後,調用send_packet輸入NULL,把狀態轉換爲send EOF,最後調用receive_frame把狀態轉換爲receive EOF即完成全部解碼任務。htm

image

 

 

以獲取frame爲主的策略

本策略是先循環調用send_packet直到返回EAGAIN,此時確定能夠輸出frame了blog

image

而後調用receive_frame輸出一幀

image

當全部的packet都輸入完成後,調用send_packet輸入NULL,把狀態轉換爲send EOF,最後調用receive_frame把狀態轉換爲receive EOF即完成全部解碼任務。

image

 

 

API代碼分析

avcodec_send_packet

avcodec_send_packet有以下結構:

image

首先粗略瞭解一下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是實際的解碼入口,它有以下結構

image

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經歷了以下流程。

image

 

avcodec_receive_frame

avcodec_receive_frame有以下結構:

image

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經歷了以下流程。

image

 

關於EAGAIN

咱們前面討論過EAGAIN狀態:

  • avcodec_send_packet返回EAGAIN代表沒法輸入當前packet,須要調用avcodec_receive_frame進行消耗上一個packet。
  • avcodec_receive_packet返回EAGAIN代表沒法獲取當前frame,須要調用avcodec_send_packet輸入更多的packet。

 

通常來講,在實際的實現中,EAGAIN是由bsf相關的函數返回的。

  • 調用avcodec_send_packet時,會先調用av_bsf_send_packet,此時若是用於暫存packet的buffer_pkt中含有有效packet時,av_bsf_send_packet會返回EAGAIN,這會致使avcodec_send_packet也返回EAGAIN。
  • 調用avcodec_receive_frame時,若是沒有可輸出的frame,則會進入decode_receive_frame_internal分支。此時若是用於暫存packet的buffer_pkt中不含有效packet時,ff_bsf_get_packet_ref會返回EAGAIN,這會致使decode_receive_frame_internal返回EAGAIN,從而也使得avcodec_receive_frame也返回EAGAIN。

不過咱們注意到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;
相關文章
相關標籤/搜索