コンテンツにスキップ

デスクトップの映像管理

NVR デスクトップには Live、Search、AI Statistics、Cameras の各ページがあります。UI には Qt Widgets と既存のハードウェア EGL レンダラーを使います。RTSP の受信、録画、再接続、選択中のカメラの解析、任意で有効にできるバックグラウンド解析は、amdva_streaming 上に構築した Qt に依存しない NVR バックエンド(backend/)のサービスです。

ライブ監視

ビューは 1、4、9、16 から選べます。タイルはダブルクリックの判定を待たず、マウスを押した時点で選択されます。表示数、タイルのカメラ、Single / grid、Show stream info の操作は、左側のカメラ一覧の上にあります。タイルをクリックしてからそこでカメラを選ぶか、一覧でカメラをダブルクリックします。Empty tile を選ぶと、その割り当てを解除します。パネル先頭の Exit Application は、確認なしでただちに終了します。終了処理は SIGINT と同じ経路です。パネルは起動のたびに閉じた状態で始まり、パネルと映像の境界にある細いボタンで表示したり隠したりできます。別のタイルで使用中のカメラを割り当てると、2 つのタイルが入れ替わります。空のタイルと切断されたタイルは位置を保ちます。グリッドの各タイルは、左上にカメラ名だけを、ストリーム情報と同じ描き方で表示します。選択中のタイルには枠が付きます。

Show stream info は、すべてのライブ画像上のコーデック、受信解像度、入力フレームレート、ビットレートの表示を切り替えます。既定はオンで、設定は <config>.ui.json に保存されます。左下のオーバーレイは 1 行で、例えば H.264 1920x1080 30.0 fps 4.2 Mbps decode 12.3 ms のように表示します。録画を再エンコードしているカメラでは、その下にエンコードの行を加え、例えば → AV1 1280x720 29.6 fps 1.9 Mbps -73% encode 96 ms のように、出力のコーデックと大きさ、エンコードしたフレームレートとビットレート、受信時のビットレートからの増減、フレームを Transcoder に渡してからエンコード済みで受け取るまでの平均の遅れを表示します。デコード遅延は、アプリケーションへの入力からデコード出力までの既存の移動平均で、カメラの撮影遅延ではありません。テレメトリを取得できない場合は decode N/A と表示します。グリッドの文字は 12px、シングルビューの文字は 22px を目標サイズとします。レートは約 1 秒ごとに更新され、キューでの破棄、デコード、録画対象の選択より前の、パーサーが出力した受信済みの圧縮アクセスユニットから算出します。ビットレートはエンコード済み映像ペイロードの 10 進 Mbit/s で、小数第 1 位まで表示します。RTP/RTSP/TCP/IP のオーバーヘッドと音声は含みません。フレームレートは実測した入力レートで、デコーダーや表示の FPS、SDP の公称値ではありません。コーデックと解像度はパーサーの caps から取得し、再接続時にリセットされます。これらのラベルは GPU のジオメトリで描画し、映像のピクセルの読み出しや文字画像のアップロードは行いません。DejaVu Sans semibold のアウトラインをテッセレーションし、4× MSAA でアンチエイリアス処理します。文字はタイルの幅に合わせて拡大縮小します。黒い背景の不透明度は 45% で、背景越しに映像が見えます。再生時のタイムスタンプも同じ読みやすいフォントと背景を使います。

Controller::status() は、ヘッドレスの利用側にも input_codec、input_width、input_height、input_fps、input_mbps を公開します。公開 Status 構造体が変わったため、SDK クライアントは再ビルドしてください。

タイルをダブルクリックするか Single / grid を使うと、そのタイルを拡大表示します。もう一度ダブルクリックするか Esc を押すと元に戻ります。拡大表示しても、元のグリッドのデコード/AI の選択は変わりません。グリッドを小さくすると、表示されるセルの外にあるカメラは選択が解除されますが、割り当ては復元用に保持されます。選択されていないカメラは、設定したバックグラウンド AI のポリシーに従います。登録済みのカメラはすべて録画を続けます。

割り当てにはデコーダースロットの割り当てとは独立した、固定のソース ID を使います。アプリケーションは割り当てを所有者のみのパーミッションで <config>.ui.json に保存し、ファイルはアトミックに置き換えます。既存の設定では、選択中のカメラから割り当てを初期化します。選択のない新規の設定では、4 つの空タイルから始まります。メディアの設定と UI のサイドカーファイルは管理を分けており、ヘッドレスのユーザーには UI のフィールドは不要です。保存される配置は 1 つだけで、名前付きの複数のビューはありません。

