FFmpeg による低遅延 RTSP/HTTP ストリーミング:AVIOInterruptCB、fflags、low_delay

目標は「カメラのライトが点滅したら、次のフレームで ピクセルが表示される」ことだ。FFmpeg のデフォルトはその用途に 調整されていない。バッファリングし、並べ替え、待機する。遅延を 抑える鍵は、いくつかのフラグと1つのコールバックだ。これらを 見逃すと数百 ms のコストになる。

フラグ

opts.cpp
AVDictionary* opts = nullptr;
av_dict_set(&opts, "rtsp_transport", "tcp", 0);   // または udp —
                                                  //   損失はあるが遅延は小さい
av_dict_set(&opts, "fflags",       "nobuffer", 0); // AVIO 入力バッファを
                                                   //   スキップ
av_dict_set(&opts, "flags",        "low_delay", 0);// デコーダーフラグ:
                                                   //   並べ替えなし
av_dict_set(&opts, "max_delay",    "0",         0);
av_dict_set(&opts, "stimeout",     "5000000",   0); // RTSP ソケットの
                                                  //   タイムアウト(µs)
av_dict_set(&opts, "reconnect",    "1",         0); // http キープアライブ

int r = avformat_open_input(&fmt, url, nullptr, &opts);

最重要なのは nobuffer だ。これがないと FFmpeg はフォーマットを 推測するためにプローブ用のウィンドウをバッファリングし、起動時の 遅延が増える。

実際に機能するタイムアウト — AVIOInterruptCB

interrupt.cpp
// stimeout はソケット層しかカバーしない。停滞した TLS ハンド
// シェイクやハングした TCP ストリームは依然として av_read_frame
// を永久にブロックし得る。本当のデッドラインとして機能するのは
// 割り込みコールバックだ:
static int interruptCb(void* ctx) {
    auto* d = static_cast<Deadline*>(ctx);
    return d->expired();  // 非ゼロ → FFmpeg はブロック中の処理を中断する
}
fmt->interrupt_callback = {interruptCb, &deadline};

deadline.arm(5000ms);
av_read_frame(fmt, pkt);   // コールバックが発火すると EAGAIN/EXIT を返す

RTSP と HTTP の両方で機能する。stimeout は RTSP 専用、timeout は HTTP 専用だが、割り込みコールバックは両方をカバーする。

デコーダー側の遅延

decode.cpp
avctx->flags |= AV_CODEC_FLAG_LOW_DELAY;    // フレームの並べ替えなし
avctx->thread_count = 1;                    // フレームスレッディングは
                                            //   フレームをキューイング = 遅延
avctx->flags2 |= AV_CODEC_FLAG2_FAST;       // 一部のチェックをスキップ
// H.264 の場合:IDR 位置に揃った出力を要求し、エンコーダーが許せば
// B フレームを落とす — B フレームの並べ替えはコーデックごとの遅延源

フレームスレッディング(thread_count > 1)は見落とされがちな遅延源だ。 デコーダーは最初のフレームを出力する前に内部で thread_count 個の フレームをキューイングする。

エンドツーエンドの遅延予算

budget.txt
ネットワーク読み取り  stimeout/interruptCb → 停滞を約100msで検出
demux              fflags=nobuffer       → プローブウィンドウの遅延なし
decode             low_delay, threads=1  → 並べ替えキューなし、フレーム
                                           スレッディングの滞留なし
upload/present     最新フレームのメールボックス → 古いフレームは破棄、
                                           最新を即座に表示

実践的なルール

  • 信頼性には rtsp_transport=tcp、最小遅延には udp を選ぶ。 UDP は再送せずパケットを落とす(ライブ映像のウォールでは正しい 選択だ。破れたフレームの方が 500ms の再送停滞よりましだ)。
  • stimeout は RTSP ソケットの接続+読み取りタイムアウトだ。 割り込みコールバックは HTTP パスと、タイムアウトでは拾えない デッドロックをカバーする。
  • decode_threads = 1 + nobuffer + low_delay がライブカメラ ウォールのベースラインだ。それぞれがスループットを少し犠牲にして 遅延を削る。
  • 実カメラでテストする。testsrc は決して停滞しない。割り込みパスが 実際に動くのは、不安定なネットワークか実カメラのキープアライブ パターンのときだけだ。
  • av_read_frame の戻り値を監視する。割り込み由来の AVERROR(EAGAIN) は回復可能な停滞、AVERROR_EOF は本当のストリーム終端だ。

同じカテゴリの類似記事をチェック: FFmpeg Streaming