DEV Community

vast cow
vast cow

Posted on

Streaming Video Over a Network with the Lowest Possible Latency Using FFmpeg + MediaMTX

When you want to send video captured from an HDMI capture device or similar source to another PC on the same LAN, one issue that can be surprisingly significant is video latency.

If you simply encode the video as H.264 and stream it over the network, small amounts of buffering can occur at multiple stages, such as:

  • Capture
  • FFmpeg’s internal queues
  • Encoding
  • Multiplexing
  • Network transport
  • Player-side buffering
  • Decoding and display

As a result, the final latency can range from several hundred milliseconds to several seconds.

This time, we will build a low-latency streaming setup that minimizes buffering as much as possible using the following configuration:

V4L2 + ALSA → FFmpeg → MediaMTX → RTSP/UDP → ffplay

In the current FFmpeg documentation, nobuffer is defined as an option for reducing latency caused by buffering during input analysis, while low_delay is defined as a flag that forces low-delay operation.


Configuration

The setup used here looks like this:

HDMI input
   ↓
Capture device
 /dev/video0
   ↓
FFmpeg
  ├─ Video input via V4L2
  ├─ Audio input via ALSA
  ├─ H.264 encoding with Intel QSV
  ↓
RTSP / UDP
   ↓
MediaMTX
   ↓
LAN
   ↓
ffplay
   ↓
Display
Enter fullscreen mode Exit fullscreen mode

Rather than having MediaMTX encode the video itself, we use it as an RTSP server that relays the stream.

MediaMTX is a media server that supports publishing and reading real-time video and audio streams, including RTSP, and it can also launch external commands as hooks.

This time, we will use its runOnDemand feature.


MediaMTX Configuration

The mediamtx.yml file is configured as follows:

paths:
  hdmi:
    runOnDemand: >-
      ffmpeg -hide_banner -y
      -loglevel warning
      -fflags nobuffer
      -flags low_delay
      -init_hw_device qsv=qsv
      -filter_hw_device qsv
      -thread_queue_size 4
      -f alsa
      -ac 2
      -ar 48000
      -i hw:1,0
      -thread_queue_size 1
      -f v4l2
      -input_format yuyv422
      -video_size 1920x1080
      -framerate 30
      -i /dev/video0
      -map 1:v:0
      -map 0:a:0
      -vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
      -c:v h264_qsv
      -preset veryfast
      -global_quality:v 35
      -async_depth 1
      -bf 0
      -g 30
      -c:a libopus -b:a 192k
      -f rtsp
      -rtsp_transport udp
      -muxdelay 0
      rtsp://127.0.0.1:8554/hdmi
    runOnDemandRestart: yes
    runOnDemandStartTimeout: 10s
    runOnDemandCloseAfter: 5s
Enter fullscreen mode Exit fullscreen mode

At first glance, this may seem like a lot of options, but from a low-latency perspective, it becomes easier to understand if you break them down into a few key areas.


Start FFmpeg Only When Needed with runOnDemand

First, let’s look at the MediaMTX side.

runOnDemand: >-
  ffmpeg ...
Enter fullscreen mode Exit fullscreen mode

runOnDemand is a feature that starts an external command when a client accesses the corresponding path.

In other words, it works like this:

Nobody is watching
↓
FFmpeg stopped

ffplay connects to /hdmi
↓
MediaMTX starts FFmpeg
↓
FFmpeg publishes to /hdmi
↓
Playback starts
Enter fullscreen mode Exit fullscreen mode

The official MediaMTX documentation also describes runOnDemand as a mechanism that starts the specified command when a reader requests the path.

Because FFmpeg does not need to run continuously, this is convenient when you only want to use HDMI streaming when needed.

In addition, we use:

runOnDemandRestart: yes
Enter fullscreen mode Exit fullscreen mode

so that if FFmpeg exits for some reason, it will be restarted. The current MediaMTX configuration reference also defines this option as a setting that restarts the command after it exits.


Minimize Input-Side Buffers

An important principle in low-latency streaming is:

Avoid “buffering first, processing later.”

For that reason, the FFmpeg command begins with:

-fflags nobuffer
-flags low_delay
Enter fullscreen mode Exit fullscreen mode

-fflags nobuffer

-fflags nobuffer
Enter fullscreen mode Exit fullscreen mode

This setting reduces latency caused by buffering during input stream analysis. The official FFmpeg documentation also describes it as an option for reducing latency caused by buffering during initial input analysis.

For real-time input, it is important to configure the pipeline in the following direction:

Avoid accumulating packets as much as possible
↓
Pass incoming data to the next processing stage immediately
Enter fullscreen mode Exit fullscreen mode

-flags low_delay

-flags low_delay
Enter fullscreen mode Exit fullscreen mode

As the name suggests, this flag configures codec processing for low latency. In FFmpeg, low_delay is defined as “Force low delay.”


Capture Video and Audio from Separate Devices

In this setup, audio is captured through ALSA:

-f alsa
-ac 2
-ar 48000
-i hw:1,0
Enter fullscreen mode Exit fullscreen mode

while video is captured through V4L2:

