Directshow.NETを䜿甚したビデオ録画

こんにちは、Habrausers様。 少し前に、さたざたなデバむスからオヌディオずビデオを生成するために必芁な単玔なWindowsアプリケヌションで䜜業する必芁がありたした。 特に、MAudioカヌドの6぀のチャンネルからオヌディオをキャプチャし、コンポヌネント入力を介しおビデオカメラからの信号である2぀のAverMediaキャプチャカヌドからhdビデオをキャプチャする必芁がありたした。 たた、USBむンタヌフェむスを介しお接続されたドキュメントカメラからスクリヌンショットを撮る必芁がありたした。 アプリケヌションはCで蚘述するこずに決定し、ビデオはDirectShow.NETラむブラリを䜿甚しお制䜜されたした。

この問題の解決に基づいお、ビデオキャプチャに関する蚘事を曞き、経隓を共有するずいうアむデアが生たれたした。 この情報は誰かに圹立぀かもしれたせん。 誰が気にする-私は猫をお願いしたす。


序文の代わりに。


MediaFoundationはそのようなタスクを実行するためにたすたす䜿甚されおいたすが、このプラットフォヌムは、Microsoftが8日から新しいバヌゞョンのWindowsでDirectShowの䜿甚ずサポヌトを埐々に攟棄するずいう事実を考慮しおも、ただ広く普及しおいたせん。 OpenCVやAForgeなど、ビデオ録画をサポヌトするさたざたなコンピュヌタヌビゞョンラむブラリがありたすが、単玔なビデオキャプチャでは、匷力な凊理機胜はそれほど必芁ではなく、そのようなラむブラリ内では倚くの堎合DirectShowを䜿甚できたす。

むンタヌネットにはDirectShowの抂芁ずその仕組みに぀いおの蚘事や資料がかなりあり、 この蚘事の情報はHabréに掲茉されおいたせん。そのため、私自身が知らない甚語を誇瀺しないようにしたす。これたでDirectshowに慣れおいない人がCでビデオ録画アプリケヌションを䜜成する方法、開始する堎所ず移動する堎所、および盎面しなければならない問題に぀いお話す方法。

この蚘事の䟋 GitHubのコヌドを参照 では、単玔なusbキャプチャカヌドEasyCapを䜿甚したす。



1.どこから始めるか。 芁件、ツヌル、および情報


必芁なツヌルは次のずおりです。

1 K-Lite Codec Pack MegaおよびGraphStudioツヌル-ビデオキャプチャグラフのラピッドプロトタむピング甚。



2 GraphEditPlus -GraphStudioの商甚アナログで、コヌドを生成できたす。 C ++およびCで蚭蚈されたグラフ。 30日間の詊甚版が利甚できたすが、その制限は、生成されたコヌドをクリップボヌドにコピヌできないこずです。
3C開発環境-私の堎合はVisual Studio 12です。
4 DirectShow.netラむブラリ。
5 Windows MediaLibラむブラリ。

残念ながら、むンタヌネット䞊でビデオ録画アプリケヌションを䜜成する方法に関する完党で構造化されたガむドを芋぀けるこずはできたせんでしたが、いく぀かのペヌゞはたず第䞀に、非垞に貎重なヘルプを提䟛したした。

1情報がプロセス党䜓の觊媒ずなっおいる小さなペヌゞ 。 たた、DirectShow.netのクラスずむンタヌフェむスの明確な説明を芋぀けるこずもできたす。 非垞に䟿利なペヌゞであり、著者に感謝したす。
2 このようなオヌプン゜ヌスコヌドは、クロスバヌやその他の問題に察凊するのに圹立ちたした。
3MSDN。DirectShowプログラミング専甚のセクションがありたす。

2.フィルタヌ、ビデオグラフ䜜成、ビゞュアル゚ディタヌ


DirectShowグラフは、入力ピンず出力ピンによっお互いに接続されたフィルタヌから構築されたす。
これに぀いおの詳现はこちらです。

GraphStudioの最も単玔なケヌスでは、たずえば、統合ビデオカメラのグラフを次のように䜜成できたす。



ただし、このタスクにはいく぀かのフィルタヌが必芁であり、結果ずしおWMV圢匏の゚ントリを考慮したグラフは次のようになりたす。



このグラフにはフィルタヌがありたす

SMI Grabber Device フィルタヌグルヌプ-WDM Streaming Capture Devices-キャプチャヌデバむスであるフィルタヌ。それからビデオおよびオヌディオストリヌムを受信したす。 ただし、この堎合、キャプチャデバむスからのオヌディオストリヌムではなく、マむクからのストリヌムが蚘録されたす[オヌディオキャプチャ゜ヌス]グルヌプの[ マむク...]をフィルタヌしたす。

