DirectShowによるシームレスなビデオ分割と接着

当社の部門の1つは、自動車用のマルチメディアコンポーネントの手動テストに従事しています。 同時に、実行されたすべてのアクション(ボタンを押す、ディスクを挿入するなど)とシステムの反応が常に記録されます。カメラの1つがディスプレイに向けられます。 この場合のビデオは、エラーを観察した証拠であり、開発者に実行されたアクションとその速度に関する貴重な情報も提供します。 同意してください、その情報はバグ報告にとって非常に重要ですよね?



システムの特異性は、エラーが自発的かつ予期せずに発生する可能性があることです。テストケースの任意の段階で、または単にビデオ録画が実行されていないスタンバイモードでも発生します。 興味のある人を猫に招待します。ここでは、ビデオをシームレスに分割して接着するために開発したソリューションについて説明します。 彼のおかげで、記録は終日行われ、ビデオは便利な小さなファイルに保存されます。これにより、最もまれなエラーをキャッチして文書化することができ、同時に不可能なシステム反応を伴う多数のビデオで開発者を喜ばせます。



なぜビデオを壊すのですか?

実際、なぜですか? 8時間のファイルを自分用に保存してから、一部のビデオエディターで目的のビデオの一部をカットし、それで終わりです。 実際には、これは以前に起こりましたが、多くの欠点があります:長いファイルを掘るのは不便です、何らかの方法でカットした後、時間と品質を失ってビデオを再圧縮する必要があり、ディスクスペースはまだゴムではありません。 さらに、遅かれ早かれ、ファイルを操作できるように記録を停止する必要があります。ここでは、平均の法則に従って、最も有害で繰り返しのないエラーが常に発生します。 一般的に、評決-記録段階でカットする必要があります!



DirectShowは何を提供しますか?

実装には、DirectShowテクノロジーが選択されました。 要件の1つは、出力がWindows Mediaファイル、つまりWMV3である必要があることです 。そのため、最新のコンピューターでも簡単に圧縮できるため、オンザフライで圧縮することが決定されました。 主なアイデアは次のとおりです。フレームを失うことなく、着信オーディオおよびビデオストリームを別のファイルにいつでも切り替える機能が必要です。 したがって、たとえば2分間続くファイルに記録し、必要に応じてシームレスに接着することができます。



Windows Media形式の音声とプレビュー付きのビデオを記録するための最も一般的なフィルターグラフを作成しましょう。 次のようなものが得られます。





このグラフはどのように機能しますか? オーディオとビデオの2つの入力フィルターは、次のフィルターの入力ピンにサンプルを配信する異なるストリームによって提供されます。 Smart Teeフィルターを使用して、着信ビデオデータを複製し、1つのコピーをVideo Rendererの画面に送信し、2つ目はWM ASF Writerフィルターに移動します。このフィルターは、オーディオとビデオを実際に同期し、それらを圧縮してファイルに書き込みます。



出力ファイルの名前を変更することでグラフ内で交換可能に使用できる2つのフィルターを備えた「直接的な」ソリューションは機能しません。





DirectShowグラフには1つの機能があります。停止するまで、すべての「出力」フィルターはファイルを開いたままにし、ファイナライズしません。 また、グラフを停止しないと、出力ファイルの名前を変更したり、フィルターを接続/切断することはできません。 しかし、グラフの停止と開始には、数フレームまたは数秒の損失が伴います。 標準的な手段ではできないことは明らかです。



自己グラフ

1つの解決策は、 キャプチャグラフを レコードグラフから独立させ、後者を停止してファイルをファイナライズすることです。 これは、たとえば、DirectShow- Geraint Daviesの作成者からのGMFBridgeを使用して可能です。 システム全体のおおよそのスキームは次のようになります。





GMFBridgeは3つのグラフすべてに同時に配置されているので、1つのサンプルを失うことなく、最初と2番目のレコードグラフ間でサンプルストリームを切り替えることができます。 記録グラフの1つがビデオを圧縮している間、2つ目のグラフ(出力ファイルの名前)を設定しています。これは、他のグラフに影響を与えることなく停止できるためです。 適切なタイミングで、2番目のグラフを開始し、 GMFBridgeを切り替えて最初のグラフを停止します。 出来上がり!



しかし、このようなソリューションには明らかな欠点があります。 まず、リソース。 記録グラフのコピーが2つ必要です。これは、全体的なパフォーマンスとメモリ使用量に悪影響を及ぼします。 さらに、ビデオとオーディオの両方が存在する場合、それらを同期することは非常に困難です-各グラフには独自のカウントダウンがあり、さらにサンプル自体は異なるストリームで配信されます。 これはすべて、グラフの切り替え時にGMFBridge自体の自発的なフリーズにつながったため、この決定を拒否することにしました。 もちろん、ツールのソースコードは公開されており、必要に応じて、その不安定な動作の理由を理解できますが、それでもリソースを節約したいという欲求は重く、私は反対側からタスクにアプローチすることにしました。