-f v4l2
-input_format yuyv422
-video_size 1920x1080
-framerate 30
-i /dev/video0
Enter fullscreen mode Exit fullscreen mode

From FFmpeg’s perspective, the inputs are therefore:

input 0 = ALSA
input 1 = V4L2
Enter fullscreen mode Exit fullscreen mode

The output streams are explicitly selected with:

-map 1:v:0
-map 0:a:0
Enter fullscreen mode Exit fullscreen mode

In other words:

Video → video 0 from input 1
Audio → audio 0 from input 0
Enter fullscreen mode Exit fullscreen mode

Keep thread_queue_size Small

For the inputs, we specify:

-thread_queue_size 4
Enter fullscreen mode Exit fullscreen mode

and:

-thread_queue_size 1
Enter fullscreen mode Exit fullscreen mode

thread_queue_size determines how many packets read from an input device or other source FFmpeg may retain in its internal queue. The official FFmpeg documentation describes it, for inputs, as the maximum number of queued packets when reading from a device or file.

If you increase the queue size, behavior tends to become:

Processing stalls slightly
↓
Packets accumulate in the queue
↓
Frames are processed without being dropped
Enter fullscreen mode Exit fullscreen mode

However, in low-latency applications, this can become a problem:

Processing cannot keep up
↓
Old video accumulates in the queue
↓
Displayed video falls further and further behind real time
Enter fullscreen mode Exit fullscreen mode

For that reason, we use very small values here.

The design philosophy is:

Prioritize displaying video that is close to the current time over guaranteeing that no frame is ever dropped.

This is a very important concept in low-latency streaming.


Encode with Intel Quick Sync Video

If 1920×1080 30 fps video is encoded to H.264 in software, the CPU load itself can become a source of latency.

For that reason, we use Intel Quick Sync Video, commonly known as QSV.

-init_hw_device qsv=qsv
-filter_hw_device qsv
Enter fullscreen mode Exit fullscreen mode

Then the video is uploaded to the QSV device and converted to NV12 with:

-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
Enter fullscreen mode Exit fullscreen mode

The encoder is:

-c:v h264_qsv
Enter fullscreen mode Exit fullscreen mode

This allows the Intel GPU’s hardware encoder to handle H.264 encoding.


Reduce Encoder “Lookahead” as Well

This is where some of the most important low-latency settings appear.

-preset veryfast
-global_quality:v 35
-async_depth 1
-bf 0
-g 30
Enter fullscreen mode Exit fullscreen mode

-preset veryfast

-preset veryfast
Enter fullscreen mode Exit fullscreen mode

QSV presets include:

veryfast
faster
fast
medium
slow
slower
veryslow
Enter fullscreen mode Exit fullscreen mode

In FFmpeg, the veryfast side prioritizes speed, while the veryslow side prioritizes quality.

For live streaming, rather than pushing encoding quality to the limit, we prioritize:

Encoding each frame quickly and sending it to the network.

That is why we choose veryfast.


async_depth 1 Is Important

-async_depth 1
Enter fullscreen mode Exit fullscreen mode

This setting also helps reduce latency.

QSV can improve throughput by processing multiple frames asynchronously.

However:

Process multiple frames in parallel
Enter fullscreen mode Exit fullscreen mode

also means that multiple frames may exist inside the encoder at the same time.

The FFmpeg QSV documentation defines async_depth as a parameter related to the number of asynchronous operations, and for the QSV decoder it explicitly notes that increasing the value also increases latency.

For that reason, we reduce it to:

-async_depth 1
Enter fullscreen mode Exit fullscreen mode

The goal is a simple pipeline:

Frame input
↓
Encode
↓
Output immediately
Enter fullscreen mode Exit fullscreen mode

We prioritize latency over throughput.


Do Not Use B-Frames

For low-latency H.264, this setting is especially important:

-bf 0
Enter fullscreen mode Exit fullscreen mode

When B-frames are used, encoding and decoding a frame may require referencing frames that occur later in time.

Conceptually, the structure may look like:

I P B B P
Enter fullscreen mode Exit fullscreen mode

As a result, the encoder and decoder may need to reorder frames, which is unfavorable for low-latency use cases.

Therefore, we completely disable B-frames with:

-bf 0
Enter fullscreen mode Exit fullscreen mode

Compression efficiency is sacrificed to some extent, but this is easier to handle in real-time applications.


Use a One-Second GOP

-g 30
Enter fullscreen mode Exit fullscreen mode

This is another key setting.

Here, we use:

-framerate 30
Enter fullscreen mode Exit fullscreen mode

so the frame rate is 30 fps.

Therefore:

30 frames ÷ 30 fps = 1 second
Enter fullscreen mode Exit fullscreen mode

which means the GOP is divided at roughly one-second intervals.

In FFmpeg, -g specifies the GOP size.

Shortening the GOP slightly reduces compression efficiency, but it makes the stream easier to handle in situations such as:

  • Playback startup
  • Recovery after packet loss
  • Joining the stream mid-session

For low-latency applications, compression ratio is not the only concern. It is also important to catch up to the current video quickly.


global_quality:v 35

-global_quality:v 35
Enter fullscreen mode Exit fullscreen mode

