GStreamerで動画ファイルの映像と音声の両方をRTSPで配信する方法

# はじめに

Jetson NanoからGStreamerを使ってリモートのMacにRTSPで動画を配信したい。GStreamerは古くからあるソフトウェアなので情報は非常に多いが、映像と音声を同時にRTSPで配信するサンプルがなかなか見つからなかった。そのためここではある動画ファイルの映像と音声を同時にRTSPで送信することを試みる。

# 実験環境

| マシン | 用途 | 動作 |
|-------|-----|---|
| Jetson Nano | サーバ | GStreamerで動画をRTSPで送信 |
| MacBook Air | クライアント | VLCにより動画をRTSPで受信

# GStreamerパイプラインの書き方

パイプライン記法については別の記事として分離した(下記URL)。

[gst-launch-1.0の典型的なパイプライン記法](https://qiita.com/yamamo-to/items/2c95760c0cce8f6fb0ba)

# 処理概要

やりたいことは「ファイルから動画を読み込みRTSPで送信」である。図にすると下記のようになる。

![gstreamer_rtp_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252224/381921f1-5444-8b16-cd9f-1bf671ff55bd.png)

動画をまず「映像(video)」と「音声(audio)」に分け多重化を解く(いわゆるdemux)。それぞれRTPに変換して、最後にまた多重化して合わせ込む(mux)。後で分かったことだが、RTPの場合には合わせ込むmuxの部分は不要だった。すなわち下記の図が正しい。

![gstreamer_rtp_graph2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252224/89d717ef-7f7d-3d59-e6e8-8a9547b015a8.png)



# 動画ファイルをRTSPで配信する

## 準備

RTSPで配信するために`test-launch`というプログラムを使用している。これは下記からダウンロードしてコンパイルする必要がある。

https://github.com/GStreamer/gst-rtsp-server/blob/master/examples/test-launch.c

```shell:ターミナル
sudo apt install libgstrtspserver-1.0-dev
gcc -o test-launch test-launch.c `pkg-config --cflags --libs gstreamer-rtsp-server-1.0`
```

## 配信用スクリプトの作成

GStreamerの記法をそのまま書くと非常に分かりにくいので

- main ... 分岐前と分岐後を示すメインストリーム
- video ... 映像のみのストリーム
- audio ... 音声のみのストリーム

の3つを別々に用意し最後にスペースで結合することを考えた。また各ストリームの処理はビックリマークで結合するために配列を使用した。そのためシェルスクリプトにはbashを用いている。

```shell:file2rtsp.sh
#!/bin/bash

if [ $# -ne 1 ]; then
    echo "usage $0 filename"
    exit 1
fi

main=(
    "filesrc location=$1"
    "qtdemux name=demux"
)

video=(
    "demux.video_0"
    "queue"
    "rtph264pay name=pay0 pt=96"
)

audio=(
    "demux.audio_0"
    "queue"
    "decodebin"
    "vorbisenc"
    "rtpvorbispay name=pay1 pt=97"
)

pipeline_main="$(IFS=!; echo "${main[*]}" | sed 's/!/ ! /g')"
pipeline_video="$(IFS=!; echo "${video[*]}" | sed 's/!/ ! /g')"
pipeline_audio="$(IFS=!; echo "${audio[*]}" | sed 's/!/ ! /g')"

pipeline="$pipeline_main $pipeline_video $pipeline_audio"

echo "$pipeline"
./test-launch "$pipeline"
```

最初に実行権限を与える。

```shell:JetsonNanoで実施
chmod +x file2rtsp.sh
```

あとはファイル名を指定するだけ。

```shell:JetsonNanoで実施
./file2rtsp.sh ファイル名
```

## 説明

先のスクリプトは下記のコマンドを実行することに等しい。`demux.video_0`や`demux.audio_0`などに気をつけてみると、どこで区切れているのかが分かる。

```shell
./test-launch "filesrc location=ファイル名 ! qtdemux name=demux demux.video_0 ! queue ! rtph264pay name=pay0 pt=96 demux.audio_0 ! queue ! decodebin ! vorbisenc ! rtpvorbispay name=pay1 pt=97"
```

# VLCによる接続

接続するのはVLCを用いる。`IPアドレス`の部分をサーバのアドレスにすれば接続できる。

```shell:Macで実施
open -a vlc rtsp://IPアドレス:8554/test
```

結果[^1]
[^1]: 動画はhttps://sample-videos.comよりダウンロード
![rtsp_vlc_client.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252224/a469cf8e-2ae8-958d-13dd-a8b7fc4be2f2.png)



# まとめ

GStreamerを使ってRTSPにより映像と音声の両方を送信することができた。GStreamerの奥が深すぎて学ぶのも一苦労だ。

# おまけ「rtpmuxが不要な理由」

rtpmuxの説明

https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-rtpmux.html

を読むと「The rtp muxer takes multiple RTP streams having the same clock-rate and muxes into a single stream with a single SSRC.」と書かれており、多重化の元となるソースは完全に同じクロックレートでなければならない。通常「映像」と「音声」のクロックレートが同じはずはないので、rtpmuxを使って多重化することはできない。従って下記(以前のシェルスクリプト)のようにrtpmuxを含めるのは間違いである。

```shell:file2rtsp.sh.old
#!/bin/bash

if [ $# -ne 1 ]; then
    echo "usage $0 filename"
    exit 1
fi

main=(
    "rtpmux name=mux"
    "filesrc location=$1"
    "qtdemux name=demux"
)

video=(
    "demux.video_0"
    "queue"
    "rtph264pay name=pay0 pt=96"
    "mux.video_0"
)

audio=(
    "demux.audio_0"
    "queue"
    "decodebin"
    "vorbisenc"
    "rtpvorbispay name=pay1 pt=97"
    "mux.audio_0"
)

pipeline_main="$(IFS=!; echo "${main[*]}" | sed 's/!/ ! /g')"
pipeline_video="$(IFS=!; echo "${video[*]}" | sed 's/!/ ! /g')"
pipeline_audio="$(IFS=!; echo "${audio[*]}" | sed 's/!/ ! /g')"

pipeline="$pipeline_main $pipeline_video $pipeline_audio"

echo "$pipeline"
./test-launch "$pipeline"
```