カメラ管理とストレージ

amdva-nvr CONFIG.json は起動するたびに、カメラの受信を始める前に、その設定の録画ルートにあるカタログ済みの以前の録画を、検索用画像、埋め込み、保存済みの解析結果とともに削除します。前回の実行で中断された録画も対象です。既存の排他的なカタログロックにより、別のプロセスが使用中のルートは消去されません。削除に失敗すると起動を中止します。カタログのデータベースと無関係なファイルは残ります。この起動時のポリシーはデスクトップの実行ファイルに固有で、SDK のカタログ/コントローラーの利用側では履歴は自動的に消去されません。

Cameras では追加、編集、削除の操作ができます。名前を変更しても接続とソース ID は変わりません。URL を変更すると、そのカメラの現在の受信、デコード、録画のセッションだけを終了し、新しい URL で再接続します。カメラを削除すると受信を停止して割り当てを解除しますが、履歴は削除しません。

表には接続、録画、解析、テレメトリの詳細が残ります。グリッドのカメラサイドバーには、カメラ一覧の下の 4 行にカタログ済み録画のバイト数、現在のファイルシステムの空き容量、設定した容量上限、予約済みの空き容量を表示します。録画スケジュール、カメラごとの録画の切り替え、エクスポートは提供していません。

Cameras の Retention (all cameras) では、保持する履歴を分単位の整数で設定します。既定値は 60 minutes で、retention_minutes フィールドのない設定でも同じです。Apply で設定を保存し、0 / Disabled で時間による削除を無効にします。コントローラーは約 1 秒ごとに、既知の終了時刻が now - retention_minutes 以前である非アクティブなファイルを探します。その境界時刻にまたがるセグメントや、長さが不明なセグメントは削除しません。再生、先読み、切り出しのハンドルが保持しているファイルは、解放されるまで保護されます。期限切れのファイルの削除は後で再試行されます。バイト数の容量上限と予約容量による削除は、保持期間を無効にしても独立して適用されます。

Delete all closed recordings は確認を求めたうえで、削除済みのカメラや中断された録画、旧形式の履歴も含め、すべてのカメラを対象にします。コントローラーが要求を受け取った時点で書き込み中でないファイルのスナップショットを取ります。再生とシーク用の切り出しは、削除を始める前に停止してハンドルを解放します。アクティブなファイルとスナップショット後に確定したファイルは残り、ライブの受信、録画、解析は継続します。進捗には確認済み、削除済み、保護中、失敗したファイルの数が表示されます。Cancel deletion は以降のバッチを止めますが、削除済みのファイルは元に戻せません。各バッチは最大 32 件を処理し、ファイルごとに 10ms の時間予算を確認します。個々のファイルシステム/データベース操作がその予算を超えることはあります。外部のファイルとシンボリックリンクは、たどることも削除することもありません。他の SDK の利用側がファイルを保持していると、一部だけが削除されることがあります。その場合はファイルを解放して操作をやり直してください。

再エンコード録画

ストレージを節約するため、Edit camera で、受信したストリームをそのまま保存する代わりに、そのカメラの録画を GPU で再エンコードできます。Re-encode recording on the GPU を有効にし、コーデック(H.264、H.265、AV1)、レート制御(VBR、QVBR、CBR)、ビットレート、解像度(元のまま、1080p、720p。拡大はしません)を選びます。設定は構成ファイルの sources[].transcode に保存され、次のキーフレームから有効になります。Cameras の一覧では Recording 列に表示されます。タイムライン、再生、プレビュー、保存した解析結果の表示、検索は、ほかの録画と同じく再エンコードしたファイルを使います。AV1 の出力は、幅を 64 の倍数、高さを 16 の倍数にそろえます(1080p は 1920x1088 で記録されます)。