This sets the QSV quality level.

When global_quality:v is specified with h264_qsv, quality-based rate control such as ICQ may be used depending on the conditions. The FFmpeg QSV documentation states that the ICQ range is 1–51, with 1 being the highest quality.

In other words:

Smaller value
↓
Higher quality, larger data volume

Larger value
↓
Lower quality, smaller data volume
Enter fullscreen mode Exit fullscreen mode

35 is fairly compression-oriented, so you can adjust it while monitoring network bandwidth and image quality.

For example, if you want better quality, you could lower it to:

-global_quality:v 28
Enter fullscreen mode Exit fullscreen mode

and compare the result.


Audio Is Opus at 192 kbps

Audio is configured as:

-c:a libopus -b:a 192k
Enter fullscreen mode Exit fullscreen mode

Compared with video, audio encoding has relatively low computational and bandwidth requirements, so we use Opus at 192 kbps.

However, for monitoring applications where you only need to see the video, you can remove audio with:

-an
Enter fullscreen mode Exit fullscreen mode

to simplify the setup further.


Use UDP for RTSP

FFmpeg outputs to MediaMTX with:

-f rtsp
-rtsp_transport udp
-muxdelay 0
rtsp://127.0.0.1:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

For reducing latency, the key setting is:

-rtsp_transport udp
Enter fullscreen mode Exit fullscreen mode

FFmpeg allows UDP, TCP, and other methods to be selected as the RTSP lower transport. With UDP, media is sent over UDP; with TCP, the data is interleaved inside the RTSP control channel.

Unlike TCP, UDP does not have a mechanism like:

Packet loss
↓
Wait for retransmission
↓
Subsequent processing also waits until the packet arrives
Enter fullscreen mode Exit fullscreen mode

Therefore, it is well suited to real-time applications where the priority is:

Even if the image becomes slightly corrupted, do not wait for old video—show the current video instead.

Naturally, however, video may become corrupted in environments where packet loss is more likely, such as:

  • Wi-Fi
  • Congested LANs
  • Streaming over the internet

If reliability is more important, another option is:

-rtsp_transport tcp
Enter fullscreen mode Exit fullscreen mode

Low latency and transmission reliability are a tradeoff.


muxdelay 0

We also specify:

-muxdelay 0
Enter fullscreen mode Exit fullscreen mode

muxdelay is a delay-related parameter used on FFmpeg’s output side. The official FFmpeg RTSP sending example also includes a case using -muxdelay 0.1.

Here, we push it even further with:

-muxdelay 0
Enter fullscreen mode Exit fullscreen mode

to configure the system so that:

As little time as possible is spent waiting to batch packets together.


Do Not Make MediaMTX Encode the Video

An important part of this setup is that FFmpeg publishes once to the local MediaMTX instance:

FFmpeg
↓
rtsp://127.0.0.1:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

Clients then access:

rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

In other words, MediaMTX acts as the relay point:

FFmpeg
   ↓
MediaMTX
   ↓
Multiple clients
Enter fullscreen mode Exit fullscreen mode

With this configuration, individual clients do not need direct access to the capture device or encoder.

It is also easier to manage when distributing the stream to multiple devices.


Reduce Buffering on the ffplay Side as Well

Even if the sender is optimized for low latency, there is little benefit if the receiving player buffers one second of video before starting playback.

For that reason, ffplay is also configured as follows:

ffplay \
  -fflags nobuffer \
  -flags low_delay \
  -framedrop \
  -rtsp_transport udp \
  rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

-fflags nobuffer

As on the sending side, we specify:

-fflags nobuffer
Enter fullscreen mode Exit fullscreen mode

to reduce latency caused by buffering during input analysis.


-flags low_delay

-flags low_delay
Enter fullscreen mode Exit fullscreen mode

This also configures decoding for low-latency operation.


-framedrop

-framedrop
Enter fullscreen mode Exit fullscreen mode

This is another important option for real-time applications.

ffplay provides the framedrop option, which allows video frames to be dropped when playback falls behind synchronization.

For low-latency use, instead of:

Faithfully displaying every single frame
Enter fullscreen mode Exit fullscreen mode

it is more important to:

If processing falls behind, discard old frames
↓
Catch up to the current time
Enter fullscreen mode Exit fullscreen mode

This setting sacrifices some visual continuity in order to prevent latency from increasing.


In Low-Latency Streaming, “Dropping” Is Important

Looking at all of these settings, there is a common philosophy:

Do not retain old data any longer than necessary.

In normal video playback, it is important to:

Avoid packet loss
Avoid dropping frames
Keep playback smooth
Enter fullscreen mode Exit fullscreen mode

However, real-time video has different priorities.

For example, when displaying a game screen or camera feed, it may be more useful to show:

Video from around 100 ms ago, even if some frames are skipped
Enter fullscreen mode Exit fullscreen mode

than to perfectly display:

Video from 3 seconds ago
Enter fullscreen mode Exit fullscreen mode

For that reason, this setup reduces waiting time at every stage of the pipeline:

