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

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

昨天周六,群里面還有人在技術交流1翰洹晒杈!

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

image

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

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

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

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

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

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

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

image

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

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

如果為 0 矫夯,說明其他幀不需要參考該幀,可以直接丟棄不發(fā)送給解碼器吊洼,而不是解碼后再丟幀训貌。

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

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

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

image

HAL Header 的計算如下:

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

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

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

image

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

FFmpeg 中的丟幀

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

那必然是有的啊Wδ!G菲 !

如果仔細看 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 憾赁,僅過濾掉無效數據污朽。

繼續(xù)跟進這個 discard 字段,就會有新發(fā)現了龙考!

關于 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 交流!R遄丁柳沙!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拌倍,隨后出現的幾起案子赂鲤,更是在濱河造成了極大的恐慌,老刑警劉巖柱恤,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛤袒,死亡現場離奇詭異,居然都是意外死亡膨更,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門缴允,熙熙樓的掌柜王于貴愁眉苦臉地迎上來荚守,“玉大人,你說我怎么就攤上這事练般〈Q” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵薄料,是天一觀的道長敞贡。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任爽茴,我火速辦了婚禮咆霜,結果婚禮上,老公的妹妹穿的比我還像新娘狐树。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布鹏漆。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艺玲。 梳的紋絲不亂的頭發(fā)上括蝠,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音饭聚,去河邊找鬼忌警。 笑死,一個胖子當著我的面吹牛若治,可吹牛的內容都是我干的慨蓝。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼端幼,長吁一口氣:“原來是場噩夢啊……” “哼礼烈!你這毒婦竟也來了?” 一聲冷哼從身側響起婆跑,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤此熬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滑进,有當地人在樹林里發(fā)現了一具尸體犀忱,經...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年扶关,在試婚紗的時候發(fā)現自己被綠了阴汇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡节槐,死狀恐怖搀庶,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情铜异,我是刑警寧澤哥倔,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站揍庄,受9級特大地震影響咆蒿,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蚂子,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一沃测、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧食茎,春花似錦芽突、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽田巴。三九已至,卻和暖如春挟秤,著一層夾襖步出監(jiān)牢的瞬間壹哺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工艘刚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留管宵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓攀甚,卻偏偏與公主長得像箩朴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子秋度,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內容

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