再エンコードするカメラは、表示しているかどうかに関わらず 1 回だけデコードします。Transcoder のデコード結果をライブのタイル、バックグラウンド AI、検索にも使い、そのカメラ用の表示デコーダーは起動しません。再エンコードするカメラ 1 台ごとに、動画エンジンにはエンコードが、非表示のカメラではさらに全フレームのデコードが加わります。動画エンジンが追いつかないときは、デコードを遅らせる代わりにエンコーダーの手前でフレームを捨てるので、録画はフレームレートを下げて続き(エンコードの行に表示されます)、タイムスタンプはカメラのものを保ちます。エンコードは、解析が待つデコードも遅らせます。VA のデコーダーはデコードが終わる前にフレームを後段へ渡すため、動画エンジンがエンコードで埋まると、1440p のカメラ 16 台での物体検出の前処理は 4ms から 34ms に延び、人物の追跡は検出の 4 分の 1 の頻度まで落ち、GPU 負荷はかえって下がりました。そのため、2 つの方法でライブの解析を優先します。物体検出と追跡は、独立したストリームに処理を投入する前にフレームのデコード完了を待ちます。この待ち時間は Decoded frame wait として記録します(動画エンジンに余裕があれば約 0.15ms)。さらにコントローラーは 2 秒ごとに、この待ち時間の平均が 2ms を超えている間はすべての Transcoder に共通のフレームレートの上限を下げ(0.7 倍、下限 5fps)、0.5ms を下回っている間は 2fps ずつ上げて、最後は上限をなくします。追跡のワーカーは、待ち行列のフレーム(カメラごとに 1 つ)を最大 8 件まとめて取り出し、切り出して、それらに写る全員の ReID を 1 回の呼び出しで計算します。これでReIDのバッチが埋まります。Radeon 890M で 1440p30 HEVC のカメラ 16 台を表示し、すべてで人物を追跡している状態では、追跡は物体検出に追いつき、フレームを捨てません(まとめる前は約 1 秒に 1 件捨てていました)。4 台を再エンコードすると、エンコードは約 20fps に落ち着き、物体検出は全速(68 回/秒)の 95%、追跡は検出の 100% でした。動画エンジンに余裕があれば、遅れ約 10ms で全フレームを再エンコードします。それでも入力のバックログが上限を超えた場合は、Recording 列に encoder overloaded; waiting for keyframe と表示し、次のキーフレームから録画をやり直します。

デモの映像を全フレームで再エンコードした場合、H.265 VBR 2 Mbps 1080p が 1.8 Mbps、AV1 CBR 1.5 Mbps 720p が 1.4 Mbps、H.264 CBR 4 Mbps 元の 1440p が 3.9 Mbps で記録されました。カメラ自身のストリームは 6.7〜7.2 Mbps です。フレームを捨てると、記録されるビットレートはさらに下がります。

録画のタイムラインと再生

Live のシングルビューに録画のタイムラインがあります。タイルをダブルクリックする(または 1 ビューを選ぶ)とシングルビューに入り、ダブルクリックか Esc でグリッドに戻ります。シングルビューでは、グリッドの操作、カメラ一覧、ストレージを含む左側のパネルを隠します。左側に縦のタイムラインがあり、新しいものが上で、その上に Back to Tile と Back to Live があります。Back to Tile は Esc と同じくグリッドに戻ります。タイムラインのサイドバーの幅は 165–225px です。カバー範囲の凡例や、画像の外に重複するライブ状態の表示はありません。カメラ名は GPU の文字オーバーレイで、シングルビューでは大きく、グリッドでは小さく表示します。タイムラインは確定済みの最も古い録画から現在までを表し、録画済みの区間を緑、欠落を灰色で示します。カバー範囲は毎秒更新されます。

タイムラインをクリックすると、一時停止中であっても、その実時刻から再生を始めます。欠落部分を指定した場合は、通知を表示して次の録画に進みます。映像をクリックすると一時停止/再開します。Play/Pause/Stop のボタンはありません。ライブ映像をクリックすると表示だけが止まり、録画と推論は続きます。もう一度クリックすると最新のライブフレームから再開します。Back to Live は録画の再生を解放し、現在のライブストリームを表示します。アーカイブの末尾まで再生すると、自動的にライブへは切り替えず、確定するセグメントを待ちます。カメラを変更すると、そのカメラのライブ表示に戻ります。

録画フレームの日時(yyyy-MM-dd HH:mm:ss)は右下に表示します。ライブでは右上に明るい緑で Live、一時停止中は黄色で Live · Paused と表示します。一時停止中の映像の中央には、表示中の映像の高さの 20% の大きさで、半透明の灰色の再生三角形を表示します。タイムスタンプは、タイマーによる推定ではなく、表示しているデコード済みフレームの時刻です。以前の時刻表示の設定は無視されます。カメラ名と任意のストリーム情報は GPU の文字ジオメトリで描画し、映像の読み戻しやグリフテクスチャのアップロードは行いません。

ホバー中は、メイン画像を一時的にキーフレームのプレビューとその実際のフレーム時刻に置き換え、Preview のラベルを付けます。録画の再生はホバー中に一時停止し、離れると以前の状態に戻ります。一時停止中の再生は一時停止のままです。ライブは最新のフレーム、一時停止中であれば保持していたフレームに戻ります。タイムラインをクリックすると新しい再生位置が確定します。ポップアップウィンドウはありません。読み込み中、欠落、エラーの間は、黒に消去せず直前のフレームを保持します。表示中のプレビューのタイムスタンプは、新しい画像が届くまで保持しているフレームの時刻を示します。