Small input queues
↓
No B-frames
↓
Reduced QSV asynchronous depth
↓
Reduced muxer waiting time
↓
UDP
↓
Reduced buffering in ffplay
↓
Drop frames if playback falls behind
Enter fullscreen mode Exit fullscreen mode

Where Does Latency Occur?

When optimizing for low latency, you need to look at the entire pipeline, not just the network:

Capture
↓
Queue
↓
Filter
↓
Encode
↓
Mux
↓
Network
↓
Demux
↓
Decode
↓
Render
Enter fullscreen mode Exit fullscreen mode

For example, even if LAN ping is:

1 ms
Enter fullscreen mode Exit fullscreen mode

if you have:

Encoder: 100 ms
Player buffer: 500 ms
Enter fullscreen mode Exit fullscreen mode

then improving the network further will not have much impact.

In fact, with real-time video:

Buffers inside the encoder, decoder, and player can sometimes contribute more latency than the network itself.

That is why this configuration uses so many low-latency options.


If You Want to Reduce Latency Even Further

If latency is still noticeable with this configuration, there are several additional things you can try.

First, ffplay’s RTSP reception uses a buffer for reordering UDP packets. The FFmpeg documentation states that packet reordering during UDP reception can be disabled by setting max_delay to 0.

For example, you can try:

ffplay \
  -fflags nobuffer \
  -flags low_delay \
  -framedrop \
  -rtsp_transport udp \
  -max_delay 0 \
  rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

However, this further reduces tolerance for out-of-order packets, so video may become unstable depending on network quality.

You can also reduce:

-g 30
Enter fullscreen mode Exit fullscreen mode

to something like:

-g 15
Enter fullscreen mode Exit fullscreen mode

However, shorter GOPs generally reduce compression efficiency.

Low-latency optimization always involves tradeoffs among:

Latency
Image quality
Bandwidth
Stability
CPU/GPU load
Enter fullscreen mode Exit fullscreen mode

UDP Does Not Automatically Mean Low Latency

One important point to keep in mind is that:

UDP = always low latency
Enter fullscreen mode Exit fullscreen mode

is not necessarily true.

UDP can help avoid retransmission delays, but if network quality is poor, you may instead see:

Packet loss
↓
Video corruption
↓
Wait until the next recoverable frame
Enter fullscreen mode Exit fullscreen mode

Therefore, the ideal environment is something like:

Wired LAN + UDP

where packet loss is low and the network is stable.

In a Wi-Fi environment, it is better to test both UDP and TCP and compare:

Actual latency
Video corruption
Stability
Enter fullscreen mode Exit fullscreen mode

Summary

In this setup, we built the following pipeline:

HDMI
↓
V4L2 / ALSA
↓
FFmpeg
↓
Intel QSV H.264
↓
RTSP / UDP
↓
MediaMTX
↓
LAN
↓
ffplay
Enter fullscreen mode Exit fullscreen mode

The key to reducing latency is not simply using a fast encoder.

What matters is removing, as much as possible, the buffers at each stage that exist to:

“Hold a little data just in case.”

The main settings used here can be summarized as follows:

-fflags nobuffer
    Reduce input-side buffering

-flags low_delay
    Configure codecs for low latency

-thread_queue_size
    Keep capture input queues small

-c:v h264_qsv
    Encode H.264 quickly with Intel QSV

-preset veryfast
    Prioritize encoding speed

-async_depth 1
    Reduce the asynchronous processing depth inside QSV

-bf 0
    Disable B-frames

-g 30
    About a one-second GOP at 30 fps

-rtsp_transport udp
    Favor avoiding retransmission delays

-muxdelay 0
    Reduce waiting time in the muxer

-framedrop
    Drop old frames if playback falls behind
Enter fullscreen mode Exit fullscreen mode

The important thing to understand about low-latency video streaming is that:

“Deliver every frame reliably” and “deliver the current video” are different goals.

For recording, not losing frames is important.

On the other hand, for real-time monitoring, remote control, game screens, camera surveillance, and similar applications, it is often more important to display the latest video even if a few frames are missing than to receive old video perfectly.

By tuning the capture, encoding, network, and player stages around that principle, you can achieve very low-latency video transmission even with RTSP.

FFmpeg + MediaMTXで映像をできるだけ低遅延にネットワーク配信する

HDMIキャプチャなどから取り込んだ映像をLAN内の別PCへ送りたいとき、意外と問題になるのが映像の遅延です。

普通にH.264へエンコードしてネットワーク配信すると、

  • キャプチャ
  • FFmpeg内部のキュー
  • エンコード
  • マルチプレクサ
  • ネットワーク
  • プレイヤー側のバッファ
  • デコード・表示

といった複数の段階で少しずつバッファリングされ、最終的には数百ms〜数秒の遅延になることがあります。

そこで今回は、

V4L2 + ALSA → FFmpeg → MediaMTX → RTSP/UDP → ffplay

という構成で、可能な限りバッファを減らした低遅延ストリーミングを構築します。

FFmpegの現行ドキュメントでも、nobuffer は入力解析時のバッファリングによる遅延を減らすためのオプション、low_delay は低遅延動作を強制するフラグとして定義されています。


構成

