FFmpeg はオーディオとビデオの同期の正確な結合を実現します

オーディオおよびビデオの開発プロセスでは、複数のクリップ (または「ショット」と呼ばれる) を結合するという問題によく遭遇します。それぞれを説明するために、いくつかの例を以下に挙げます。

この記事に記載されている例は、次の前提条件を満たしている必要があります。

1. 各クリップ自体はオーディオとビデオと同期していますが、結合後に同期が崩れるという問題があります。

2. 各セグメントのビデオ ストリームは同じです: ピクチャ幅、ピクチャ高さ、ピクセル アスペクト比、フレーム レート、およびタイム ベース; / 3. 各セグメントのオーディオ ストリームは同じです: サンプリング レート、タイム ベース; /4.各クリップにはビデオ ストリームとオーディオ ストリームがあります。

直接連結

FFmpeg は、ビデオだけでなくオーディオもスティッチできる concat フィルターを提供します。例えば:

ffmpeg -i demo_1.mp4 -i demo_2.mp4 -filter_complex \
"[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" mix.mp4

この種の結合後にオーディオとビデオを同期したい場合、前提条件は次のとおりです。各セグメントのオーディオ ストリームの長さはビデオ ストリームの長さと同じである必要があります

このような: 

FFmpeg に付属の ffprobe ツールを使用すると、ファイル内の各ストリームに対応する継続時間情報を表示できます。

ffprobe -v quiet -show_entries \
stream=index,codec_name,time_base,start_pts,start_time,duration_ts,duration \
-of json demo_1.mp4

出力は次のとおりです。

{
   ...
    "streams": [
        {
            "index": 0,
            "codec_name": "h264",
            "time_base": "1/12800",
            "start_pts": 0,
            "start_time": "0.000000",
            "duration_ts": 128512,
            "duration": "10.040000"
        },
        {
            "index": 1,
            "codec_name": "aac",
            "time_base": "1/44100",
            "start_pts": 0,
            "start_time": "0.000000",
            "duration_ts": 442764,
            "duration": "10.040000"
        }
    ]
}

ここでの主な焦点は期間フィールドですサンプル ファイル内のオーディオストリームとビデオ ストリームの長さは同じ 10.04 秒であることがわかります。したがって、このようなビデオ スプライシングの数によってオーディオとビデオの同期の問題は発生しません。以下の図に示すように:

 

予想に反して、実際の処理では、セグメントのオーディオ ストリームとビデオ ストリームの長さが等しくないことがよくあります。もちろん、長さの差は非常に小さい場合があり、数十ミリ秒程度であれば、クリップを再生するだけでは長さの違いをほとんど感じられません。上記の ffprobe コマンドを使用しないと、オーディオ ストリームとビデオ ストリームの長さが等しくない可能性があることがわかります。

例えば:

 

直接連結すると、出力は次のようになります。

 

さらに、複数のファイル入力を使用するため、ffmpeg は複数のオーディオとビデオを同時にデコードするため、多くの CPU とメモリを消費します。作者はかつて 16G メモリ環境で 60 以上のビデオ ストリームを連結しようとしましたが、いくつかのフレームの処理を開始しました。エラー: thread_get_buffer() が失敗し、get_buffer() が失敗し、最終的に異常終了しました。したがって、量が予測できない場合にこのフィルターを使用することは強くお勧めできません。

xfade および afade フィルターのスプライシング

xfade フィルターを使用すると、特定の特殊効果を使用して 2 つのシーン間の移行をよりソフトにできますが、このフィルターでは画面の移行にビデオ内の時間がかかります。

同様に、afade も前のオーディオ ストリームでフェードアウト、後のオーディオ ストリームでフェードインを実現しますが、フェードアウトとフェードインの交差点がマージされるため、一定のオーディオの長さも失われます。

基本的に、xfade フィルターと afade フィルターは両方とも、ビデオ ストリームとオーディオ ストリームに対して直接連結操作を実行し、オーディオとビデオの同期を必要としません。

オーディオとビデオの同期の改善

これらのクリップを分析すると、各クリップのオーディオとビデオが同期しており、問題はクリップのオーディオとビデオの長さのわずかな違いによって発生していることがわかります。

これらのクリップを結合するとき、ビデオ ストリームの継続時間を基準フレームとして使用できますか?

全体のアイデア

オーディオ ストリームはスプライシングを直接使用しなくなり、最初に、以前のすべてのサブクリップ ビデオ ストリームの継続時間の合計に等しい一定期間、サブクリップのオーディオ ストリームを遅延させます。

