デコードと表示のテレメトリ¶
フレームワークは、ローカルでの受信からデコードまで、および受信から表示までの遅延、スループット、フレーム間隔、観測された停止、把握できているローカルでの破棄を測定します。これらの測定値は観測結果です。測定値に基づいてストリームの合否を判定したり、ソースを再接続したり、フレームの受け入れ方針を変更したりすることはありません。
タイミングの境界¶
| ステージ | 観測内容 |
|---|---|
input |
このフレームの最初の RTP パケットを、jitter buffer に入る前の rtpbin の受信 sink で観測した時点 |
access_unit |
パーサーが出力した圧縮済みアクセスユニットが、設定したパッドに到達した時点 |
decode_input |
圧縮済みバッファーがデコーダーの sink パッドに到達した時点 |
decode_output |
生のバッファーがデコーダーの source パッドに到達した時点 |
preview_ready |
NVR の VPE によるプレビュー処理が完了した時点 |
draw |
NVR がその画像の GL 描画コマンドを発行した時点 |
swap |
この画像を含む描画について、QOpenGLWindow が frameSwapped を発行した時点 |
入力の時刻には、受信プロセスの単調時計を使います。これはユーザー空間での RTP の観測であり、NIC のハードウェアタイムスタンプ、カメラの撮影時刻、実時間の時計ではありません。TCP のフレーミングや、それより前のトランスポート処理はこの境界の外にあります。RTP の受信を jitter buffer の前で記録するため、受信からデコードまでの測定値には jitter buffer での滞留時間が含まれます。パーサーでの受信とデコーダーへの入力は別々に記録するので、デコーダーの前でのキュー待ちと、デコーダーの入力から出力までの経路(並べ替えや内部キューを含みます)を区別できます。
デコーダーの出力はバッファーの受け渡しを意味し、GPU での物理的なデコード完了を独立に同期して測ったものではありません。swap は Qt のバッファースワップ通知を意味し、モニターのスキャンアウトや発光ではありません。タイミングのために glFinish、VA サーフェスの同期、HIP の同期、CPU へのピクセルのマップを追加することはありません。既存の処理の同期も変更しません。フレームの最初の描画と最初に観測したスワップは 1 回だけ数えます。同じ画像を再び描画しても、新しい画像の FPS は増えません。
SDK と GStreamer での使い方¶
amdva/telemetry.hpp は、Qt と GStreamer に依存しない Telemetry 収集器、FrameTrace、TraceToken、ステージの利用可否、TelemetrySnapshot を提供します。Telemetry::monotonic_ns() が本番用の時計です。アプリケーションはこの時計を使ってステージを手動で記録するか、決定的なテストでは時計を注入します。これらの単調時計による観測値から、PTS、RTP タイムスタンプ、NTP タイムスタンプ、他プロセスの時計の値を引かないでください。
amdva/gst_telemetry.hpp は、PipelineTelemetry と、値をコピーして保持する
FrameTraceMeta を追加します。アダプターはパッドプローブとシグナルハンドラーを所有します。アダプターは PLAYING にする前に取り付け、アダプターを破棄する前にパイプラインを停止してください。アダプターは、取り外すまで観測対象の GStreamer オブジェクトを保持します。フレームのメタデータはパイプラインとアダプターの両方より長く存続でき、その場合も収集器は保持しますが、画像バッファーは保持しません。
auto collector = std::make_shared<amdva::Telemetry>("camera-1");
collector->begin_generation(1);
amdva::PipelineTelemetry timing(collector);
timing.attach_rtsp(rtsp_source);
timing.attach_access_units(parser_src_pad);
timing.attach_decoder(va_decoder);
// Set PLAYING, periodically read collector->snapshot().json(), then set NULL.
rtspsrc を使わない RTP パイプラインでは、attach_rtp(before_jitter, after_jitter) で同等の対応付けができます。attach_stage(pad, stage) は、ほかの処理の境界で既存のトレースにステージを記録します。レンダラーは frame_trace(buffer) で TraceToken を取得し、バックエンドからの実際の通知の時点で mark(Draw)、続いて mark(Swap) を呼び出します。sink のプローブだけでは表示を測定できません。
appsrc で別のデコードパイプラインに渡す場合は、バッファーのヘッダーとメタデータをコピーし、必要に応じて PTS/DTS を付け直したうえで、push する前にデコードアダプターの submit(buffer) を呼び出します。渡した PTS は元のトレースに関連付けられます。デコーダーの sink プローブは、デコーダーに実際に入った時点を記録します。source プローブは、まずデコーダーが保持したフレームの識別情報を使い、それがなければ一意に決まる出力 PTS を使います。FIFO の順序や、最も近い PTS による推測は使いません。これにより、フレームのメタデータをそのまま保持しつつ出力 PTS を調整するデコーダーにも対応できます。ネイティブの NVR には、これらの処理がすでに組み込まれています。H.264 または H.265 の RTSP を扱う単独のパイプラインについては、外部の
SDK テレメトリのサンプル を参照してください。このサンプルには互換性のある VA/GStreamer ランタイムが必要ですが、Qt は不要です。
RTSP アダプターは、1 つの RTSP ソースに映像トラックが 1 つであることを前提とします。複数トラックの RTP パイプラインでは、選択したトラックのパッドごとに別々の収集器とアダプターを割り当ててください。対応付けには SSRC と RTP タイムスタンプを使い、最初の到着を jitter buffer 通過後の PTS に、さらにパーサーの AU の PTS に関連付けます。保持期間内であれば、並べ替えや、タイムスタンプとシーケンス番号の折り返しに対応します。PTS の欠落やあいまいさ、対応していないタイムスタンプの書き換え、対応が取れない出力から、架空の受信遅延を作ることはありません。これらはカウンターを増やします。利用できない分布はサンプル数が 0 になり、時間の値は null になります。対応が取れない出力についても、ローカルでのデコーダー出力数は測定します。
対応表の大きさは 4,096 項目、およそ 30 秒に制限されます。期限切れと容量超過による削除はカウンターで確認できます。対応する出力が観測されないまま破棄されたデコーダーへの投入には専用のカウンターがあります。これはハードウェアデコーダーがフレームを落とした証拠ではありません。flush と、ストリームの差し替えによる stream-start イベントで、タイミングの世代を終了します。明示的な再接続では begin_generation を呼び出します。世代番号は、呼び出し側が古い番号を指定した場合でも、収集器の中で単調に進みます。古い世代のステージ通知は拒否されます。タイミングの世代は収集器ごとのもので、アプリケーションのソースの世代とは無関係に、デコーダーの flush で進むことがあります。対応付けにはタイミングのトークンを使ってください。
統計と NVR への組み込み¶
各スナップショットには continuous と background のグループがあります。入力と AU の数は、非表示のソースを含むすべてのソースについて、連続した受信を対象とします。まばらに行うバックグラウンドのデコード出力は background グループに入ります。音声の RTP は対象外です。一部だけ受信したフレームも、AU にならなかった場合を含め、入力として数えます。各グループには、ステージごとの数、FPS、経過時間、停止の統計、フレーム間隔の分布が含まれます。遅延の分布は、入力→デコード、デコーダーの入力→出力、入力→描画、入力→スワップ、デコード→プレビュー、プレビュー→描画、描画→スワップを対象とします。時間の単位はミリ秒です。数は収集器が存在する間ずっと累積します。再接続すると、直近のウィンドウとフレームの対応付けがリセットされます。ソースを削除して追加し直すと、新しい収集器が作られます。
直近の分布は、10 秒間で最大 4,096 サンプルを保持し、P95/P99 は
nearest-rank 法で求めます。samples、effective_window_ms、truncated から対象範囲が分かります。FPS は、直近 10 秒間の到着数を、実際に観測していた経過時間で割った値です。サンプルがない場合に、遅延を偽の 0 で表すことはありません。後のステージを記録するとき、コピーしたトークンは既知の前段のタイムスタンプを収集器から取り戻します。そのため、レンダラーは描画とスワップのフックの間でトークンのコピーを受け渡せます。スナップショットでは、上限のあるメタデータのサンプルだけを並べ替えます。メディアのホットパスではフレームごとのファイルシステム I/O を行いません。スナップショットは、パケットやフレームごとではなく、1 秒に 1 回など監視に適した頻度で取得してください。
稼働中のステージで最初のフレームが出た後、stall_ms 以上更新がない期間を
1 回の停止として記録します。しきい値に達するまでの時間を含め、空白期間全体の長さを数えます。継続中の停止はスナップショットのたびに更新されます。ステージを一時停止または終了すると、現在の観測期間が終わります。最初の観測がまだないステージは、停止時刻を推測せず、経過時間を利用不可として報告します。まばらに行うバックグラウンドのキーフレーム推論は、レートと間隔を別に扱い、連続ステージの停止検出の対象になりません。
NVR サンプルは Controller::telemetry() を提供し、実際のデコード、スワップ、AI の FPS と、入力→デコード、入力→スワップの P99 を列に表示します。統計の詳細は FPS
セルのツールチップに表示します。ウィンドウが非表示のとき、画面に出ていないとき、最小化されているときは、表示の観測を一時停止します。ヘッドレスモードでは、プレビュー、描画、スワップは利用不可になります。既存の Status::decoded
と display_fps は、API の互換性のため、従来どおりプレビュー処理の意味を保ちます。代わりに新しい decode_output と swap のステージのカウンターを使ってください。
"telemetry": { "enabled": true, "stall_ms": 1000 }
この NVR の設定は省略でき、既定値は上のとおりです。テレメトリを無効にすると測定用のプローブが外れ、スナップショットは enabled=false を明示的に報告します。カウンターでは、圧縮データのキューでの破棄、未処理のまま上書きされた最新フレーム、表示されなかったプレビュー、観測されなかった表示の投入、対応が取れなかった
RTP とデコードの対応付け、あいまいまたは欠落した PTS、期限切れ、容量超過による削除、古い世代、単調時計でのステージの順序の不正を区別します。これらのカウンターを運用上の合否として解釈することはありません。
amdva-streams と GUI の検証ツールは、定期的に出力する JSON に、別のトップレベルの telemetry 配列を追加します。AI の結果の送信には影響しません。nvr_soak.py は詳細な JSON-lines のログを保存し、遅延や FPS の受け入れ判定を加えずに、最終的なテレメトリを要約に含めます。簡潔なレポートは次のように生成します。
python3 tools/timing_report.py artifacts/timing-nvr-hour-final \
--start=720 --output=artifacts/timing-hour-report.json
このレポートは、実行全体の P99 ではなく、移動ウィンドウごとの P99 を要約します。また、FPS、間隔の P99、プロセスの CPU/RSS/GPU 使用量も記録します。CPU の割合は論理コア 1 つに対する値です。同じテスト用素材でテレメトリの有効と無効の実行を比べると、しきい値を設けずに測定のオーバーヘッドを確認できます。
検証ツール¶
telemetry-contract は、注入した時計を使って、間隔、パーセンタイル、移動ウィンドウの上限、停止の集計、重複した通知、世代の終了、無効化を検証します。gst-telemetry-contract は、制御したステージの遅延、RTP の折り返し、一部だけの映像フレーム、音声の除外、メタデータの所有権、調整された出力 PTS、並べ替えられた出力、メタデータの欠落、あいまいな PTS、flush、対応表の上限、copy-on-write のバッファーリストを検証します。フレームワークのパイプラインテストでも、AMD のタスク要素、ウォーターマーク、メタデータの集約、異種の mux/demux を通してタイミングのメタデータを確認します。
amdva-validate-timing-rtp INPUT.mkv h264|h265 normal|reorder|loss は、ローカルの Matroska の B フレームのテスト用素材を RTP パケットに分割し直し、パケットの並べ替えや重複、または欠落を加えて、実際の VA デコーダーでデコードします。運用上の遅延のしきい値は使わずに、対応付けの正しさを確認します。テスト用素材のタイムスタンプはコンテナーから取得します。エンコード済みデータだけを扱う NVR の RTSP テスト用素材サーバーは、意図的に B フレームのタイミングに対応していないため、このテストには使えません。このツールでは、RTP のタイムスタンプとシーケンス番号にオフセットを与えて折り返しを検証します。
nvr-timing-tests CONFIG は、実際の RTSP ソースを描画し、ウィンドウを非表示にし、再表示し、最小化し、再マップします。表示が一時停止している間もデコードが続くことと、同じ画像を繰り返し描画しても新しい画像の数が増えないことを確認します。非表示と再マップによる明示的な復元は、プログラムからの最小化解除の要求を実装していない
Wayland コンポジターに対応するためです。
記録したハードウェア検証¶
機械可読の検証レポートには、Ryzen AI 9 HX 370 / Radeon 890M での、テスト用素材、ランタイムのハッシュ、ステージごとの数、対応付けのカウンター、観測した遅延、FPS、リソースの統計を記録しています。
16 件の CTest の契約テストはすべて成功しました。フレームワークの GPU のコピー監査では、処理、ウォーターマーク、集約、mux/demux を通してタイミングのメタデータが保持され、コピー監査の違反はありませんでした。H.264 と H.265 の B フレームのテスト用素材は、通常の受け渡しと、並べ替えや重複を加えた場合で、それぞれ 60 フレームをデコードしました。パケットの欠落を加えた場合は 59 フレームでした。6 ケースすべてで、入力とデコードの対応付けが正確に保たれました。これとは別に、実際に描画するテストで、非表示、表示、最小化、再マップ、サイズ変更を確認しました。インストールした単独の SDK サンプルは両方のコーデックの RTSP を処理し、Qt に依存しないヘッドレスのビルドは表示を利用不可として報告しました。
NVR の回帰試験では、受信して録画する 20 ソース、16 の選択中のソース、4 つのバックグラウンドの枠を使い、選択、追加、削除の動的な操作、RTSP の再起動、コーデックの差し替えを行いました。90 秒の回帰試験では有効な録画を 45 件確定し、再起動から 7.50 秒後に受信と録画が回復しました。1 時間の実行では動的な操作を 100 サイクル行い、有効な録画を 1,315 件確定し、再起動から 7.00 秒後に回復しました。ウォームアップ後の最大の増加量は、RSS が 30,284 KiB、GPU の割り当てが 27,456 KiB、FD が 4 個でした。この実行では固定した中間段階のランタイムを使っています。そのハッシュと、その後の変更(最終ビルドでの別のテストで確認したもの)を明示的に記録しています。中間段階のランタイムでは、バックグラウンドのキーフレームにヘッダーを前置した後の入力の対応付けが欠けていました。最終ビルドでの比較で、修正した経路を非表示の 4 ソースすべてで確認しています。この長時間の実行の一部の時間帯には、回帰テストを並行して実行していました。テレメトリの有効と無効を順に比べる試験は、それらのジョブが終わった後に最終ランタイムで行っています。
レポートの遅延の統計は、10 秒ウィンドウごとの P99 の観測値を要約したもので、実行全体のパーセンタイルではありません。これらの統計と、テレメトリの有効と無効の 1 組の比較は観測結果であり、遅延や FPS の受け入れしきい値や性能の保証を伴いません。既存の長時間試験での録画とリソースの監視は、そのまま維持しています。
他の負荷から切り離した最終ランタイムでの比較では、選択中のソースのプレビュー FPS の中央値は、テレメトリありで 29.88、なしで 29.93 でした。プロセスの CPU 使用率の平均は、論理コア 1 つに対して 130.12% と 123.94% で、差は 6.18 ポイント(相対値で 4.99%)でした。これらの実行には、検証ツールによるスナップショットと JSON の出力が含まれます。テレメトリありでの入力→デコードと入力→スワップのウィンドウ P99 の中央値は、選択中のソースと観測ウィンドウ全体で、それぞれ 31.75 ms と 66.21 ms でした。90 秒の実行は 2 回とも、既存の録画とリソースの確認に合格しました。非表示の 4 ソースはすべて、バックグラウンドの入力→デコードのサンプルを出力し、対応が取れないデコーダーの観測は 0 件でした。