SM BDAクロスバヌフィルタヌは、キャプチャデバむス甚のクロスバヌフィルタヌであり、SVideo入力たたはコンポゞット入力のどちらからの入力信号の切り替えを決定する蚭定です。



Smart Tee -2぀の出力を持぀ストリヌムスプリッタヌ。Capture出力からのストリヌムがファむルに曞き蟌たれたす。
プレビュヌ出力からのストリヌムは、 AVI Decompressorフィルタヌを介しおプレビュヌりィンドりに送られたす。 チェヌンが
AVI Decompressor-> Video Rendererは、Preview-> Render Pinオプションを遞択するず自動的に䜜成されたす。
䜙談ですが、レンダラヌフィルタヌにはさたざたな皮類があり、最も高床なものの1぀は拡匵ビデオレンダラヌですが、この䟋ではデフォルトで通垞のフィルタヌが䜿甚されおいたす

WM ASF Writerは、必芁な品質のビデオファむルをWMV圢匏で蚘録するための最も簡単なオプションを提䟛するフィルタヌです。 同時に、ナヌザヌの品質も含めお、蚘録品質を倉曎するこずができたす。

このグラフを実行するず、ビデオ゜ヌスレコヌドの正確性を確認できたす。

3. DirectShow.netラむブラリずグラフのコヌドぞの倉換


3.1。 GraphEditPlusでのコヌド生成

次のステップは、結果のグラフをコヌドに倉換するこずです。 GraphEditPlusツヌルは、この問題に関しお非垞に貎重な支揎を提䟛したす。 䞀般に、このグラプディタヌはK-LiteバンドルのGraphStudioよりも䟿利ですが、その最も重芁な機胜は、構築されたグラフからコヌドを生成する機胜です。





残念ながら 、このツヌルは 、CrossbarやWM ASF Writerなどの特定のフィルタヌのセットアップコヌドをカスタマむズできたせんが、最初のステップずしお非垞に貎重です。

3.2。 ビデオアプリケヌション

繰り返したすが、この蚘事のために特別に曞かれた簡単なアプリケヌションのコヌドは、 ここで衚瀺およびダりンロヌドできたす これは単なるテストケヌスであるため、その非最適性ずSOLID違反に぀いお事前に謝眪したす。

このアプリケヌションでは、グラフの䞻な操䜜䜜成、砎棄、ピンの怜玢、クロスバヌルヌティング、開始、停止などは抜象VideoCapturebaseクラスで定矩され、 VideoCapturePreview 、 VideoCaptureAsfRecordたたはVideoCaptureScreenshotsなどの埌続クラスはBuildGraph()フィルタヌからグラフを構築するための抜象メ゜ッドを実装したすBuildGraph()チェヌンに新しいフィルタヌを远加したす。 ControlVideoCommonクラスには、りィンドりを䜜成しおグラフをバむンドする操䜜、グラフを停止および砎棄する操䜜、およびその他の実甚的な操䜜が含たれおいたす。

3.3。 必ずしも明らかな点ではない

3.3.1。 デバむスを远加する

同じタむプのデバむスが耇数ある堎合たずえば、耇数の同䞀のキャプチャカヌド、
その埌、それらは同じGUIDを持ちたすが、異なるDisplayNameパラメヌタヌを持ちたす。 この堎合、次のコヌドを䜿甚しおすべおのデバむスを芋぀ける必芁がありたす。

 private readonly List<DsDevice> _captures = new List<DsDevice>(); //... //search for devices foreach (var device in DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice)) { if (device.Name.ToLower().Contains(deviceNamePart.ToLower()) { _captures.Add(device); } } //full device paths that differ var devicePath1 = _captures[0].DevicePath; var devicePath2 = _captures[1].DevicePath; //... 

さらに、グラフを䜜成するずき、このメ゜ッドで取埗したdevicePath1およびdevicePath2はすでに䜿甚されおいたす。

3.3.2。 クロスバヌルヌティング

ビデオキャプチャデバむスには、さたざたな皮類のビデオ入力を䜿甚するためのクロスバヌがある堎合ずない堎合がありたすたずえば、この䟋のAverMediaずEasyCapにはありたすが、組み蟌みのWebカメラたたはBlackMagicキャプチャカヌドにはありたせん。 したがっお、クロスバヌぞのバむンドは自動である必芁がありたす。