ASFライターを書く

好みと遊女で 。 そう! グラフを停止することなく別のファイルに切り替えるようにコマンドすることができるWM ASFライターが必要です。 次に、最初の最も単純なグラフを取得し、標準のWM ASFライターの代わりにカスタムフィルターを挿入して、生活を楽しみます。

別の新しいStreamToFileを標準のメソッドに追加して、ファイルを切り替えるためのフィルターを作成しましょう。

class CCustomASFWriter : public CBaseFilter { public: STDMETHOD(StreamToFile)(BSTR szFileName); }
      
      





コード例について
以下のコード例はすべて、わかりやすくするために非常に単純化されています。たとえば、エラー処理は完全に破棄され、さまざまな追加チェックは削除されます。


切り替え時にサンプルが失われないようにするため、およびサンプル配信のフローをブロックしないようにするために、着信データ用のマルチスレッドキューをフィルターに追加します。 私はこのような実装を使用し、 複数のプロデューサー-シングルコンシューマモードで使用するために少し仕上げました。 ビデオとオーディオの両方にキューを使用することにしました。それが理由です。 すべては、新しいファイル切り替え機能に由来します。 このため、原則として、ビデオサンプルの配信頻度はオーディオの配信頻度よりもはるかに高いことを覚えておくことが重要です。たとえば、オーディオの場合は30 Hzビデオ、2 Hz(サンプルあたり500 ms)です。 したがって、切り替えはオーディオサンプルの配信直後に行う必要があります。 キュー内のサンプルの自然な順序を観察すると、まさにそれを非常に便利に行うことができます。



これにより、 StreamToFileメソッドは、次のオーディオサンプルの直後に現在のファイルを閉じて、新しいファイルで録音を開始することをフィルターに通知するだけです。 新しいファイルが準備されている間、着信サンプルはすべてキューに保存されます。

 HRESULT STDMETHODCALLTYPE CCustomASFWriter::StreamToFile(BSTR szFileName) { wcscpy_s(m_szCurrentFile, szFileName); { CAutoLock lock(m_pLock); m_bSwitchRequested = TRUE; } return S_OK; }
      
      





実際、圧縮およびファイルへの書き込み自体は、 Windows Media Format SDK 、つまりIWMWriterインターフェイスを使用して行われます。

  IWMWriter *pWriter = NULL; WMCreateWriter(NULL, &pWriter);
      
      





これを行うには、別のストリームで、着信サンプルの処理サイクルを回転させます。

  while (bRunning) { StreamSamplesToWriter(pWriter); DWORD dwWaitResult = WaitForSingleObject(hEventStopStreaming, 33); if (dwWaitResult == WAIT_OBJECT_0) { m_pPinVideo->StopQueuingNow(); m_pPinAudio->StopQueuingNow(); bRunning = FALSE; } }
      
      





最も興味深いことは、 StreamSamplesToWriterメソッドで発生します。 ここで、サンプルがIWMWriterに送信され、 StreamToFileメソッドを使用して切り替える信号が与えられた場合、ファイルは適切なタイミングで切り替えられます。

 STDMETHODIMP CCustomASFWriter::StreamSamplesToWriter(IWMWriter *pWriter) { BOOL bMustSwitch = FALSE; void *pObject = NULL; while (m_pSamplesQueue->Pop(pObject)) { CQueuedSample *pSample = (CQueuedSample*)pObject; DWORD inputNumber = pSample->MediaType == MEDIATYPE_Video ? m_pPinVideo->InputNumber : m_pPinAudio->InputNumber; INSSBuffer *pBuffer = NULL; pWriter->AllocateSample(pSample->DataSize, &pBuffer); LPBYTE pbDestBuffer = NULL; pBuffer->GetBuffer(&pbDestBuffer); CopyMemory(pbDestBuffer, pSample->Data, pSample->DataSize); pWriter->WriteSample(inputNumber, pSample->Start, pSample->IsDiscontinuity | pSample->IsSyncPoint, pBuffer); pBuffer->Release(); if (inputNumber == m_pPinAudio->InputNumber) { { CAutoLock lock(m_pLock); bMustSwitch = m_bSwitchRequested; if (m_bSwitchRequested) m_bSwitchRequested = FALSE; } if (bMustSwitch) { pWriter->EndWriting(); pWriter->SetOutputFilename(m_szCurrentFile); pWriter->BeginWriting(); } } delete pSample; } }
      
      