今回の構成は次のようになります。

HDMI入力
   ↓
キャプチャデバイス
 /dev/video0
   ↓
FFmpeg
  ├─ V4L2で映像入力
  ├─ ALSAで音声入力
  ├─ Intel QSVでH.264エンコード
  ↓
RTSP / UDP
   ↓
MediaMTX
   ↓
LAN
   ↓
ffplay
   ↓
画面表示
Enter fullscreen mode Exit fullscreen mode

MediaMTXには映像そのものをエンコードさせるのではなく、RTSPサーバーとして中継を担当させます

MediaMTXはRTSPを含むリアルタイム映像・音声ストリームのpublish/readに対応するメディアサーバーで、外部コマンドをhookとして起動することもできます。

今回はそのrunOnDemand機能を使います。


MediaMTXの設定

mediamtx.ymlを次のようにしています。

paths:
  hdmi:
    runOnDemand: >-
      ffmpeg -hide_banner -y
      -loglevel warning
      -fflags nobuffer
      -flags low_delay
      -init_hw_device qsv=qsv
      -filter_hw_device qsv
      -thread_queue_size 4
      -f alsa
      -ac 2
      -ar 48000
      -i hw:1,0
      -thread_queue_size 1
      -f v4l2
      -input_format yuyv422
      -video_size 1920x1080
      -framerate 30
      -i /dev/video0
      -map 1:v:0
      -map 0:a:0
      -vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
      -c:v h264_qsv
      -preset veryfast
      -global_quality:v 35
      -async_depth 1
      -bf 0
      -g 30
      -c:a libopus -b:a 192k
      -f rtsp
      -rtsp_transport udp
      -muxdelay 0
      rtsp://127.0.0.1:8554/hdmi
    runOnDemandRestart: yes
    runOnDemandStartTimeout: 10s
    runOnDemandCloseAfter: 5s
Enter fullscreen mode Exit fullscreen mode

一見するとオプションがかなり多いですが、低遅延化という観点ではいくつかのポイントに分けて考えると分かりやすくなります。


runOnDemandで必要なときだけFFmpegを起動する

まずMediaMTX側です。

runOnDemand: >-
  ffmpeg ...
Enter fullscreen mode Exit fullscreen mode

runOnDemandは、クライアントからそのpathへのアクセスが発生したときに外部コマンドを起動する機能です。

つまり、

誰も見ていない
↓
FFmpeg停止

ffplayから /hdmi に接続
↓
MediaMTXがFFmpegを起動
↓
FFmpegが /hdmi にpublish
↓
再生開始
Enter fullscreen mode Exit fullscreen mode

という動作になります。

MediaMTX公式ドキュメントでも、runOnDemandに指定したコマンドはreaderからpathが要求されたタイミングで開始される仕組みになっています。

常時FFmpegを動かしておく必要がないため、HDMI配信を必要なときだけ使いたい場合に便利です。

さらに、

runOnDemandRestart: yes
Enter fullscreen mode Exit fullscreen mode

としているので、FFmpegが何らかの理由で終了した場合にも再起動されます。現行のMediaMTX設定リファレンスにも、このオプションはコマンド終了時に再起動する設定として定義されています。


入力側のバッファを極力小さくする

低遅延ストリーミングで重要なのが、

「溜めてから処理する」のを避けること

です。

そのためFFmpegの冒頭で、

-fflags nobuffer
-flags low_delay
Enter fullscreen mode Exit fullscreen mode

を指定しています。

-fflags nobuffer

-fflags nobuffer
Enter fullscreen mode Exit fullscreen mode

は入力ストリーム解析時に発生するバッファリングによる遅延を減らすための設定です。FFmpeg公式ドキュメントでも、初期入力解析時のバッファリングによるレイテンシーを削減するオプションとされています。

リアルタイム入力では、

できるだけパケットを貯めない
↓
届いたデータをすぐ次の処理へ渡す
Enter fullscreen mode Exit fullscreen mode

という方向に設定しておくことが重要です。

-flags low_delay

-flags low_delay
Enter fullscreen mode Exit fullscreen mode

も名前の通り、コーデック処理を低遅延方向へ設定するためのフラグです。FFmpegではlow_delayが「Force low delay」と定義されています。


映像と音声を別デバイスから入力する

今回の構成では音声をALSA、

-f alsa
-ac 2
-ar 48000
-i hw:1,0
Enter fullscreen mode Exit fullscreen mode

映像をV4L2、

-f v4l2
-input_format yuyv422
-video_size 1920x1080
-framerate 30
-i /dev/video0
Enter fullscreen mode Exit fullscreen mode

から取り込んでいます。

したがってFFmpegから見ると入力は、

input 0 = ALSA
input 1 = V4L2
Enter fullscreen mode Exit fullscreen mode

になります。

出力するストリームを、

-map 1:v:0
-map 0:a:0
Enter fullscreen mode Exit fullscreen mode

と明示しています。

つまり、

映像 → input 1のvideo 0
音声 → input 0のaudio 0
Enter fullscreen mode Exit fullscreen mode

です。


thread_queue_sizeを小さくする

入力にはそれぞれ、