1 つのプレビューワーカーが 75ms の間の要求をまとめ、指定位置またはそれ以前のキーユニットをデコードします。VA/VPE は最大 1280×720 の GPU 画像を生成し、DMA-BUF のインポートでメインの EGL レンダラーに渡します。8 枚の LRU でメモリを制限します。結果を反映できるのは最新の要求だけで、カメラの変更、ホバーの終了、削除の際には処理を取り消してキャッシュを解放します。映像のピクセル全体を CPU にコピーすることはありません。プレビューは、指定した時刻より最大 1 GOP 前になることがあります。

ライブ、保持中のライブ、録画の映像上で領域をドラッグすると検索できます。表示中のサーフェスは、切り抜きの取り出しが終わるまで固定されます。ドラッグすると録画の再生は一時停止し、Esc で取り消して以前の状態に戻ります。クリック/ダブルクリック/ドラッグはデスクトップのしきい値で区別します。検索には既存の上限付きの切り抜きと埋め込みのパイプラインを使います。Search から戻ると、カメラと再生モードが復元されます。

再生はファイルの境界や録画セッションの切り替わりを自動的にまたぎます。次の録画は 2 つ目のデコーダーで開いてプリロールし、先読みワーカーで使うのは最大 2 つの再生デコーダーと 1 つの保持中のプリロールフレームです。2 つのワーカーは同じ固定のプレビュースロットを使います。次のワーカーは一時停止したままで、引き継ぎまではそのスロットに書き込めません。古い要求は出力スロットの世代番号で無効になります。再生済みの時間に完全に含まれるファイルは飛ばし、一部が重なるファイルは古い時間を再生しないよう前方にシークします。

欠落部分は空白の時間として再生せず、すぐに飛ばします。確定済みの最新の録画まで再生すると、最後のフレームを表示したまま待機中のメッセージを示します。登録済みのカメラは、再接続待ちのものも含め、新しいフラグメントが確定すると再生を再開します。削除済みのカメラは、アーカイブの末尾で再生を終えます。アクティブなファイルと中断されたファイルは開きません。そのため、末尾を追従している間の再生は受信より約 1 セグメントと確定処理の遅延分だけ遅れます。利用できないファイル、壊れたファイル、対応していないファイルは、通知を表示して飛ばします。各ファイルは 1 回の open/seek 要求につき 1 回だけ試行します。デコーダーの準備のタイムアウトは 5 秒です。カタログの致命的なエラーは再生の状態に表示し、ライブの受信、録画、解析は止めません。

新しい録画では、受信側の実時刻の基準点と、splitmuxsink::format-location-full から渡される最初のエンコード済みサンプルから、セグメントの開始時刻を求めます。タイムスタンプは開いた録画とともにアトミックに登録し、クローズ時に確定したフラグメントのオフセットで確認します。これにより、ファイルの作成時刻がメディアのクロックより遅い場合でも、境界付近の解析結果が拒否されることを防ぎます。既存の録画は、以前に登録したタイムスタンプをそのまま使います。これらは受信側の日時で、カメラの撮影時刻や同期した監視用の時計ではありません。コンテナーだけを使う長さの索引付けは GUI スレッドの外で行い、1 回の更新で長さ不明のファイルを最大 1 つ処理し、プリロールの待ち時間は 2 秒で、映像デコーダーは使いません。失敗した調査はカタログが存在する間は再試行しません。長さが不明な録画は、10 秒の長さを仮定せず、カバー範囲から除外したままにします。再生時の日時はプレビューフレームのストリームタイムスタンプに基づき、再生のペース制御には GStreamer の単調増加するメディアクロックを使います。

再生は引き続き通常速度の H.264/H.265 で、既存のプログレッシブ NV12、2560×1440、BT.709 限定レンジの制約があります。音声は追加していません。Live から離れると録画の再生は一時停止し、戻ると以前の状態に戻ります。ライブのデコード/AI/録画は継続するため、最大 16 のライブデコーダーに加えて、2 つの再生デコーダーと 1 つのシークプレビュー用デコーダーが必要です。VA デコード、VPE プレビュー、DMA-BUF/EGL 表示の経路では、CPU へのピクセルのマップやアップロードは行わず、ソフトウェアデコーダーへのフォールバックもありません。現在開いているファイル、先読み中のファイル、切り出し中のファイルは、容量上限と時間による削除の両方から保護されます。停止、シーク、カメラの変更では、不要なハンドルを非同期に解放します。キャッシュされたサムネイルは小さな GPU サーフェスだけを保持し、ファイルは保護しません。