遅延処理の後、すべてのクリップのオーディオ ストリームが 1 つのメイン オーディオ ストリームにミックスされるため、オーディオ ストリームの再生開始のタイミングは前のオーディオの長さと無関係になります。

以下に示すように:

 

事前に、最終ミックスに十分な長さのオーディオ ストリームを準備してください。一般に、バックグラウンド ミュージック (BGM) が使用されますが、必要に応じてバックグラウンド ミュージックが必要ない場合は、代わりに長さ無制限の空のオーディオ ストリームを使用することもできます。

-f lavfi -i anullsrc=r=44100:cl=stereo

導入事​​例

現在、次の 3 つのフラグメントがあります。

上記のビデオ クリップ ファイルのオーディオ ストリーム チャネル レイアウトはステレオで、サンプリング レートは 44100 Hz です (ここで、上記のオーディオ タイム ベース 1/44100 はサンプリング レートの逆数にすぎませんが、これは単なる偶然であることに注意してください)。 。

BGM があります:

上記の BGM チャンネル レイアウトはステレオ、サンプリング レートも 44100Hz です。

FFmpeg ステッチング コマンド:

ffmpeg -i v_1.mp4 -i v_2.mp4 -i v_3.mp4 -stream_loop -1 -i bgm.mp3 \
-filter_complex "[0:v][1:v][2:v]concat=n=3:v=1:a=0;\
[0:a]anull[a_delay_0]; \
[1:a]adelay=delays=10000:all=1[a_delay_1]; \
[2:a]adelay=delays=16000:all=1[a_delay_2]; \
[3:a]volume=volume=0.2,atrim=end_sample=943740[bgm]; \
[bgm][a_delay_0][a_delay_1][a_delay_2]amix=inputs=4:duration=first" \
-b:v 2M \
-b:a 128k \
-movflags faststart \
concat.mp4

この例の BGM の長さは十分ですが、実際のアプリケーション シナリオでは、BGM はユーザー定義である可能性があるため、毎回十分な長さになるとは限りません。問題を回避するために、BGM ストリームは無限ループで再生されます。

最初の入力オーディオには遅延がないため、null フィルターを使用して統合ストリーム ラベルを通過させます。

v_2.mp4 からのオーディオ ストリーム [1:a]。その遅延時間は v_1.mp4 のビデオ ストリームの長さ (秒をミリ秒に変換) から取得します。

v_3.mp4 のオーディオ ストリーム [2:a]、その遅延の長さは、v_1.mp4 のビデオ ストリーム + v_2.mp4 のビデオ ストリームの長さによって決まります。

bgm.mp3 のオーディオ ストリーム [3:a] は、音量を下げた後、ビデオの全長に合わせてトリミングされます。ここでは、サンプリング レートに基づく計算が使用されます。

なぜサンプルレートに基づいてクリップするのでしょうか? 上でわかるように、すべてのクリップのオーディオ サンプリング レートは同じである必要があります。したがって、定数は 44100 です。これは、1 秒間に 44100 個のサンプルがあることを意味します。ビデオの合計時間がわかったら、それをオーディオに含めるサンプル数に変換できます。タイムベースは、ソース BGM のタイムベースがクリップ内のオーディオ ストリームのタイムベースと一致しない可能性があるため、トラブルを避けるために使用されません。

ここまでで基本的な準備はすべて完了しました。残りはミキシングです。すべての遅延オーディオ ストリームを bgm オーディオ ストリームにミックスします。amix フィルターの継続時間アルゴリズムが最初に使用するため、つまり、最初のストリームの継続時間が保持されるため、bgm ストリーム タグを最初に配置する必要があることに注意してください。

これまでのところ、出力はオーディオとビデオが厳密に同期されたビデオです。

レイテンシをより正確に計算する方法

ロジックを単純化するために、上記のコマンド例では遅延のミリ秒数を単純に書き込みます。しかし、このミリ秒数はどのように計算すればよいのでしょうか?

まず、ビデオの長さがどのように計算されるかを見てください。

duration = duration_ts * time_base

たとえば、ビデオ ストリーム情報は次のとおりです。

"time_base": "1/12800"
"start_pts": 0
"start_time": "0.000000"
"duration_ts": 128000
"duration": "10.000000"

たった今:

duration = 128000 * 1 / 12800 = 10(秒)

duration_ts は整数ですが、除算が導入されているため、期間は割り切れない可能性があり、期間フィールドの精度は失われます。