-thread_queue_size 4
Enter fullscreen mode Exit fullscreen mode
-thread_queue_size 1
Enter fullscreen mode Exit fullscreen mode

を指定しています。

thread_queue_sizeは、入力デバイスなどから読み込んだパケットをFFmpeg内部で何個までキューに保持するかを決める設定です。FFmpeg公式ドキュメントでも、入力の場合はデバイスやファイルから読み込む際のqueued packetsの最大数と説明されています。

キューを大きくすると、

多少処理が詰まる
↓
キューへ蓄積
↓
フレームを捨てず処理
Enter fullscreen mode Exit fullscreen mode

しやすくなります。

一方で低遅延用途では、

処理が追いつかない
↓
過去の映像がキューへ溜まる
↓
表示がどんどん現実時間から遅れる
Enter fullscreen mode Exit fullscreen mode

という問題になります。

そのため今回はかなり小さい値にしています。

これは、

フレームを絶対に落とさないことより、現在時刻に近い映像を表示することを優先する

という設計です。

低遅延ストリーミングでは非常に重要な考え方です。


Intel Quick Sync Videoでエンコードする

1920×1080 30fpsの映像をH.264へソフトウェアエンコードすると、CPU負荷によっては処理そのものが遅延原因になります。

そこでIntel Quick Sync Video、いわゆるQSVを利用しています。

-init_hw_device qsv=qsv
-filter_hw_device qsv
Enter fullscreen mode Exit fullscreen mode

そして映像を、

-vf 'hwupload=extra_hw_frames=0,vpp_qsv=format=nv12'
Enter fullscreen mode Exit fullscreen mode

でQSV側へアップロードし、NV12へ変換します。

エンコーダーは、

-c:v h264_qsv
Enter fullscreen mode Exit fullscreen mode

です。

これによってH.264エンコードをIntel GPUのハードウェアエンコーダーへ担当させます。


エンコーダーでも「先読み」を減らす

ここからが低遅延化の重要部分です。

-preset veryfast
-global_quality:v 35
-async_depth 1
-bf 0
-g 30
Enter fullscreen mode Exit fullscreen mode

としています。

-preset veryfast

-preset veryfast
Enter fullscreen mode Exit fullscreen mode

QSVのpresetは、

veryfast
faster
fast
medium
slow
slower
veryslow
Enter fullscreen mode Exit fullscreen mode

という選択肢があり、FFmpegではveryfast側が速度優先、veryslow側が画質優先として定義されています。

ライブ配信ではエンコード品質を限界まで高めるより、

1フレームを素早くエンコードしてネットワークへ送る

ことを優先します。

そのためveryfastを選択しています。


async_depth 1が重要

-async_depth 1
Enter fullscreen mode Exit fullscreen mode

も低遅延化に効く設定です。

QSVは複数フレームを非同期に処理することでスループットを高められます。

ただし、

複数フレームを並列処理
Enter fullscreen mode Exit fullscreen mode

するということは、逆に言えばエンコーダー内部に複数フレームが存在することにもなります。

FFmpegのQSVドキュメントでもasync_depthは非同期処理数に関係するパラメータとして定義されており、QSV decoderについては値を増やすほどレイテンシーも増えると明記されています。

そこで、

-async_depth 1
Enter fullscreen mode Exit fullscreen mode

まで下げています。

狙っているのは、

フレーム入力
↓
エンコード
↓
すぐ出力
Enter fullscreen mode Exit fullscreen mode

という単純なパイプラインです。

スループットよりlatencyを優先しています。


Bフレームを使わない

低遅延H.264では、

-bf 0
Enter fullscreen mode Exit fullscreen mode

が特に重要です。

Bフレームを使用すると、あるフレームをエンコード・デコードする際に未来側のフレームを参照することがあります。

概念的には、

I P B B P
Enter fullscreen mode Exit fullscreen mode

のような構造です。

そのためエンコーダーやデコーダーでフレーム並び替えが必要になり、低遅延用途では不利になります。

そこで、

-bf 0
Enter fullscreen mode Exit fullscreen mode

としてBフレームを完全に無効化しています。

圧縮効率は多少犠牲になりますが、リアルタイム用途ではこちらの方が扱いやすくなります。


GOPを1秒にする

-g 30
Enter fullscreen mode Exit fullscreen mode

もポイントです。

今回は、

-framerate 30
Enter fullscreen mode Exit fullscreen mode

なので30fpsです。

したがって、

30フレーム ÷ 30fps = 1秒
Enter fullscreen mode Exit fullscreen mode

となり、おおよそ1秒ごとにGOPが区切られます。

FFmpegでは-gはGOP sizeを指定するパラメータです。

GOPを短くすると圧縮効率は多少悪くなりますが、

  • 再生開始
  • パケットロス後の復帰
  • ストリームへの途中参加

といった場面で扱いやすくなります。

低遅延用途では圧縮率だけでなく、素早く現在の映像へ追いつけることも重要です。


global_quality:v 35

-global_quality:v 35
Enter fullscreen mode Exit fullscreen mode

ではQSVの品質を指定しています。