録画済み検出結果のオーバーレイ

解析した各フレームについて、正規化したボックス、信頼度、人物/トラックの識別子、カメラ、録画セッション、解析の実行、メディアに合わせた実時刻を SQLite に保存します。検出のないフレームも保存します。これは処理の遅い埋め込みの収集とは独立しており、再生中の推論は必要ありません。これらの記録がない既存の録画は、ボックスなしで通常どおり再生され、過去分の補完は行いません。

メインの録画ビュー、シークのプレビュー、検索結果のクリップは同じレンダラーを使います。同じ録画/セッション内で直前の最新の解析結果を使い、ボックスは解析間隔の最大 2 倍(上限 2 秒)まで保持します。検出のないフレームでボックスを消去し、シークと削除でキャッシュした結果を無効にします。座標の補間やカメラをまたいだ同一人物の照合は行いません。ラベルと色はライブの人物 ID と一致します。表示するのは #ID だけで、信頼度の割合は表示しません。未割り当ての ID とグリッドのラベルはすべて非表示です。色付きの枠の不透明度は 40%、その黒い輪郭は 20% です。ID の文字の不透明度は 60%、その背景は 30% です。

Catalog::enqueue_analysis は、記録を上限 1,024 フレームの書き込みキューに送ります。トランザクションごとに最大 64 フレームを保存し、250ms ごとにフラッシュします。キューがあふれると、未保存の最も古いフレームを破棄します。AI Statistics には Analysis DB save、Analysis save queue と失敗数が表示され、ストレージのエラーはビューの状態表示にも表示されます。追加した analysis_frames / analysis_objects テーブルは、ストレージの容量上限の見積もりに含まれ、録画とともに削除されます。遅れて完了した処理が、削除済みの録画のデータを再作成することはありません。Catalog::analysis は、上限付きの時間範囲をレンダリングスレッドの外で取得します。Catalog::opened は任意でメディアの開始タイムスタンプを受け取り、既定の引数によりソース互換性を保ちます。公開 Plane と GpuPreviewImage のメタデータに録画の識別情報と区間の範囲が加わったため、SDK の利用側は再ビルドが必要です。

フレームワークのインターフェースと移行

amdva/streaming/catalog.hpp には、Recording、RecordingQuery、StorageStatus、RecordingFile に加えて、ページ単位の Catalog::list、過去のカメラ、安全な open_recording、長さの更新、中断されたセッションの集計が加わりました。開いたファイルは読み取り専用の FD を所有し、その識別情報を保持します。カタログより長く存在することもできますが、削除からの保護が有効なのは所有するカタログの中だけです。コントローラーは、SQLite のハンドルを公開せずに、これらの操作とソースの更新を提供します。

カタログは、元の 4 列のスキーマをトランザクション内で移行します。既存の非アクティブな行は legacy に、以前アクティブだった行は interrupted になります。新しい finalized 状態には、デストラクターやタイムアウトではなく、フラグメントのクローズメッセージが必要です。旧形式の録画の再生は、パースとデコードに成功する必要があります。再生できても、元の録画が完全であることの証明として付け直すことはありません。既知のメディアの長さは、再生に成功した結果で補います。opened と closed は、既存の SDK 呼び出し側とソース互換性を保ちます。SDK クライアントは再ビルドしてください。4 列で挿入する古いバイナリは、移行後のカタログに書き込めません。

amdva/streaming/playback.hpp には、Qt に依存しないファイルベースの PlaybackSession が残り、TimelinePlaybackSession が加わりました。後者は VA ディスプレイと Catalog & を渡して構築します。カタログはプレーヤーより長く存在する必要があり、ディスプレイは保持されます。制御には open(camera_id, unix_ms, play, follow)、seek(unix_ms)、play、pause、stop、follow(expected) を呼びます。既定値は play=true と follow=true で、単独のアーカイブでは follow=false を使ってください。状態にはカメラ、要求した実時刻と実際の実時刻、状態、通知/エラー、フレーム/切り替え/ スキップの回数が含まれます。回数は open/seek でリセットされます。操作は非同期で、状態はスレッドセーフなスナップショットです。slot() は常に同じ PreviewSlot(amdva/streaming/preview.hpp)を返します。

