こんにちは。Visual M2M Data Visualizer の製品開発を担当している白金です。
以前に、「WebCodecs の VideoDecoder を使用してH.264の動画を再生してみた」の記事を紹介させていただきました。
その後、弊社が提供するVisual M2M Data Visualizer に含む標準ビジュアルパーツ「Video Player パーツ」に WebCodecs の VideoDecoder を適用した結果、複数の課題が改善できましたので、改善結果と苦労の軌跡について紹介したいと思います。
Visual M2M Data Visualizer を利用した動画ストリーミング再生に興味がある方、または WebCodecs の VideoDecoder を利用中・利用したい方のお役に立てたら嬉しいです。
WebCodecs について気になる方は、上記ブログで紹介しておりますのでご参照ください。(当ブログでは WebCodecs の説明については割愛します。)
改善した結果
まずは改善した結果について、5つの項目について紹介したいと思います。
動画再生の安定化、遅延の改善、及び再生中の動画情報の表示が可能になった、など以下の課題が解決できました。
- 特定のH.264 エンコーダーとの組み合わせで発生していた動画再生の不安定、カクつきが解消した。
- LIVE 動画 15fps の再生で約70〜200msの遅延を改善し、iOS向けアプリ の Stream Video の遅延とほぼ同じになった。
- Google Chrome の詳細設定で「ハードウェアアクセラレーションを使用可能な場合は使用する」を OFF にする運用が不要となり、運用コスト削減に繋がった。
- 再生している時刻の動画フレームのタイムスタンプの表示が可能になった。
- 再生している動画のコーデック、解像度、フレームレートなどの情報の表示が可能になった。
では、Video Player パーツの改善前後について詳細な情報を紹介していきたいと思います。 ボリュームが多いですが、最後までお付き合いいただけると嬉しいです。
Video Player パーツとは?
Video Player パーツは、弊社が提供するVisual M2M Data Visualizer に含む標準ビジュアルパーツの一つです。
計測した H.264 の動画フレームを弊社のintdashサービスを経由して LIVE ストリーミング再生、またはストアした動画を後から動画再生するための機能として提供しています。
また、Visual M2M Data Visualizer のダッシュボードで同じ時刻の計測データの値と動画の再生時刻を同期させて再生することも可能です。
その他の表現については、Visual M2M Data Visualizer でご確認いただけます。
改善前の Video Player パーツ
アーキテクチャー
LIVE 動画の再生は、H.264 の動画フレームを Google Chrome (M93以前) では直接表示する方法が API で提供されていなかったため、Media Services を経由して、Fragmented MP4 のコンテナ形式に変換し、Media Source Extension API を使用して再生していました。
Fragmented MP4 については、下記記事でも紹介されていますので当ブログでは説明を割愛します。
過去動画の再生は、HLS コンテナ形式でストアされた動画情報を、hls.js の SDK を使用していました。
発生していた課題
上記アーキテクチャーの実装では以下4つの課題が発生していました。
(一部、前回のブログから再掲になります。)
課題1 - LIVE 動画再生時の動画フレームのタイムスタンプの判別
モバイル回線など電波状況により著しく通信速度が低下するケースがある環境では、H.264 の動画フレームがリアルタイムで intdash Core Services に届かず、 Fragmented MP4 の動画フレームが欠損した状態になるケースがありました。
課題2: LIVE 動画再生時のGoogle Chrome で動作する H.264 デコーダーの遅延
弊社で扱うことができる動画フレームは、特定の H.264 エンコーダー には限定していませんが、H.264 エンコーダーと Google Chrome が使用するハードウェアアクセラレーションの組み合わせによって、デコード対象となる動画フレームを十分に確保しないと、デコード後の動画フレームが出力されないケースがありました。
そのため、低遅延で動画をストリーミングするケースでは、動画の再生が安定しない課題が発生していました。
暫定対策として、Google Chrome 詳細設定の「ハードウェア アクセラレーションが使用可能な場合は使用する」を OFF に変更することで、 ソフトウェアデコーダーを使用するようになりデコーダー遅延は解消できました。
しかし、事前に Google Chrome の設定を変更する必要がある運用コストの課題は未解決の状態になっていました。
課題3 - LIVE 動画再生時の動画フレームの受信が詰まったときの再生位置のケア
Google Chrome を使用して Fragmented MP4 を再生するときは、動画フレームに含まれる timescale や duration の情報を使用して、各フレームを実時間に沿って再生します。
intdash Core Services から H.264 動画フレームを受信するときに、一時的な通信速度の低下が発生すると、動画フレームの受信が詰まるケースがあり、動画再生は一時的に停止する症状が発生します。
その後、通信速度が復帰した後に、詰まっていた動画フレームを短時間に一度に取得することも想定されますが、その際、詰まっていた動画フレームが順番に再生されることで再生可能な動画フレームが余分に生じてしまい、LIVE 動画の再生遅延が発生する要因となっていました。
暫定対策として、現在の動画の再生時刻と、まだ再生していない再生可能な動画の最後の時刻を定期的に監視することで、一定以上再生可能なフレームあるときは、再生位置を再生可能な最後のフレームの位置までシークする補正を適用していました。
課題4 - 過去動画再生時の HLS 動画のセグメント内の動画フレームのタイムスタンプの補正
intdash で扱う100ミリ秒∼1ミリ秒間隔程度の高頻度で発生する時系列データでは、データの転送遅延とタイムスタンプによる忠実な再生に関する課題を解決する必要があります。
動画の再生についても、計測した動画のタイムスタンプに忠実に再生することで、100ミリ秒∼1ミリ秒間隔程度の高頻度で計測した他のセンサーの値と同期して確認、及び解決したいケースがあります。
また、Media Services では過去再生で使用する動画として HLS コンテナ形式を採用しており、HLS コンテナ形式に含む一定間隔で区切られた各セグメントファイル内に含む動画フレームのタイムスタンプは、等間隔のタイムスタンプで再配置される仕様になっています。
そのため、動画のタイムスタンプに忠実に再生、確認したいケースを満たせないケースがありました。*1
改善後の VideoPlayer パーツ
ここまで、改善前の Video Player パーツの課題について紹介させていただきました。
アーキテクチャー
まずは、改善後のアーキテクチャーの変更点について紹介したいと思います。
Google Chrome M94 で導入された WebCodecs の VideoDecoder を使用することで、直接 H.264 動画フレームを利用することが可能になり、構成がシンプルになりました。
改善結果は下記のとおりです。
(当ブログの冒頭に記載した改善結果と同じ内容です。)
- 特定のH.264 エンコーダーとの組み合わせで発生していた動画再生の不安定、カクつきが解消した。
- LIVE 動画 15fps の再生で約70〜200msの遅延を改善し、iOS向けアプリ の Stream Video の遅延とほぼ同じになった。
- Google Chrome の詳細設定で「ハードウェアアクセラレーションを使用可能な場合は使用する」を OFF にする運用が不要となり、運用コスト削減に繋がった。
- 再生している時刻の動画フレームのタイムスタンプの表示が可能になった。
- 再生している動画のコーデック、解像度、フレームレートなどの情報の表示が可能になった。
課題は解決できた?
WebCodecs の VideoDecoder を使用することで、改善前に発生していた課題が全て解決できました!
VideoDecoder で利用した機能は以下2つです。
- 動画フレームをデコードするときに指定したタイムスタンプは、デコードされた動画フレームに引き継がれる。
- VideoDecoder でデコードする事前準備でハードウェアアクセラレーションを使用しない設定を指定することができる。
以下、Video Decoder を使用したサンプルコードです。
const videoDecoder = new VideoDecoder({ ... output: (videoFrame) => { // 1... decodeFrame で指定した timestamp 123456789 が参照可能。 console.log(videoFrame.timestamp) }, }) videoDecoder.configure({ ... // 2... ハードウェアアクセラレーションを使用しない設定をする。 hardwareAcceleration: 'prefer-software', }) videoDecoder.decode(new EncodedVideoChunk({ type: 'key', // 1... 動画フレームに紐づくタイムスタンプを指定する。 timestamp: 123456789, data: encodedVideoFrameData, }))
では、各課題の解決方法について見ていきましょう。
課題1 - LIVE 動画再生時の動画フレームのタイムスタンプの判別
WebCodecs の VIdeoDecoder でデコードする前の動画フレームのタイムスタンプは、デコードされた動画フレームでも利用ができます。
この機能を利用して、intdash を使用して計測する H.264 動画フレームのタイムスタンプをそのまま使用することで、再生時刻のタイムスタンプを表示することが可能になりました。
課題2 - LIVE 動画再生時のGoogle Chrome で動作する H.264 デコーダーの遅延
H.264 デコーダーの遅延については、改善前の状態でも「Google Chrome の詳細設定でハードウェアアクセラレーションを使用可能な場合は使用する」を OFF に変更することで暫定対策は実現できていました。
また、
VideoDecoder でデコードする事前準備でハードウェアアクセラレーションを使用しない設定を指定することができる。
から、Google Chrome の詳細設定を変更することなく Video Player パーツで「ハードウェアアクセラレーションを使用しない」ように自動で設定することが可能となり、暫定対策は廃止、及び運用コストの削減することができました。
課題3 - LIVE 動画再生時の動画フレームの受信が詰まったときの再生位置のケア
改善前は、Fragmented MP4 と、Media Source Extension API を使用した動画再生で、最新の動画フレームの再生位置を表示するケアが必要でした。
WebCodecs の VideoDecoder を利用することによって、デコードされた最新の動画フレームが更新される毎に、デコードされた動画フレームを Video Player パーツで表示するだけのシンプルな構成に改善することができました。
課題4 - 過去動画再生時の HLS 動画のセグメント内の動画フレームのタイムスタンプの補正
課題1の改善と同様に、H.264 の動画フレームのタイムスタンプをデコードされた動画フレームでも利用可能になりました。
その結果、再生したい時刻とデコードされた動画フレームのタイムスタンプを使用して忠実に再生できるようになりました。
苦労の軌跡
さて、上記のとおり、複数の課題が解決できて喜ばしい限りなのですが、実現するためには何度も壁にぶち当たり、一つずつ解消していきました。
以降は、WebCodecs の VideoDecoder を使用する上で解決していった苦労の軌跡について紹介したいと思います。
1フレームのデコード遅延が発生する
15fps など 秒間に複数の動画フレームの再生では気づかなかったのですが、1fps の動画フレームでテストしているときに、動画の再生遅延が1秒遅れの症状になっていることに気づきました。
調査を進めた結果、WebCodecs の VideoDecoder で 動画フレームをデコードするときに、動画フレームが 1フレーム遅れでデコードされていることがわかりました。
当症状については VideoDecoderConfig で、下記 optimizeForLowLatency
を有効にすることで解決できました。
videoDecoder.configure({ ... optimizeForLowLatency: true, }
パフォーマンス低下時に、動画が緑色に塗りつぶされて表示される
Windows 環境でテストしていると、デコードされた動画フレームの画像が緑色で塗りつぶされるケースが発生しました。
調査を進めた結果、デコードされた動画フレーム VideoFrame を OffscreenCanvas の drawImage で表示している箇所に原因があるとわかりました。
Visual M2M Data Visualizer のダッシュボードで Video Player パーツを複数表示、またはその他のビジュアルパーツを同時に表示したときなど、Google Chrome のパフォーマンスが低下した際に当症状が発生しやすい状況になっていました。
対策として、描画方法を OffscreenCanvas から MediaStreamTrackGenerator で使用可能な WritableStream.getWriter に置き換えることで当症状を解決しました。
LIVE 動画の再生時に、ネットワーク通信状況が不安定になるとパフォーマンスが低下する
LIVE 動画再生時にネットワークの通信速度の低下から復帰した際に、詰まっていた H.264 動画フレームを実再生時間より短い間隔で一度に受信するケースがあります。
その場合、デコードするための H.264 動画フレームのキューが想定以上に増加する可能性があります。
全て1フレームずつ逐次動画デコードする場合、必要以上にデコード処理コストの負荷が上昇し、パフォーマンスの低下、動画の再生遅延に影響します。
対策として、デコードする前の動画フレームのキューでキーフレームが含まれている場合は、キューに含む最新のキーフレームより古い動画フレームを破棄することで、デコード処理コストの削減を実現しました。
過去動画の再生時に、デコードされた動画フレームが使用するメモリで圧迫する
LIVE 動画の再生のときは、最新の1フレームの動画のみ表示すればよかったので、過去の動画フレームは破棄する運用で問題ありませんでした。
過去動画の再生では、時系列データを意識して複数のタイムスタンプに相当する動画フレームを管理、表示する必要があります。
そこで、最も気をつける必要がある対象が、デコードされた動画フレームのメモリ管理になります。メモリの使用量が増加すると、Google Chrome のパフォーマンスの低下、またはブラウザのタブがクラッシュするなどの症状が発生します。
デコードされた動画フレームの ByteSize は、表示する動画の解像度に比例します。
以上から、次のポイントを気をつけるようにしました。
- 一度にデコードする動画フレームの流量を制限し、デコードされた動画フレームの数は必要最小限に抑える。
- 再生している間は、動画フレームを差分で順番にデコードし、デコードされた動画を順次に表示する。
- 再生時刻を巻き戻すなどシーク位置を変更した場合は、移動後の時刻を基準に直前のキーフレームから動画フレームをデコードをやり直す。
- デコード実施前の動画フレームは、現在の再生時刻を含む1分未満の範囲のみメモリにキャッシュし、それ以外は都度 intdash Core Services から取得し直す。
構成は下記のようになりました。
結果、動画再生で必要なメモリ使用量を 250MB 以内に抑えることができ、パフォーマンスが低下することなく再生できるようになりました。
おわりに
WebCodecs の VideoDecoder を使用して、Video Player パーツ の課題を改善し、より使いやすい機能としてアップデートできたと実感しています。
また、メジャーバージョン級の開発内容の更新で良い開発経験ができ達成感を感じることができました。
Visual M2M Data Visualizer は開発をスタートしてからの期間が長い製品ではありますが、今回のように Web の新しい技術も取り込みつつ改善ができる領域は多く、これからも積極的に改善を推進できればと考えています。
上記も含めて弊社製品、またはフロントエンドに興味を持って頂けた方は是非、こちらの弊社採用ページもご覧ください。
製品に関するお問い合わせはこちらへ!
*1: HLS 形式コンテナに含む各セグメントの先頭のフレームのタイムスタンプは H.264 の動画フレームと一致するため、正確なタイムスタンプを意識しなければ気になることはありません。