h264_qsvglobal_quality:vを指定すると、条件に応じてICQなどの品質ベースのレート制御が利用されます。FFmpegのQSVドキュメントではICQ時の範囲は1〜51で、1が最高品質とされています。

つまり、

小さい値
↓
高画質・大きなデータ量

大きい値
↓
低画質・小さなデータ量
Enter fullscreen mode Exit fullscreen mode

という方向です。

35はかなり圧縮寄りの設定なので、ネットワーク帯域や画質を見ながら調整できます。

たとえば画質を上げるなら、

-global_quality:v 28
Enter fullscreen mode Exit fullscreen mode

などへ下げて比較すると分かりやすいでしょう。


音声はOpus 192kbps

音声は、

-c:a libopus -b:a 192k
Enter fullscreen mode Exit fullscreen mode

としています。

映像に比べれば音声エンコードの負荷や帯域は小さいため、Opus 192kbpsとしています。

ただし「映像だけ確認できればよい」という監視用途などでは音声を削除して、

-an
Enter fullscreen mode Exit fullscreen mode

とすることで構成をさらに単純化できます。


RTSPはUDPを使う

FFmpegからMediaMTXへの出力は、

-f rtsp
-rtsp_transport udp
-muxdelay 0
rtsp://127.0.0.1:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

です。

低遅延化では、

-rtsp_transport udp
Enter fullscreen mode Exit fullscreen mode

がポイントです。

FFmpegではRTSPのlower transportとしてUDPまたはTCPなどを選択できます。UDPの場合はUDP、TCPの場合はRTSP control channel内へインターリーブしてデータが流れます。

UDPにはTCPのような、

パケット消失
↓
再送待ち
↓
到着するまで後続処理も待つ
Enter fullscreen mode Exit fullscreen mode

という仕組みがありません。

そのため、

多少映像が乱れてもよいので、古い映像を待たず現在の映像を出す

というリアルタイム用途と相性がよい方式です。

ただし当然ながら、

  • Wi-Fi
  • 混雑したLAN
  • インターネット越し

などパケットロスが発生しやすい環境では映像が乱れる可能性があります。

逆に安定性を優先するなら、

-rtsp_transport tcp
Enter fullscreen mode Exit fullscreen mode

という選択肢もあります。

低遅延と伝送の確実性はトレードオフです。


muxdelay 0

さらに、

-muxdelay 0
Enter fullscreen mode Exit fullscreen mode

としています。

muxdelayはFFmpegの出力側で使用される遅延関連パラメータです。FFmpeg公式のRTSP送信例でも-muxdelay 0.1を指定した例があります。

今回はさらに攻めて、

-muxdelay 0
Enter fullscreen mode Exit fullscreen mode

とし、

パケットをまとめるために待つ時間を極力発生させない

方向へ寄せています。


MediaMTX自身にはエンコードさせない

今回の構成で重要なのは、

FFmpeg
↓
rtsp://127.0.0.1:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

として、一度localhostのMediaMTXへpublishしていることです。

クライアントは、

rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

へアクセスします。

つまりMediaMTXは、

FFmpeg
   ↓
MediaMTX
   ↓
複数クライアント
Enter fullscreen mode Exit fullscreen mode

という中継点になります。

この構成にすると、キャプチャデバイスやエンコーダーを各クライアントが直接触る必要がありません。

複数端末へ配信したい場合にも扱いやすくなります。


ffplay側でもバッファを削る

送信側を低遅延化しても、受信側のプレイヤーが映像を1秒溜めてから再生していたら意味がありません。

そこでffplay側も、

ffplay \
  -fflags nobuffer \
  -flags low_delay \
  -framedrop \
  -rtsp_transport udp \
  rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

としています。


-fflags nobuffer

送信側と同様に、

-fflags nobuffer
Enter fullscreen mode Exit fullscreen mode

を指定し、入力解析時のバッファリングによるレイテンシーを減らします。


-flags low_delay

-flags low_delay
Enter fullscreen mode Exit fullscreen mode

でデコードも低遅延方向へ設定します。


-framedrop

-framedrop
Enter fullscreen mode Exit fullscreen mode

もリアルタイム用途では重要です。

ffplayでは、映像が同期から遅れた場合にvideo frameをdropできるオプションとしてframedropが用意されています。

低遅延用途では、

すべてのフレームを律儀に表示する
Enter fullscreen mode Exit fullscreen mode

よりも、

処理が遅れたら古いフレームを捨てる
↓
現在時刻へ追いつく
Enter fullscreen mode Exit fullscreen mode

ほうが重要です。

これは映像品質を多少犠牲にしてでも遅延を増大させないための設定です。


低遅延配信では「捨てる」ことが重要

ここまでの設定を見ると、共通した思想があります。

それは、

古いデータをなるべく保持しない

ということです。

通常の動画再生では、

パケットロスさせない
フレームを落とさない
映像を滑らかにする
Enter fullscreen mode Exit fullscreen mode

ことが重要です。

しかしリアルタイム映像では事情が違います。

例えばゲーム画面やカメラ映像を表示するとき、

3秒前の映像を完璧に表示
Enter fullscreen mode Exit fullscreen mode