だから、結果を達成することができました! StreamToFileメソッドを任意の時点でプルすると、1つのフレームを失うことなく、新しいファイルが取得されます。



カットのり

さて、2分間ファイルの束を取得しました。 しかし、4分間のビデオが必要で、あるファイルから別のファイルに切り替えるときに最も興味深い場所が正確に観察されるとしたらどうでしょうか。 重要ではありません-これらのファイルを非常に簡単に1つに接着し、再エンコードせずにそれを行うことができます! この場合、録音中に単一のフレームが失われることはないため、接着は本当にシームレスになります。



これには、 IWMSyncReaderIWMWriterAdvancedを使用します。

  IWMWriter *pWriter = NULL; IWMWriterAdvanced *pWriterA = NULL; WMCreateWriter(NULL, &pWriter); pWriter->QueryInterface(IID_IWMWriterAdvanced, (void**)&pWriterA; IWMSyncReader *pReader = NULL; WMCreateSyncReader(NULL, 0, &pReader); for (element = m_oMergeFileList.begin(); element < m_oMergeFileList.end(); element ++) { pReader->Open(element->FileName); IWMProfile *pProfile = NULL; pReader->QueryInterface(IID_IWMProfile, (void**)&pProfile); //   ,      ,      " " for (WORD i = 0; i < dwStreamCount; i++) { pProfile->GetStream(i, &pStream); pStream->GetStreamNumber(&wStreamNumber); pReader->SetReadStreamSamples(wStreamNumber, TRUE); } HRESULT hr = S_OK; while (SUCCEEDED(hr)) { hr = pReader->GetNextSample(0, &pSample, &cnsSampleTime, &cnsDuration, &dwFlags, &dwOutputNum, &wStreamNum); pWriterA->WriteStreamSample(wStreamNum, qwSampleTimeToWrite, 0, cnsDuration, dwFlags, pSample); } }
      
      





その結果、非常に迅速に(ミリ秒!)4分の長さのファイルが得られます。これは、検出が不可能な接着の場所です。 厳密に言えば、これは完全に真実ではなく、まれで特定の条件下では、接着がまだ完璧に機能しません(たとえば、カメラで設定された非常に低いフレームレートで)。 ただし、通常の状況では、この場所を表示することは実際には感知できません。



私もカットして接着したい!

私はそこで止まらず、先に進むことにしました。 マジックボタンが追加され、最後の1分間でビデオファイルを即座に受信できるようになりました(実際、必要な期間はカスタマイズ可能です)。 この機能はテスターに​​非常に人気があることが判明しました-予期しないエラーが発生し、ボタンを突き、ビデオを受信しました。



より明確になるように状況を説明します。 次のファイルに自動的に切り替えられた直後に、ボタンが押されたとします。



魔法のボタンをクリックすると、再び新しいファイルに切り替わり、 ファイル2が使用可能になります。 しかし、今でもファイル1をカットしてから、 ファイル2で接着する必要があります



ここでも複雑なことはありません。 私はすでに接着について書いており、切断は同じ方法で行われます:圧縮されたパケットは解凍せずに読み取られ、すべての不要なパケットはスキップされ、ある時点から開始して、すべての読み取り可能なパケットはタイムスタンプが調整されたファイルに書き込まれるため、最初のパケットは00:00:00になります。 ここで、新しいファイルの最初のパケットに、予測されたPフレーム(デルタフレーム)ではなく、 参照フレーム (キーまたはIフレーム)が含まれるように、適切なカットモーメントを選択する必要もあります。 参照フレームは、画像を少し変更するだけで30分ごとにWMVファイルに配置できるため、強制参照フレームの使用を構成する必要がありました。 スライス時の位置決め精度とファイルサイズの妥協点として、1秒の2つの参照フレーム間の最大継続時間を選択しました。



  IWMVideoMediaProps *pVMProps = NULL; pStreamConfig->QueryInterface(IID_IWMVideoMediaProps, (void**)&pVMProps); pVMProps->SetMaxKeyFrameSpacing(10000000i64);
      
      





やった! 魔法のボタンが機能します!



おわりに

結論として、私はDirectShowの複雑さをすべて自分で理解したと言いたいと思います-MSDN、インターネット上のコードサンプル、試行錯誤の方法。 しかし、これがまさに結果をとても楽しいものにした理由です。このアプリケーションは、ビデオを記録するために、また同時に複数のカメラからアクティブに使用されます。 エラーが検出されると、テスターはマジックボタンを喜んで突いて、2秒後にビデオ編集を一切必要とせずに3台のカメラから既製の同期ビデオファイルを受信します! そして、それはユーザーからの感謝ほど開発者を喜ばせませんよね?



All Articles