<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          刨根問底 | FFmpeg 解碼 API 以及在解碼過程中存在的丟幀問題

          共 4267字,需瀏覽 9分鐘

           ·

          2021-11-29 10:29

          背景

          在優(yōu)化視頻客觀全參考算法(主要是PSNR, SSIM, MS-SSIM)時,我們首先利用FFmpeg提供的API(avcodec_send_packet(),avcodec_receive_frame())對輸入的兩個MP4文件轉成對應的YUV格式的數(shù)據(jù)文件,然后再基于這兩份YUV數(shù)據(jù)文件進行計算,得到對應的結果。

          但是,我們發(fā)現(xiàn),MP4文件轉成YUV數(shù)據(jù)后,總是會發(fā)生丟失視頻最后幾幀的現(xiàn)象。

          為了弄清楚這個問題,查閱了FFmpeg的源碼,并參考了網(wǎng)絡上的資料,然后總結出了這篇文章。

          FFmpeg的編解碼API

          從3.1版本開始,F(xiàn)Fmpeg提供了新的編解碼API來對音視頻數(shù)據(jù)進行編解碼操作,從而實現(xiàn)對輸入和輸出的解耦:

          • 解碼API

          • avcodec_send_packet()

          • avcodec_receive_frame()

          • 編碼API

          • avcodec_send_frame()

          • avcodec_receive_packet()

          /**
          ?*?@ingroup?libavc
          ?*?@defgroup?lavc_encdec?send/receive?encoding?and?decoding?API?overview
          ?*?@{
          ?*
          ?*?The?avcodec_send_packet()/avcodec_receive_frame()/avcodec_send_frame()/
          ?*?avcodec_receive_packet()?functions?provide?an?encode/decode?API,?which
          ?*?decouples?input?and?output.
          ?*?...
          ?*/

          同時,也正是從3.1版本開始,之前的編解碼API也被標注為deprecated:

          • 解碼API
          • avcodec_decode_video2()
          • avcodec_decode_audio4():
          • 編碼API
          • avcodec_encode_video2()
          • avcodec_encode_audio2()
          attribute_deprecated
          int?avcodec_decode_audio4(AVCodecContext?*avctx,?AVFrame?*frame,
          ??????????????????????????int?*got_frame_ptr,?const?AVPacket?*avpkt)
          ;

          在我們的工具中,我們采用了新的解碼API:avcodec_send_packet()和avcodec_receive_frame(),實現(xiàn)視頻幀的解碼,并將解碼后的數(shù)據(jù)轉成YUV數(shù)據(jù)。具體的代碼片段如下:

          int?process_frame()?{
          ????......
          }

          //decode?operation.
          while?(!av_read_frame(fmt_ctx,?pkt))?{
          ????if?(pkt->stream_index?!=?video_stream_idx)?{
          ????????continue;
          ????}

          ????int?packet_new?=?1;
          ????while?(process_frame(fmt_ctx,?dec_ctx,?video_stream->codecpar,?
          ?????????????????????????frame,?pkt,?&packet_new)?>?0)?{
          ????????i++;
          ????};
          ????av_packet_unref(pkt);
          }

          從代碼可以看出,i是解碼幀的總數(shù),但是我們運行之后發(fā)現(xiàn),一個252幀的視頻,最終只得到了248幀。

          send_packet & receive_frame

          為了加深對解碼API的了解,以便能查出問題原因,我們查閱了FFmpeg的代碼,從代碼的注釋中,我們發(fā)現(xiàn)了問題:我們沒有遵循API的使用規(guī)范,同時FFmpeg在注釋中也說明了為什么會出現(xiàn)我們遇到的問題。

          /**
          ??*?...?
          ??*?At?the?beginning?of?decoding?or?encoding,?the?codec?might?accept?multiple
          ??*?input?frames/packets?without?returning?a?frame,?until?its?internal?buffers
          ??*?are?filled.?This?situation?is?handled?transparently?if?you?follow?the?steps
          ??*?outlined?above.
          ??*?
          ??*?End?of?stream?situations.?These?require?"flushing"?(aka?draining)?the?codec,
          ??*?as?the?codec?might?buffer?multiple?frames?or?packets?internally?for
          ??*?performance?or?out?of?necessity?(consider?B-frames).
          ??*?...
          ??*/

          也就是說,為了提升性能或出于其他的考慮,解碼器會在內部緩存多個frames/packets。因此,當流結束的時候,需要對解碼器執(zhí)行flushing操作,以便獲取解碼器緩存的frames/packets。

          我們的工具中,在流結束之后,并沒有執(zhí)行flushing操作,因此就出現(xiàn)了解碼過程丟幀的現(xiàn)象。按照FFmpeg的指導,我們補充了如下的邏輯,以便獲取解碼器中緩存的幀。

          //Flush?remaining?frames?that?are?cached?in?the?decoder
          int?packet_new?=?1;
          av_init_packet(pkt);
          pkt->data?=?NULL;
          pkt->size?=?0;
          while?(process_frame(fmt_ctx,?dec_ctx,?video_stream->codecpar,?
          ?????????????????????frame,?pkt,?&packet_new)?>?0)?{
          ????i++;
          ????packet_new?=?1;
          };

          再次運行,我們發(fā)現(xiàn),丟幀問題消失了。

          FFMPeg 解碼 API 狀態(tài)機

          avcodec_send_packet返回值

          從FFmpeg的源碼中,我們會發(fā)現(xiàn),正常情況下,avcodec_send_packet()函數(shù)的返回值主要有以下三種:

          • 0: on success.
          • EAGAIN: input is not accepted in the current state - user must read output with avcodec_receive_frame() (once all output is read, the packet should be resent, and the call will not fail with EAGAIN).
          • EOF: the decoder has been flushed, and no new packets can be sent to it (also returned if more than 1 flush packet is sent).

          avcodec_receive_frame返回值

          同樣的,正常情況下,avcodec_receive_frame()函數(shù)的返回值主要有以下三種:

          • 0: success, a frame was returned.
          • EAGAIN: output is not available in this state - user must try to send new input.
          • EOF: the decoder has been fully flushed, and there will be no more output frames.

          解碼 API 狀態(tài)機

          avcodec_send_packet()和avcodec_receive_frame()不同的返回值代表了解碼器的不同的狀態(tài)。

          對API的調用實際上是一種動作,而API的返回值則用來標志當前解碼器的狀態(tài)。因此,解碼API的整個過程實際上就是一個狀態(tài)機。

          根據(jù)avcodec_send_packet返回值和avcodec_receive_frame返回值中的介紹,可以得到正常情況下,解碼過程的狀態(tài)機,如下圖所示。

          在圖中,節(jié)點代表狀態(tài)(API的返回值),箭頭代表API的調用。藍色表示和avcodec_send_packet()相關,紅色表示和avcodec_receive_frame()相關。

          我們修復版本的解碼實現(xiàn)實際上就是對如上圖所示的狀態(tài)機的實現(xiàn)。

          而如果在實現(xiàn)的時候,沒有處理如下圖所示的狀態(tài),則會導致無法獲取視頻最后幾幀的問題。

          思考 & 總結

          • 源碼面前,了無秘密。侯捷老師說過“源碼面前,了無秘密”。工作中發(fā)現(xiàn),源碼確實是我們獲取知識和經(jīng)驗的一個非常有效的途徑,尤其是那些好的開源項目的源碼,更是如此。

          • 源碼還是我們解決問題的強有力的手段之一。對于這些優(yōu)秀的開源項目的源碼而言,代碼只是一個部分,源碼中的注釋、文檔等會為我們提供足夠的資源。這次問題的解決就是依賴源碼,之前在Android攝像頭Mock技術的研究中,也是在查閱Android相關源碼后才有了思路。因此,當我們在工作中遇到問題的時候,第一手的資料還是源碼(當然,要有源碼才行),其次才是官方文檔,最后才是網(wǎng)絡上的其他資源。

          • 看了那么多的源碼才發(fā)現(xiàn):優(yōu)秀的項目是那么的一致,而糟糕的項目,各有各的糟糕之處。


          技術交流,歡迎加我微信:ezglumes ,拉你入技術交流群。

          私信領取相關資料

          推薦閱讀:

          音視頻開發(fā)工作經(jīng)驗分享 || 視頻版

          OpenGL ES 學習資源分享

          開通專輯 | 細數(shù)那些年寫過的技術文章專輯

          Android NDK 免費視頻在線學習?。?!

          你想要的音視頻開發(fā)資料庫來了

          推薦幾個堪稱教科書級別的 Android 音視頻入門項目

          覺得不錯,點個在看唄~

          瀏覽 99
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大鸡八操逼视频免费试试看 | 大香蕉2020 | 免费男女激情内射视频网站大全 | 视频在线一区 | 欧美成人无码A片免费一区澳门 |