ライブ RTSP ストリームを処理する
| 項目 | 値 |
|---|---|
| カテゴリ | カメラとストリーミング |
| 難易度 | 中級 |
| 推定所要時間 | 5-10 minutes |
| ラベル | rtsp, h264, h265, streaming, input-group, live-input |
これは、入力がプログラム外部から発生する最初の章です。以前の章では、テスト画像を生成したり、ディスクからファイルを読み込んだりしていましたが、ここでは、ネットワークストリームからフレームが継続的に到着し、そのフレームをできるだけ速く処理します。この仕組みは、再利用可能なGraphフラグメントであるRtspDecodedInputであり、RTSPから生のフレームへの変換処理全体を1つのインターフェースにまとめます。
この章では、あえて「デコードされたフレームを処理する」ところで終了します。それらのフレームをModelに供給する方法については、別の場所で説明します(001は単一のモデル実行用、007はモデルをパイプラインに組み込む用、015はモデルをグラフ内に埋め込む用)。最終的には、RTSP URLに接続し、各デコードされたフレームのテンソルの形状を出力することで、ストリームが正常に動作していることを確認します。
これはコンシューマーのみです。ストリームを公開するには、別のRTSPサーバー(例:mediamtx)を実行し、--urlをそのサーバーに設定します。
ウォークスルー
RTSPクライアントを構成する
RtspDecodedInputOptionsは、ソースとデコーダーを構成します。urlは、rtsp://...ソースを選択します。codecは、エンコードされた形式を選択します。デフォルトはH.264です。このチュートリアルでは、avc、h265、およびhevcもサポートされており、AVCはH.264、HEVCはH.265と同等です。
ソースのフレームレートがわかっている場合は、source_fpsを設定します。設定しない場合、このチュートリアルではRTSPソースをOpenCVで開き、報告されたFPSを読み取り、検出された値をRtspDecodedInputに供給します。このグループ自体はURLをプローブしません。プローブに必要なのはOpenCVのみであり、Pythonバージョンでは必要に応じてインポートされるため、--source-fpsを供給することで、OpenCVなしで実行できます。プローブするには、pip install opencv-pythonでインストールします。H.265の場合、Neatはこの値を解析されたストリームキャップとデコーダー構成に伝播します。フレームレートは変更されません。H.265ストリームは、HEVC Mainプロファイル、8ビット、4:2:0入力を使用する必要があります。
tcp = trueを設定すると、RTPがTCP経由で送信されます。TCPは順序を保持し、失われたセグメントを再送信するため、UDPと比較して、目に見える損失を減らすことができますが、失われたデータを回復する際に遅延が増加する可能性があります。
// Configure the URL, codec, source cadence, and RTSP transport.
simaai::neat::nodes::groups::RtspDecodedInputOptions rtsp_opt;
rtsp_opt.url = url;
rtsp_opt.codec = parse_codec(codec_name);
rtsp_opt.source_fps = source_fps;
rtsp_opt.tcp = true;
グラフを構成する
2つのステージだけでGraphを構築します。それは、RtspDecodedInputフラグメント(ソース)と、シンプルなOutputノード(プルエンドポイント)です。フラグメントを追加するのは、単一のadd(...)操作です。これは内部的に、接続/デパケット化/デコード要素に展開されるため、構成は意図のレベルに留まります。入力がパイプラインの内部から生成されるため、初期サンプルを受け取らないbuild(RunOptions{})のオーバーロードを使用します。ストリームがフレームを生成するため、事前にフレームをbuild()する必要はありません。
// Build a Graph whose only stages are the RTSP group and an Output node.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::groups::RtspDecodedInput(rtsp_opt));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::RunOptions{});
デコードされたフレームをプルする
実行が開始されたら、ループしてタイムアウト付きでpull(...)を行います。各成功したプル操作により、1つのデコードされたフレームのテンソルを含むSampleが生成されます。チュートリアルでは、デフォルトのNV12出力を使用します。これは、YおよびUVプレーンのメタデータを持つ論理[H, W]テンソルとして表されます。何も(または空のテンソル)を返すプル操作は、frame=N rtsp_timeoutを出力し、ループを中断します。これは通常、URLが間違っているか、ストリームがデータを送信していないことを意味します。タイムアウトは、停止したストリームがプログラムをハングアップするのを防ぎます。
フ レームはtensors_from_sample(*sample, true)を使用して抽出されます。ループは、shapeを読み取る前に、空のリストがないか確認します。
for (int i = 0; i < frames; ++i) {
auto sample = run.pull(/*timeout_ms=*/5000);
if (!sample.has_value() || simaai::neat::tensors_from_sample(*sample, true).empty()) {
std::cout << "frame=" << i << " rtsp_timeout\n";
break;
}
const auto tensors = simaai::neat::tensors_from_sample(*sample, true);
const auto& shape = tensors.front().shape;
std::cout << "frame=" << i << " shape=[";
for (std::size_t d = 0; d < shape.size(); ++d) {
std::cout << shape[d] << (d + 1 < shape.size() ? ", " : "");
}
std::cout << "]\n";
}
実行
この章では、ライブRTSPストリームを消費するため、アクセス可能な
--urlを指定する必要が あります。カメラがない場合は、互換性のあるビデオをRTSP
サーバー経由で公開し、--urlをそれに向けてください。Neatのインストールルート(share/と
lib/を含むディレクトリ)から、Pythonおよび**C++(事前にビルドされたもの)**コマンドを実行します。ソースからビルドするコマンドは、リポジトリのルートから実行します。
自動化されたチュートリアル回帰テストは、両方のコーデックを実行します。これは、SIMANEAT_TEST_RTSP_H264_URLまたはSIMANEAT_TEST_RTSP_H264_URLS、および
SIMANEAT_TEST_RTSP_H265_URLまたはSIMANEAT_TEST_RTSP_H265_URLSから、最初に利用可能なURLを読み取ります。テストは、各ソースをプローブし、検出されたFPSをRTSPグループに供給します。
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_018_consume_rtsp_stream \
--url rtsp://host:port/stream --codec h265 --source-fps 30 --frames 5
C++ (build from source):
./build.sh --target tutorial_018_consume_rtsp_stream
./build/tutorials-standalone/tutorial_018_consume_rtsp_stream \
--url rtsp://host:port/stream --codec h265 --source-fps 30 --frames 5
予想される出力(形状は、ストリームの解像度とデコーダーの形式によって異なります):
frame=0 shape=[720, 1280]
frame=1 shape=[720, 1280]
frame=2 shape=[720, 1280]
frame=3 shape=[720, 1280]
frame=4 shape=[720, 1280]
ストリームにアクセスできない場合、代わりにframe=0 rtsp_timeoutが表示されます。この章のC++ソースコードを、カスタムのCMakeLists.txt(追加のフォルダーは不要)を使用して、独自のプロジェクトに統合する方法については、ランディングページにあるチュートリアルの実行方法を参照してください。
完全なソース
完全なソースプログラムを表示
// Consume a live H.264 or H.265 RTSP stream via RtspDecodedInput.
//
// The fragment handles RTSP connect, codec-specific depacketize/parse, and
// hardware decode. This chapter is about the input fragment only.
//
// Usage:
// tutorial_018_consume_rtsp_stream --url rtsp://host/path
// [--codec h264|avc|h265|hevc] [--source-fps 30] [--frames 5]
#include "neat.h"
#include "nodes/groups/RtspDecodedInput.h"
#include <opencv2/videoio.hpp>
#include <cmath>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
namespace {
bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}
int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string v;
if (!get_arg(argc, argv, key, v))
return def;
return std::stoi(v);
}
simaai::neat::nodes::groups::RtspCodec parse_codec(const std::string& value) {
using simaai::neat::nodes::groups::RtspCodec;
if (value == "h264" || value == "avc")
return RtspCodec::H264;
if (value == "h265" || value == "hevc")
return RtspCodec::H265;
throw std::invalid_argument("--codec must be h264, avc, h265, or hevc");
}
int probe_source_fps(const std::string& url) {
cv::VideoCapture capture(url);
if (!capture.isOpened()) {
throw std::runtime_error("failed to open RTSP source for FPS probe");
}
const int fps = static_cast<int>(std::lround(capture.get(cv::CAP_PROP_FPS)));
capture.release();
if (fps <= 0) {
throw std::runtime_error("failed to probe a positive RTSP source FPS");
}
return fps;
}
} // namespace
int main(int argc, char** argv) {
try {
std::string url;
if (!get_arg(argc, argv, "--url", url)) {
std::cerr << "Usage: tutorial_018_consume_rtsp_stream --url <rtsp://...> "
"[--codec h264|avc|h265|hevc] [--source-fps <n>] [--frames <n>]\n";
return 1;
}
std::string codec_name = "h264";
get_arg(argc, argv, "--codec", codec_name);
const int frames = parse_int_arg(argc, argv, "--frames", 5);
int source_fps = parse_int_arg(argc, argv, "--source-fps", -1);
if (source_fps != -1 && source_fps <= 0) {
throw std::invalid_argument("--source-fps must be positive");
}
if (source_fps == -1) {
source_fps = probe_source_fps(url);
}
// CORE LOGIC
// Configure the URL, codec, source cadence, and RTSP transport.
simaai::neat::nodes::groups::RtspDecodedInputOptions rtsp_opt;
rtsp_opt.url = url;
rtsp_opt.codec = parse_codec(codec_name);
rtsp_opt.source_fps = source_fps;
rtsp_opt.tcp = true;
// Build a Graph whose only stages are the RTSP group and an Output node.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::groups::RtspDecodedInput(rtsp_opt));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::RunOptions{});
for (int i = 0; i < frames; ++i) {
auto sample = run.pull(/*timeout_ms=*/5000);
if (!sample.has_value() || simaai::neat::tensors_from_sample(*sample, true).empty()) {
std::cout << "frame=" << i << " rtsp_timeout\n";
break;
}
const auto tensors = simaai::neat::tensors_from_sample(*sample, true);
const auto& shape = tensors.front().shape;
std::cout << "frame=" << i << " shape=[";
for (std::size_t d = 0; d < shape.size(); ++d) {
std::cout << shape[d] << (d + 1 < shape.size() ? ", " : "");
}
std::cout << "]\n";
}
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}