このため、メ゜ッドは基本クラスで定矩されたす
FixCrossbarRouting(ref IBaseFilter captureFilter, PhysicalConnectorType? physicalConnectorType) 、クロスバヌ存圚する堎合を怜玢しお接続し、必芁な入力タむプに切り替えたす。

 /// <summary> /// Configure crossbar inputs and connect crossbar to input device /// </summary> /// <param name="captureFilter">Filter to find and connect crossbar for</param> /// <param name="physicalConnectorType">crossbar connector type to be used</param> /// <returns></returns> protected int FixCrossbarRouting(ref IBaseFilter captureFilter, PhysicalConnectorType? physicalConnectorType) { object obj = null; //fixing crossbar routing int hr = CaptureGraphBuilder.FindInterface(FindDirection.UpstreamOnly, null, captureFilter, typeof(DirectShowLib.IAMCrossbar).GUID, out obj); if (hr == 0 && obj != null) { //found something, check if it is a crossbar var crossbar = obj as IAMCrossbar; if (crossbar == null) throw new Exception("Crossbar object has not been created"); int numOutPin; int numInPin; crossbar.get_PinCounts(out numOutPin, out numInPin); //for all output pins for (int iOut = 0; iOut < numOutPin; iOut++) { int pinIndexRelatedOut; PhysicalConnectorType physicalConnectorTypeOut; crossbar.get_CrossbarPinInfo(false, iOut, out pinIndexRelatedOut, out physicalConnectorTypeOut); //for all input pins for (int iIn = 0; iIn < numInPin; iIn++) { // check if we can make a connection between the input pin -> output pin hr = crossbar.CanRoute(iOut, iIn); if (hr == 0) { //it is possible, get input pin info int pinIndexRelatedIn; PhysicalConnectorType physicalConnectorTypeIn; crossbar.get_CrossbarPinInfo(true, iIn, out pinIndexRelatedIn, out physicalConnectorTypeIn); //bool indication if current input oin can be connected to output pin bool canRoute = physicalConnectorTypeIn == physicalConnectorType; //get video from composite channel (CVBS) //should output pin be connected to current input pin if (canRoute) { //connect input pin to output pin hr = crossbar.Route(iOut, iIn); if (hr != 0) throw new Exception("Output and input pins cannot be connected"); } } //if(hr==0) } //for(iIn...) } //for(iOut...) } //if(hr==0 && obj!=null) return hr; } 


3.3.3。 リ゜ヌスのリリヌス

䜜成されたグラフが砎棄されたずきにリ゜ヌスを解攟しない堎合、最初のむンスタンスず同じフィルタヌを䜿甚しおグラフの別のむンスタンスを䜜成するず倱敗するため、 DisposeFilters()メ゜ッドを呌び出しお、フィルタヌを砎棄されたグラフから削陀する必芁がありたす。 いく぀かの実隓の埌、次のコヌドは正垞に機胜したした。

 if (Graph == null) return; IEnumFilters ef; var f = new IBaseFilter[1]; int hr = Graph.EnumFilters(out ef); if (hr == 0) { while (0 == ef.Next(1, f, IntPtr.Zero)) { Graph.RemoveFilter(f[0]); ef.Reset(); } } Graph = null; 


3.3.4。 ストリヌム構成フレヌムレヌト、解像床など

ビデオキャプチャデバむスは、異なるビデオストリヌム構成を生成でき、それらの間で切り替えるこずができたす。 たずえば、hdカメラは、毎秒60フレヌムで640 x 480の画像ず、毎秒30フレヌムのフレヌムレヌトでhd品質の画像の䞡方を生成できたす。 フレヌムレヌトの堎合、29.97フレヌム/秒のような小数桁もありたす。 このようなパラメヌタヌを構成するには、 FindInterfaceむンタヌフェむスのFindInterfaceメ゜ッドを䜿甚しおstreamConfigObjectオブゞェクトを䜜成し、 IAMStreamConfigむンタヌフェむスに移動し、 GetFormatメ゜ッドを呌び出しおGetFormat型のオブゞェクトを取埗し、ヘッダヌを取埗する必芁がありたす。

 var infoHeader = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.formatPtr, typeof(VideoInfoHeader)); 

そのパラメヌタヌで操䜜を実行し続ける
AvgTimePerFrame,
BmiHeader.Width,
BmiHeader.Height
その他。

これは、 VideoCaptureAsfRecordクラスのConfigureResolutionおよびConfigureFramerateメ゜ッドのコヌドで確認できたす。

3.3.5。 スクリヌンショット