することより、

多少フレームが飛んでも100ms前後の映像を表示
Enter fullscreen mode Exit fullscreen mode

できるほうが有用な場合があります。

そのため今回の設定では、

小さい入力キュー
↓
Bフレームなし
↓
QSVの非同期深度を削減
↓
muxerの待ち時間を削減
↓
UDP
↓
ffplayでもバッファ削減
↓
遅れたらframe drop
Enter fullscreen mode Exit fullscreen mode

という形で、パイプラインの各段階から待ち時間を削っています。


遅延はどこで発生するのか

低遅延化するときは、ネットワークだけを見るのではなく、

Capture
↓
Queue
↓
Filter
↓
Encode
↓
Mux
↓
Network
↓
Demux
↓
Decode
↓
Render
Enter fullscreen mode Exit fullscreen mode

というパイプライン全体を見る必要があります。

例えばLANのpingが、

1ms
Enter fullscreen mode Exit fullscreen mode

だったとしても、

エンコーダー 100ms
プレイヤーバッファ 500ms
Enter fullscreen mode Exit fullscreen mode

なら、ネットワークをいくら改善しても大きな効果はありません。

むしろリアルタイム動画の場合、

ネットワークそのものよりエンコーダー・デコーダー・プレイヤー内部のバッファが大きな遅延源になる

ことがあります。

今回多数の低遅延オプションを指定しているのはそのためです。


さらに遅延を詰めたい場合

この設定でもまだ遅延が気になる場合、いくつか試せるポイントがあります。

まずffplayのRTSP受信では、UDPパケットの並び替え用バッファが存在します。FFmpegドキュメントでは、UDP受信時のpacket reorderingをmax_delayを0にすることで無効化できるとされています。

例えば、

ffplay \
  -fflags nobuffer \
  -flags low_delay \
  -framedrop \
  -rtsp_transport udp \
  -max_delay 0 \
  rtsp://x.x.x.x:8554/hdmi
Enter fullscreen mode Exit fullscreen mode

という設定も試せます。

ただしこれはパケット順序の乱れに対する耐性をさらに削るため、ネットワーク品質によっては映像が不安定になります。

また、

-g 30
Enter fullscreen mode Exit fullscreen mode

を、

-g 15
Enter fullscreen mode Exit fullscreen mode

などへ縮めることもできます。

ただしGOPを短くするほど一般に圧縮効率は悪化します。

低遅延化には常に、

遅延
画質
帯域
安定性
CPU/GPU負荷
Enter fullscreen mode Exit fullscreen mode

のトレードオフがあります。


UDPだから必ず低遅延、ではない

注意したいのが、

UDP = 必ず低遅延
Enter fullscreen mode Exit fullscreen mode

ではないということです。

UDPにすると再送待ちを避けやすい一方、ネットワーク品質が悪い場合は、

packet loss
↓
映像破損
↓
次の復旧可能なフレームまで待つ
Enter fullscreen mode Exit fullscreen mode

ということもあります。

したがって実際には、

有線LAN + UDP

のような、低ロスかつ安定したネットワークで使うのが理想的です。

Wi-Fi環境ならUDPとTCPを両方試し、

実際の遅延
映像の乱れ
安定性
Enter fullscreen mode Exit fullscreen mode

を比較したほうがよいでしょう。


まとめ

今回の構成では、

HDMI
↓
V4L2 / ALSA
↓
FFmpeg
↓
Intel QSV H.264
↓
RTSP / UDP
↓
MediaMTX
↓
LAN
↓
ffplay
Enter fullscreen mode Exit fullscreen mode

というパイプラインを作りました。

低遅延化のポイントは、単に高速なエンコーダーを使用することではありません。

各段階で発生する、

「念のため少し溜めておく」バッファを可能な限り取り除くこと

が重要です。

今回の主要な設定をまとめると、

-fflags nobuffer
    入力側のバッファリングを削減

-flags low_delay
    コーデックを低遅延方向へ

-thread_queue_size
    キャプチャ入力のキューを小さくする

-c:v h264_qsv
    Intel QSVで高速にH.264エンコード

-preset veryfast
    エンコード速度優先

-async_depth 1
    QSV内部の非同期処理深度を削減

-bf 0
    Bフレームを無効化

-g 30
    30fpsなら約1秒GOP

-rtsp_transport udp
    再送待ちを避ける方向へ

-muxdelay 0
    muxer側の待ち時間を削減

-framedrop
    再生が遅れたら古いフレームを捨てる
Enter fullscreen mode Exit fullscreen mode

となります。

低遅延映像配信で大切なのは、

「すべてのフレームを確実に届ける」ことと「今の映像を届ける」ことは別の目標

だと理解することです。

録画ならフレームを失わないことが重要です。

一方、リアルタイムモニター、遠隔操作、ゲーム画面、カメラ監視などでは、古い映像が完全に届くよりも、多少フレームが欠けても最新の映像が表示されることのほうが重要です。

その方針でキャプチャ・エンコード・ネットワーク・プレイヤーのすべてを調整すると、RTSPでもかなり低遅延な映像伝送を構成できます。

Top comments (0)