カタログには coverage(camera_id)、resolve(camera_id, unix_ms)、next(recording)、media_start(name, unix_ms)、index_unknown(camera_id) が加わりました。カバー範囲では、接する半開区間や重なる半開区間を結合します。時刻の解決では、その時刻を含むファイルのうち開始が最も遅いものを優先し、ない場合は次に利用できるファイルを選びます。同順位の場合はファイル名の順序で安定して決めます。検索は一覧のページ上限には依存しません。Controller::recording_catalog() は、共有の再生サービス向けに、コントローラーが所有するカタログを公開します。その参照をコントローラーの存続期間より長く保持してはいけません。ブロックするカタログ操作は GUI スレッドの外で行ってください。

追加した NULL 許容の media_started_ms 列はトランザクション内で移行し、メディアのタイミングがない過去の行には created を使います。公開カタログのレイアウトが変わったため、SDK の利用側は再ビルドしてください。元の 2 引数の再生用 open オーバーロードを含め、既存のファイルベースのソース API は引き続き利用できます。利用側は、再生セッションを破棄する前に EGL/GL の参照を解放する必要があります。

どちらの再生セッションの型にも stop_and_release() が加わりました。これはデコーダーの破棄と入力ハンドルの解放の後に準備完了となる future を返します。KeyframePreviewSession も Qt に依存しません。保持したディスプレイと、セッションより長く存在するカタログを渡して構築し、request(camera, unix_ms) を呼び、status() をポーリングして要求 ID、状態、不変の GpuPreviewImage を取得します。エクスポートしたサーフェス記述子は、FD の所有権を呼び出し側に移します。cancel() は解放の待ち合わせ用の future を返し、保留中の処理と GPU キャッシュを消去します。共有された画像の所有権はキャッシュから追い出されても残ります。レンダラーは、所有権を手放す前に GPU のサンプリングを終える必要があります。

Catalog::begin_delete(optional_cutoff_ms) はスナップショットのジョブを作成し、delete_batch(job) はハンドル、アクティブなファイル、パスを確認しながらジョブを進めます。deletion_generation() を使うと、画像の利用側は古い結果を無効にできます。Controller::retention_minutes、delete_closed_recordings、cancel_recording_deletion は管理操作をキューに入れ、deletion_status は進捗を返します。任意で渡す共有の解放用 future により、スナップショット後の手動削除の開始を制御できます。GUI の呼び出し側はこれを使い、イベントループを止めずに、プレーヤーとプレビューの非同期の破棄と調整します。カタログとコントローラーの操作はヘッドレスのアプリケーションでも再利用できます。確認と描画はデスクトップアプリの役割です。

ファイルベースの SDK サンプル、タイムラインの SDK サンプル、GPU キーフレームプレビューのサンプル で、Qt に依存しない利用側の例を示しています。各サンプルは単独でカタログを排他的に所有します。稼働中の NVR の録画ルートを指定しないでください。

検証

その後の縦型タイムラインと表示の改良では、33 件のテストすべてと、16 チャンネル 1440p の 90 秒間の GUI 確認に合格しました。録画済みの解析はカメラごとに 1.998–2.015 Hz を維持し、起動時のトラッキングの破棄やコピー監査の違反はありませんでした。確認項目には、シングルビューで隠すコントロール、グリッドの 4 行のストレージ状態、Esc での復帰、一時停止中のライブの描画、プレビューの読み込み中に直前の画像を保持することが含まれます。管理の確認では、100 回のホバー要求(p95 193 ms、最大 286 ms)、期限切れ/削除、ストリーム情報の切り替えに合格しました。検索の確認では、テキスト検索、インライン再生、ライブ/保持中のライブ/録画での領域選択、削除に合格しました。ローカルのログとスクリーンショットは artifacts/view-layout/ にあります。

統合ビューの検証記録は 2026-10-22 の実装を対象としています。元の 1440p HEVC 映像を 16 チャンネルで 600 秒間実行し、グリッド/ライブ/再生/ホバーのモードを切り替えました。安定した区間でのトラッキングはカメラごとに 1.983–2.017 Hz で、保存された解析は録画の境界をまたいでカメラごとに 2.000 Hz でした。2 つのキューの破棄カウンターは、起動時も含めて 0 のままでした。プロセスの RSS のピークは 7,425 MiB でした。GPU のコピー監査に合格しました。これは 10 分間の確認で、長い長時間試験ではありません。

33 件のテストに合格した後、影響のあるカタログ/タイムライン/Unicode の契約テストにも合格しました。GUI の確認では、3 つの領域検索モードすべて、テキスト検索、インライン再生、ホバーからの復元、実際のマウスクリック、アーカイブ済みカメラの選択、期限切れ、削除を対象にしました。日本語のカメラ名で、その場でのホバーを 100 回計測した結果は p95 160 ms / 最大 289 ms で、コピー/GL 画像の違反は 0 件でした。コールドプレビューのテストでは、別途 100 回の H.264 1080p / HEVC 1440p の要求に合格しました。