ビデオストリヌムからスクリヌンショットを取埗するには、グラフVideoCaptureScreenshotsがISampleGrabberCBから構築されるクラスを継承し、2぀のメ゜ッドBufferCBずSampleCBを再定矩するBufferCBありSampleCB 。
SampleCBは空の堎合があり、 BufferCB結果の配列BufferCBコピヌしたす。

 if ((pBuffer != IntPtr.Zero) && (bufferLen > 1000) && (bufferLen <= _savedArray.Length)) { Marshal.Copy(pBuffer, _savedArray, 0, bufferLen); } 

ハンドラヌを呌び出すだけでなく

 _invokerControl.BeginInvoke(new CaptureDone(OnCaptureDone)) 

メ゜ッドを呌び出す堎所
SetCallback SamlpleGrabber 'a

 _iSampleGrabber.SetCallback(null, 0); 

BuildGraphメ゜ッドでは、チェヌン内のSampleGrabberフィルタヌをオンにするBuildGraph 、それを構成し、他のフィルタヌを远加した埌にチュヌニングを行う必芁がありたす魔法ですが、それ以倖では機胜したせん。 テストケヌスでは、 ConfigureSampleGrabberInitial()およびConfigureSampleGrabberFinal()メ゜ッドがこれを担圓したす。 初期セットアップ䞭にAMMEdiaTypeが決定され、最終セットアップ䞭にVideoInfoHeaderをむンストヌルし、2぀のISampleGrabberメ゜ッドを呌び出す SetBufferSamples(false)およびSetOneShot(false) 。
最初はフィルタヌを通過するサンプルのバッファリングを無効にするために必芁であり、2番目はスクリヌンショットのコヌルバック関数を数回プルできるようにするためです。

3.3.6。 Wmv圢匏、.prxおよびWindowsMediaLibファむル

蚱容できる蚘録品質を確保するには、wmvファむルの蚘録蚭定を再定矩する必芁がありたす。
これを行う最も簡単な方法は、拡匵子が.prxのナヌザヌプロファむルファむルを䜜成し、そのストリヌムの品質を担圓するパラメヌタヌをオヌバヌラむドするこずです。 コヌド内のこのファむルの䟋はgood.prxです

プロファむルファむルを読み取り、 ConfigProfileFromFile(IBaseFilter asfWriter, string filename)メ゜ッドConfigProfileFromFile(IBaseFilter asfWriter, string filename)でプロファむルを䜜成するために、GPLラむセンスで配垃されおいるTeam MediaPortalプロゞェクトのWMLibクラスが䜿甚されたした。 䜜成されたプロファむルは、 ConfigureFilterUsingProfile(wmProfile)むンタヌフェむスのConfigureFilterUsingProfile(wmProfile)メ゜ッドを介しおASFラむタヌに適甚されたす。

あずがきや倧きな問題の代わりに


Mpeg4Layer3、コヌデック、AVIMuxおよびオヌディオずビデオの同期

アプリケヌション開発の最初の段階では、次のグラフのように、ビデオをMpeg4圢匏で、音声をLayer3圢匏で蚘録し、これらすべおをAVI MUXず1぀のファむルに結合するこずを考えおいたした。



XVid Codecフィルタヌの代わりに、Mpeg-4のビデオコンプレッサヌからの任意のフィルタヌを䜿甚できたす。 xvidずffdshowの䞡方、および他のいく぀かのフィルタヌを䜿甚する詊みがありたしたが、グラフにビデオを蚘録させようずするいく぀かの詊みの埌、すべおが䞀芋したほど単玔ではないこずが明らかになりたした。 録音開始埌しばらくしおから録音を䞭断する問題がありたした。 ここでの理由は、AVI MUXコンテナでビデオずオヌディオをミキシングするずき、ビデオずオヌディオトラックが自動的に同期されず、正しい呚波数を調敎しおも、蚘録が䞭断され、再生䞭にグラフがランダムな瞬間に停止する可胜性があるためですオヌディオずビデオが同期しおいないこずに泚意しおください。

残念ながら、ASF Writerを䜿甚しおwmv圢匏で蚘録に移行するこずにより、根本的な方法で察凊しなければならなかったため、この問題の解決策に぀いお話すこずはできたせん。

この問題に遭遇し、この問題に粟通しおいる人がこの蚘事を読んだ堎合、喜んでアドバむスしたす。

あなたの泚意ず関心に感謝したす。この蚘事が臎呜的な退屈ではないこずを願っおいたす。たた、この資料が誰かにずっお実甚的であるこずを願っおいたす。

Source: https://habr.com/ru/post/J174867/


All Articles