はじめに
とあるシステムの検証で、RTP over HTTP(RTSP over HTTP) の多セッション負荷を確認する必要が出てきました。
- クライアント側は Axis カメラ互換の「HTTPトンネル」(GET/POST + X-Sessioncookie)のみ対応
- サーバ側は OSS の MediaMTX を使って RTSP サーバを立てたい
- 100〜300 セッション規模で負荷を見たい
という背景から、
「Go で Axis 互換の RTSP-over-HTTP プロキシを書いて、MediaMTX に中継しよう」
となり、ついでに 生成系AIをフル活用してツールを作ってみました。
全体構成
今回の構成はシンプルです。
録画サーバ/ビューア
↓ HTTP/1.0 (GET/POST, X-Sessioncookie)
rtsp-http-proxy (Go)
↓ RTSP/TCP
MediaMTX
↓ H.264 copy
MP4ファイル
ポイント
- クライアントは Axis カメラ互換の HTTP トンネルで接続
- 自作 rtsp-http-proxy が HTTP トンネルを終端して RTSP/TCP へ変換
- MediaMTX は MP4 ファイルから H.264 を copy 配信(再エンコードなし)
要件整理
- RTSP over HTTP(Axis スタイル)の再現
- GET /axis-media/media.amp
- POST /axis-media/media.amp
- X-Sessioncookie でセッション識別
- セッション管理
- 1 セッション = 1本の RTSP/TCP コネクション
- RTSP リクエストの書き換え
- HTTPリクエストをRTSPに書き換え
- OPTIONS rtsp://127.0.0.1:8554/axis-media/media.amp RTSP/1.0
- メッセージ処理
- Base64 をデコードして RTSP メッセージを MediaMTX に転送
- MediaMTX からの RTSP/RTP バイト列をそのまま HTTP レスポンスとして中継
ChatGPTへの要求
以下のプロンプトを設定したコードを動作確認しながら仕上げました。
MediaMTX を RTSP サーバとして利用し、
Axis 互換の「RTSP over HTTP(HTTPトンネル)」を行うプロキシを Go で実装したいです。
【やりたいこと】
- クライアントは GET/POST /axis-media/media.amp + X-Sessioncookie でアクセス
- POST ボディは Base64 でエンコードされた RTSP メッセージ
- セッションは cookie 単位で管理し、上流には RTSP/TCP で中継する
【要件】
- RTSP リクエスト 1 行目の書き換え
- GET 側で MediaMTX からの RTP をそのまま HTTP 応答として流す
- YAML の config で listen / upstream / log を指定できるように
まず、全体構成の設計方針を文章で説明し、そのあと main.go のフルコードを出してください。
実装ポイント
セッション管理
セッションはcookie でマップしています。
受信したリクエストがcookieと紐づいて管理されるようになっています。
type session struct {
cookie string
remote string
created time.Time
rtspMu sync.Mutex
rtspConn net.Conn
getMu sync.Mutex
getWriter http.ResponseWriter
getFlusher http.Flusher
upstreamReaderStarted bool
postMu sync.Mutex
b64Buf []byte
rtspBuf []byte
closed bool
}
RTSPリクエストの書き換え
/axis-media/... を rtsp:///axis-media/... に変換します。
func rewriteRTSPRequest(req []byte) []byte {
idx := bytesIndexCRLF(req)
if idx <= 0 {
return req
}
firstLine := string(req[:idx])
rest := req[idx+2:]
fields := strings.Split(firstLine, " ")
if len(fields) < 3 {
return req
}
method, uri, version := fields[0], fields[1], fields[2]
if strings.HasPrefix(strings.ToLower(uri), "rtsp://") {
return req
}
if strings.HasPrefix(uri, "/") {
newURI := fmt.Sprintf("rtsp://%s%s", cfg.Upstream, uri)
newFirst := fmt.Sprintf("%s %s %s", method, newURI, version)
// ログなど省略…
newReq := append([]byte(newFirst), '\r', '\n')
newReq = append(newReq, rest...)
return newReq
}
return req
}
MediaMTX側の設定
MediaMTX は、MP4 ファイルから H.264 をコピー配信する設定にしました。
paths:
"axis-media/media.amp":
source: file
sourceOnDemand: yes
sourceFile: /path/to/sample.mp4
# H.264 copy 配信(再エンコードなし)
60秒で切断される問題と KeepAlive
最初は MediaMTX のログに、こんな感じのメッセージが出ていました。
約 60 秒ごとに RTSP コネクションが閉じられ、セッションも破棄されてしまいます。
[RTSP] [conn 127.0.0.1:51984] closed: read tcp 127.0.0.1:8554->127.0.0.1:51984: i/o timeout
[RTSP] [session XXXXXXXX] destroyed: not in use
対策として、、
- プロキシ側から一定間隔で OPTIONS を送る KeepAlive を実装
に変更したところ、長時間安定してセッションを維持できるようになりました。
まとめ
- Axis 互換の RTSP over HTTP を Go で再現し、MediaMTX に中継するプロキシを実装
- 生成AIをうまく使うことで、要件整理 → 初回実装 → 不具合修正 → KeepAlive 対応までかなりのスピードで回すことができた
- 次はHTTPSにも対応させたい