用意したローカルのデモは bash artifacts/unified-view/run-demo.sh です。元の映像、ポート 18570、artifacts/unified-view/ 以下の分離した設定/録画を使います。これらの成果物はコミットしていません。

以下の数値は以前の UI のリビジョンのものです。統合ビューでは、それらのポップアップ、時刻表示の切り替え、一時停止中のシークの動作を、上記のとおり置き換えています。

playback-error-contract は、不正な録画、stop/open の繰り返し、エラーの分離、再生ハンドルの解放を確認します。カタログのテストでは、元のルート/所有権/シンボリックリンクの確認に加えて、移行、legacy/interrupted/missing の状態、ページカーソル、日付フィルター、保護、容量上限による削除を確認します。

エンコード済みの RTSP テスト用素材を使って、実際の GUI シナリオを実行します。

source scripts/env.sh
python3 examples/nvr/tools/vms_soak.py artifacts/nvr-local.json \
  artifacts/nvr-model/bus1080.h264 artifacts/nvr-model/blank1080.h265 \
  --seconds=600 --restart-at=180 --output=artifacts/vms-ten-minute

現在の GUI 検証ツールは、タイムラインのコントロール、ライブの受信/デコード/AI、連続再生、一時停止中の実時刻でのシーク、再接続、コーデックの変更を確認します。ラッパーでは少なくとも 130 秒を確保してください。240 秒のウォームアップ後のリソース増加の監視を維持し、確定した MKV を検証し、GPU のコピー監査ツールの結果を確認します。短い実行では、長期的なリソースの安定性は確認できません。Qt ウィジェットのキャプチャにはネイティブの映像ウィンドウが写らないことがあるため、別のフレームバッファーのキャプチャでインポートした再生画像を確認します。

timeline-catalog-contract は、実時刻の解決、カバー範囲の和集合、missing/active/長さ不明の録画、10,005 ファイルの履歴を確認します。オフスクリーンの timeline-widget-contract は、64 ビットの日付、端の対応付け、ホバー/ドラッグの動作、空の状態、描画を確認します。amdva-timeline-validate H264.mkv H265.mkv NEW_ROOT は 2 秒・30fps のテスト用素材を使い、6 回の境界の切り替え、一時停止中の正確なシーク、欠落、破損、追加された録画の追従、カメラの変更、連続した要求、再生/先読みによる削除保護の解放を確認します。フレーム間隔を 250ms のしきい値で計測し、コピー監査のプリロード下でも実行できます。

タイムラインの検証記録 には、ソース/ランタイムのハッシュとローカルの成果物が記載されています。標準ビルドは 25 件の CTest ケースすべてに合格しました。2 台のライブカメラ、AI、10 秒の録画を使った 130 秒間の GUI 実行では、コーデックを入れ替えた RTSP の再起動を経ても動作を続け、10 回の再生の境界をまたぎ、26 個の有効な録画を確定しました。新しい再生フレームを表示するスワップの間隔は最大 64.3ms で、テストのしきい値 250ms を下回りました。GPU のコピー監査ツールの違反は 0 件でした。単独のハードウェアテストでは、6 つの境界でのプレビューの送出間隔は最大 37.5ms で、その後、新しく確定したフラグメントの追従を確認しました。インストールした SDK のサンプルは、移行した 3 つのアーカイブファイルを連続して再生し、ファイルベースの再生との互換性を保ちました。ストリーミングライブラリは Qt に依存していません。

その後の 65 秒間の 2 カメラの GUI 回帰試験では、最初の選択時に自動で一時停止状態のプレビューになること、一時停止中にカメラを切り替えても選択した実時刻を保つこと、その後再生を再開できることを確認しました。その実行のコピー監査と、オフスクリーンのタイムラインウィジェットの契約テストはどちらも合格しました。

これらの確認では、16 のライブデコーダーと 2 つの再生デコーダーを同時に動かせる能力や、長期的なメモリ/FD の安定性は確認できません。GUI の実行は、リソース監視の 240 秒のウォームアップより短いためです。以下の結果はタイムラインの再設計より前のもので、以前のファイル一覧の UI を記録しています。

記録した結果

