ffmpeg protocol concat 進行ts流合併視頻的時間戳計算及其音畫同步方式一點淺析

ffmpeg protocol concat 進行ts流合併視頻的時間戳計算及音畫同步方式一點淺析


ffmpeg 有三種常見的視頻合併方式: demuxerprotocolfilter

這裏有介紹它的使用 :

http://trac.ffmpeg.org/wiki/Concatenate#demuxer

本文主要介紹ts流合併視頻時候合併後視頻的pkt是如何計算的,音畫是怎麼同步的。


這種方式 是以複製pkt的方式進行的,不需要解碼,不像fitler方式合併沒有編碼損失。
其基本命令如下 :

ffmpeg -i input1.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate1.ts
ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4

libavformat/concat.c中會處理 -i concat:"...." 打開所有輸入文件,

輸出的 pkt 的 dts 和 pts 爲 所有輸入pkt的 dts 、 pts + 上一個 ts_offset
第一個片段的ts_offset 應該是 0 - 第一個片段的起始時間
第二個片段的ts_offset 是第一個片段中 最長流的 pts + 上一段的ts_ofsset
依此類推後面的。

main->tanscode()->transcoder_step()->process_input(): 中的這段代碼 即處理了 一個片段末尾新ts_offset的計算:

    if ((ist->dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ||
         ist->dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) &&
         pkt_dts != AV_NOPTS_VALUE && ist->next_dts != AV_NOPTS_VALUE &&
        !disable_discontinuity_correction) {
        int64_t delta   = pkt_dts - ist->next_dts;
        if (is->iformat->flags & AVFMT_TS_DISCONT) {
            if (delta < -1LL*dts_delta_threshold*AV_TIME_BASE ||
                delta >  1LL*dts_delta_threshold*AV_TIME_BASE ||
                pkt_dts + AV_TIME_BASE/10 < FFMAX(ist->pts, ist->dts)) {
                ifile->ts_offset -= delta;
                av_log(NULL, AV_LOG_DEBUG,
                       "timestamp discontinuity for stream #%d:%d "
                       "(id=%d, type=%s): %"PRId64", new offset= %"PRId64"\n",
                       ist->file_index, ist->st->index, ist->st->id,
                       av_get_media_type_string(ist->dec_ctx->codec_type),
                       delta, ifile->ts_offset);
                pkt.dts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);
                if (pkt.pts != AV_NOPTS_VALUE)
                    pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q, ist->st->time_base);
            }

因此 其合併 方式 應該如下圖所示 :

如是,生成兩個測試源,驗證一下 :

audio 10 video 5s 銜接測試

ffprobe -i c.mp4 -select_streams v:0 -show_packets -of json |grep pts

可以看到 pts 在 5s 處 會有一段 跳變 而 audio 在5-10s是連續的 ->

            "pts_time": "4.776667",
            "pts": 432900,
            "pts_time": "4.810000",
            "pts": 435900,
            "pts_time": "4.843333",
            "pts": 438900,
            "pts_time": "4.876667",
            "pts": 441900,
            "pts_time": "4.910000",
            "pts": 444900,
            "pts_time": "4.943333",
            "pts": 447900,
            "pts_time": "4.976667",
            "pts": 450900,
            "pts_time": "5.010000",
            "pts": 904341,
            "pts_time": "10.048233",
            "pts": 907341,
            "pts_time": "10.081567",
            "pts": 910341,
            "pts_time": "10.114900",
            "pts": 913341,
            "pts_time": "10.148233",
            "pts": 916341,
            "pts_time": "10.181567",

打開 deug_ts 在ffmeg日誌處 可以看到 ts 合併方式 下一段新的 偏移 取得是audio 的長度。

在這裏插入圖片描述

audio 5s video 10s 接着音頻短的片尾斜街一段

ffprobe -i c.mp4 -select_streams a:0 -show_packets -of json |grep pts

這次我們檢查音頻流,可以看到音頻在斷點處 時間戳是有跳變的。

            "pts": 657792,
            "pts_time": "14.915918",
            "pts": 658944,
            "pts_time": "14.942041",
            "pts": 660096,
            "pts_time": "14.968163",
            "pts": 661248,
            "pts_time": "14.994286",
            "pts": 882216,
            "pts_time": "20.004898",
            "pts": 883368,
            "pts_time": "20.031020",
            "pts": 884520,
            "pts_time": "20.057143"

在這裏插入圖片描述

因此 基本符合開頭猜想的邏輯:

小結

在這裏插入圖片描述

這種合併方式的優點是 能夠 不打亂 原來每段的 音視頻時間戳 進而確保音畫同步,

缺點是 在音畫 duration 差別過大的片段後面進行銜接 會留出一段 音或視頻的空隙。 這種 空隙 播放器可能會卡最後一幀處理,不過建議是 轉碼處理是自行補齊靜音 或視頻 最後一幀。

再或者 嘗試使用 ffmpeg -shortest 選項 截掉 音畫 偏長的那一段內容 ,來進行 concat。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章