百倍變速--解碼到底能不能丟 非參考幀 ?FFmpeg 有話說!!!

本文首發公眾號 音視頻開發進階 ,鏈接:https://mp.weixin.qq.com/s/qPBSe0itF40ek1laIZRQnw

昨天周六,群里面還有人在技術交流!!

默默吐槽一下:這些人真卷啊,大周末還搞技術,是游戲不好玩還是電影不好看。

image

一開始是討論剪映的 100 變速是如何實現,群主作為相關人士肯定就不方便透露這些了,不過也有其他大佬給出了思路。

討論焦點還是圍繞如何丟幀展開的。

百倍變速,比如正常速度下一幀是播放第 1s 時刻的內容,而變速后要播放 100s 時刻了。

此時的邏輯有以下幾種情況:

  1. 如果下一個播放時刻要超過目前 GOP 大小了,那么就及時 seek 到離目標 pts 最近的關鍵幀,比如從 1s 變速后到了 100s ,那就 seek 到第 98s 。
  2. 如果下一個播放時刻在同一 GOP 內了,那就繼續往下解碼,判斷解碼后的幀 pts 不需要顯示就直接丟棄再接著往后解 ,直到接近了目標時間點就顯示。
  3. seek 后的時間點沒達到目標時間點的情況,需要繼續解碼的可以重復第二步。

以上是針對群內大佬的總結,拿著小本本趕緊記下來。

此時,還有大佬對解碼丟幀給出了其他意見:

image

主要是針對非參考幀的丟幀處理,也是文章的重點內容。

當我們通過 av_read_frame 得到一個 AVPacket 之后,可以判斷它的 nal_ref_idc 值來決定是否要丟棄。

如果為 0 ,說明其他幀不需要參考該幀,可以直接丟棄不發送給解碼器,而不是解碼后再丟幀。

如果你不清楚 nal_ref_idc 是什么意思 ? 那么可以了解一下 H.264 碼流 NALU 的概念。

H.264 碼流傳輸時以 NALU 的形式進行,NALU 主要由一個字節的 HAL Header 和 RBSP 兩部分組成。

HAL Header 的組成形式如下圖所示:

image

HAL Header 的計算如下:

forbidden_zero_bit(1bit) + nal_ref_idc(2bit) + nal_unit_type(5bit)

nal_unit_type 不同的值代表不同類型的幀,解析 AVPacket 完全可以得到如上的信息,后面在公眾號音視頻開發進階繼續更新文章詳解如何計算

所以,在解碼時完全是可以丟棄這些非參考幀的,放心大膽地操作吧。

image

而且丟非參考幀的操作也是經過了產品億級檢測的,這一點我確實可以作證!!!

FFmpeg 中的丟幀

以上的丟幀邏輯是根據 H.264 規范來的,那么在 FFmpeg 的源碼中有沒有針對這一邏輯做處理呢?

那必然是有的啊!!!

如果仔細看 ffplay 的源碼,在源碼中有如下的調用方式:

/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg)
{
    // 省略部分代碼
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream *st = ic->streams[i];
        enum AVMediaType type = st->codecpar->codec_type;
        // AVDISCARD_ALL  拋棄所有的幀
        st->discard = AVDISCARD_ALL;
        if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1)
            if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0)
                st_index[type] = i;
    }
    
    // 省略部分代碼
    if (!video_disable)
        st_index[AVMEDIA_TYPE_VIDEO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    if (!audio_disable)
        st_index[AVMEDIA_TYPE_AUDIO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                st_index[AVMEDIA_TYPE_AUDIO],
                                st_index[AVMEDIA_TYPE_VIDEO],
                                NULL, 0);

    /* open the streams */
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        // 開啟解碼線程
        stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
    }
    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
       // 開啟解碼線程
        ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
    }
    // 省略代碼
}

read_thread 方法運行在單獨線程上,該方法首先進行解封裝操作,然后開啟一個線程進行解碼,接下來調用 av_read_frame 方法讀取 AVPacket 放到隊列中供解碼線程使用。