このような不正確な値が累積され続けると、新たなエラーが発生するのと同じことになります。

正しい方法は、int64 型の各フラグメントの duration_ts を取得することです。concat では、各サブクリップのビデオ ストリームが同じタイム ベースを持つ必要があるため、duration_ts を累積できます。

実際に遅延を加える際には、蓄積されたduration_tsが対応するdurationに変換されるため、誤差は常に妥当な範囲内に制御されます。

なぜ遅延を追加するのか

この解決策について同僚と話し合っていたとき、ある人が「接続時に音声とビデオが同期していません。遅延が発生するとさらに同期がずれるのではないでしょうか?」と疑問を投げかけました。その後、彼の考えを理解した後、それが非常に基本的な質問であることに気づきました。この写真を見てください。

 

FFmpeg に入力されるソースの数やストリームの数に関係なく、demux 後、すべてのストリームは時間 0 から開始され、入力ソースの順序によって入力ストリームに多少の遅れが生じるとは限りません。

別の concat 実装

ffmpegには一括結合機能が備わっていますので、まず結合するファイルを次の形式でリストします。

file 'media_1.mp4'
file 'media_2.mp4'
...

ファイルで開始し、ファイル名を一重引用符で囲み、完全なファイル名と相対ファイル名をサポートし、BOM なしの UTF-8 を使用してマニフェスト ファイル (manifest.txt など) として保存します。次に、次のコマンドを使用します。

ffmpeg -f concat -i manifest.txt -c copy concat.mp4

この方法では、オーディオとビデオが自動的に調整されます。調整アルゴリズムは上記のロジックと非常によく似ており、これも位置ずれによって行われます。ただし、違いは、オーディオ部分がパケット データの再生時間 (pts) を直接変更することです (具体的なコードについては、libavformat/concatdec.c の concat_read_packet 関数の実装を参照してください)。しかし、上記の例のように、2 つのビデオ ファイルのオーディオ ストリームがそれぞれのビデオ ストリームより短いと仮定すると、再生中に可能性があるという問題も明らかになりました (ここでは、プレーヤーのサポートに応じて可能です) ) 問題は表示されず、正常に表示されます。しかし、このようなファイルを Premiere Pro などの編集ソフトウェアに直接インポートすると、直接連結した場合と同様に、2 つのオーディオが隣り合って再生されることがわかります。動画編集ソフトでなくても、このような繋ぎ合わせた動画からFFmpegコマンドを使って音声ストリームを抽出すると同様の結果が得られます。間に無音の音声はありません。

解決策: オーディオ ストリームが最初から最後まで自然に再生されるように、2 つのオーディオ セグメントの間に無音オーディオを追加する必要があります。具体的な方法は 2 つあります。

ffmpeg -f concat -i manifest.txt -filter_complex "[0:a]aresample=async=1000" concat.mp4

また

ffmpeg -f concat -i manifest.txt -async 1000 concat.mp4

どちらも同じで、中央の空きスペースが自動的に無音のオーディオで埋められます。このうち、async パラメータは調整可能で、一般的には 1000 でおおよその要件を満たすことができます。

ただし、埋められるのは中央の空白のみであり、最後のセグメントの音声ストリームもビデオストリームより短い場合は、ビデオの最後まで埋められません。

この例には 2 つのビデオ ファイルがあり、オーディオ ストリームがビデオ ストリームより短いと考える生徒もいるかもしれません。ビデオストリームよりも長い場合はどうなりますか? この方法を例にとると、スプライシング結果は次のようになります。

前のビデオ ストリームの最後のフレームは、オーディオ ストリームの再生が終了するまでコピーされ続けます (ビデオ ストリームは終了しません)。

後者のビデオ ストリームは再生後に停止し、従来のプレーヤー処理では通常静止表示が行われ (ただし、この時点ではビデオ ストリームは終了しています)、オーディオ ストリームは終了するまで再生され続けます。

原文 FFmpeg はオーディオとビデオ同期の正確なフラグメント スプライシングを実現_ffmpeg は音楽のリズム ポイントに応じてショットをスプライス_Jack_Chai のブログ - CSDN ブログ 

★記事末尾の名刺では、オーディオ・ビデオ開発学習教材(FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、srs)やオーディオ・ビデオ学習ロードマップ等を無料で受け取ることができます。

下記参照!

 

おすすめ

転載: blog.csdn.net/yinshipin007/article/details/130645720