検証記録 には、コードのリビジョン、ハードウェア、ランタイムのハッシュ、ローカルの成果物のパスが記載されています。17 件の CTest ケースすべてに合格しました。600 秒間の実行では、20 台のカメラを受信し、16 のライブストリームを選択し、1 つの再生デコーダーを動かし、RTSP サーバーの再起動とコーデックの変更を行いました。1,201 個の録画がすべて確定し、メディアの検査に合格しました。240 秒のウォームアップ後の最大の増加量は、RSS が 9,652 KiB、GPU の割り当てが 18,360 KiB、FD が 4 つで、既存の監視基準の範囲内でした。固定したランタイムは、検証記録に記載した最後の小さな改良より前のものです。

最終的な実装は、別途行った 150 秒・25 フェーズの GUI 実行に合格し、317 個の有効な確定済み録画を生成しました。この実行では、カメラの追加/削除、履歴の保持、16 ビューでの置き換え、入れ替え、解除、保存された設定を確認しました。どちらの GUI 実行も、GPU のコピー監査ツールの違反なしで完了しました。インストールした SDK の再生では、コーデックごとに 300 フレームをデコードしました。AMDVA_BUILD_NVR=OFF でのビルドも、Qt に依存せずに完了しました。これらは正しさとリソースの確認であり、性能の保証ではありません。

録画管理と GPU によるシークプレビュー

recording-management-contract は、セグメントの終了時刻による期限切れ、境界時刻との重なり、長さ不明、保護、アクティブ/新規ファイルのスナップショット保護、上限付きのバッチ、存在しないファイル、シンボリックリンク/外部ファイルの保護を確認します。設定のテストでは、60 分の既定値、0 と不正な値の保存を確認します。

source scripts/env.sh
python3 examples/nvr/tools/vms_management_validate.py artifacts/vms-timeline-input.json \
  artifacts/nvr-model/bus1080.h264 artifacts/nvr-model/blank1080.h265 \
  artifacts/vms-timeline-fixtures/h264.mkv \
  --output artifacts/vms-management-check

これは別の録画ルートと RTSP サーバーを作成します。ハードウェアでの GUI 確認では、保持期間の保存、期限切れの保護/解放、画像クリックでの再生、ホバーからフレームのスワップまでの 100 回の計測、全削除の解放の待ち合わせ、2 つのライブのデコード/AI パイプラインが動作している間のアクティブな録画の保持を確認します。amdva-preview-validate H264.mkv H265.mkv NEW_ROOT は別途、100 回のコールドのサムネイル要求、上限付きのキャッシュ、取り消し、欠落、破損、削除による無効化を確認します。テスト用素材は、1 秒の GOP を持つ 2 秒・30fps のファイルです。どちらの確認でもコピー監査ツールを有効にでき、GUI の確認では GL のピクセルの読み戻しと CPU からのテクスチャアップロードも監査します。結果と制限は vms-management-validation-results.json に記録しています。これらの短い実行では、16 のライブに加えて 3 つの追加デコーダーを動かせる能力や、長期的なリソースの安定性は確認できません。

標準ビルドは 26 件の CTest ケースすべてに合格しました(分離したビルドでは 23 件)。標準の GUI 実行では、ホバーからスワップまでの 100 回の計測を完了し、95 パーセンタイルは 191.9ms、最大は 288.1ms で、GPU コピー/GL 画像の監査の違反は 0 件でした。また、保持期間、取り消し、画像クリックの操作、アクティブなフラグメントを残しライブ AI を継続したままでの 5 つのクローズ済みファイルの削除も確認しました。単独の 2 秒 GOP/B フレームのテストでは、100 回のコールドの H.264/H.265 要求を 95 パーセンタイル 88.8ms で完了し、その後、破損と削除による無効化を確認しました。インストールした SDK の利用では 320×180 の GPU 画像を生成し、ストリーミングライブラリは Qt に依存していませんでした。これらは合成入力からスワップまでの計測であり、物理的なディスプレイの遅延の計測ではありません。

その後のプレビュー保持の GUI 回帰試験では、99 回の読み込み要求で直前の GPU 画像が保持されること、91 回のマウス移動でポップアップの位置が変わることを確認しました。Wayland プロトコルのログから、ネイティブのサブサーフェスの位置が更新されていることを確認しました。削除による無効化では、保持していたピクセルが引き続き消去されました。管理の GUI シナリオ全体にも再び合格し、コピー/GL 画像の監査の違反は 0 件でした。詳細は管理の検証記録の追補の節を参照してください。

人物検索

ネイティブの EmbeddingGemma 2 の設定、収集のポリシー、テキストと領域による検索、検索結果のインライン再生については、NVR の意味検索 を参照してください。