在 av_find_best_stream 方法之前先將 discard 置為 AVDISCARD_ALL ,過濾掉 AVStream 中的數據,接下來就是 stream_component_open 操作。


/* open a given stream. Return 0 if OK */
static int stream_component_open(VideoState *is, int stream_index)
{
    // 省略部分代碼
    // AVDISCARD_DEFAULT 默認模式,過濾無效數據
    ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
     // 省略部分代碼
        if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
            goto out;
    // 省略部分代碼
    case AVMEDIA_TYPE_VIDEO:
     // 省略部分代碼
        if ((ret = decoder_start(&is->viddec, video_thread, "video_decoder", is)) < 0)
            goto out;
    // 省略部分代碼
}

stream_component_open 方法又將 discard 置為了 AVDISCARD_DEFAULT ,僅過濾掉無效數據。

繼續跟進這個 discard 字段,就會有新發現了!

關于 discard 的所有類型值和使用方式,FFmpeg 中有如下定義:

/**
 * @ingroup lavc_decoding
 */
enum AVDiscard{
    /* We leave some space between them for extensions (drop some
     * keyframes for intra-only or drop just some bidir frames). */
     // 不拋棄,不放棄任何數據
    AVDISCARD_NONE    =-16, ///< discard nothing
    //  丟掉無用的數據,比如 size 為 0 這種
    AVDISCARD_DEFAULT =  0, ///< discard useless packets like 0 size packets in avi
    // 丟掉所有的非參考幀
    AVDISCARD_NONREF  =  8, ///< discard all non reference
    // 丟掉所有的雙向幀
    AVDISCARD_BIDIR   = 16, ///< discard all bidirectional frames
    // 丟掉所有的非內幀
    AVDISCARD_NONINTRA= 24, ///< discard all non intra frames
    // 丟掉所有的非關鍵幀
    AVDISCARD_NONKEY  = 32, ///< discard all frames except keyframes
    // 丟掉所有幀
    AVDISCARD_ALL     = 48, ///< discard all
};

所以,完全可以使用 discard 字段來標識解碼時丟棄哪些幀。

另外,在 avcodec_send_packet 方法源碼注釋中也提示了可以通過 AVCodecContext.skip_frame 字段來決定丟棄哪些幀。

/**
 * Internally, this call will copy relevant AVCodecContext fields, which can
 * influence decoding per-packet, and apply them when the packet is actually
 * decoded. (For example AVCodecContext.skip_frame, which might direct the
 * decoder to drop the frame contained by the packet sent with this function.)
 *  省略部分注釋
 */
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

摘錄了部分注釋內容,寫的就很清楚了。

所以,在解碼時也可以不用自己解析 AVPacket 的 nal_ref_idc 字段值,直接通過 AVCodecContext.skip_frame 實現同樣的目的。

親測有效,過濾非關鍵幀之后,解碼出來的全是關鍵幀了。

以上,就是關于丟幀的一些分享,技術交流探討歡迎加我微信 ezglumes 交流!!!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • ### YUV顏色空間 視頻是由一幀一幀的數據連接而成,而一幀視頻數據其實就是一張圖片。 yuv是一種圖片儲存格式...
    天使君閱讀 3,363評論 0 4
  • FFmpeg 介紹 FFmpeg是一套可以用來記錄、轉換數字音頻、視頻,并能將其轉化為流的開源計算機程序。采用LG...
    Y了個J閱讀 11,372評論 0 28
  • 本文主要介紹在用FFmpeg進行視頻相關開發時涉及到的一些視頻基本概念。 一、視頻幀 在H264協議里,圖像以組(...
    一葉知秋0830閱讀 1,337評論 0 0
  • 《音視頻文章匯總》[http://www.lxweimin.com/p/167b605add32]接觸ffmpeg...
    一畝三分甜閱讀 2,119評論 0 0
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,082評論 0 4