What can be done with DirectShow and C#
We would like to start by asking why there is no managed wrapper for DirectShow. The DirectShow SDK documentation contains a FAQ with this answer: "There are no current plans to implement a "Managed DirectShow" platform. You can use COM interop to create DirectShow client applications in managed code, but creating filters that depend on the Common Language Runtime (CLR) is not recommended for performance reasons."
Should we buy this answer like it would be "words from the Gospels"? I like the motto "trust but verify". One thing is sure, when we create a GraphBuilder object and run a stream through it, we end up creating a lot of threads and mixing native COM and managed code might not be an ideal mix for people who don‘t want to work more than what they have to get the job done.
So, we‘re not going to write filters in C#. But DirectShow client applications are an interesting possibility. The SDK already contains samples written in VB and there is no reason why we couldn‘t write similar applications in C#. So how do we go about it. There are a few samples at www.codeproject.com that use C# and DirectShow.
The easiest way to write a DirectShow client application is simply to wrap the library "quartz.dll" (with the tool tlbimp from the .Net SDK or by adding a reference to in a VS project). That‘s the easiest way but the methods we‘d like might not be exposed this way. So we have a couple of alternatives. There a library called "FSFWrap.dll" that adds some of the missing APIs or we can look at DShowNet (available on the CodeProject web site).
上面这段话的意思就是,DirectShow并不支持托管的代码,所以我们也不用托管的代码来实现filter,但是我们可以使用相应的DLL来写DirectShow的客户端程序。先来看一个C++的版本,首先编程环境要进行配置,前面的博客已经介绍过了。
Playing a file using DirectShow in C++ is illustrated in the SDK documentation as:
#include <dshow.h> void main(void) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // Initialize the COM library. HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { printf("ERROR - Could not initialize COM library"); return; } // Create the filter graph manager and query for interfaces. hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)) { printf("ERROR - Could not create the Filter Graph Manager."); return; } hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); // Build the graph. IMPORTANT: Change this string to a file on your system. hr = pGraph->RenderFile(L"C:\\Example.avi", NULL); if (SUCCEEDED(hr)) { // Run the graph. hr = pControl->Run(); if (SUCCEEDED(hr)) { // Wait for completion. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // Note: Do not use INFINITE in a real application, because it // can block indefinitely. } } pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); }
Here‘s is the equivalent code in C# using interop and quartz.dll:
using System; using QuartzTypeLib; class PlayFile { public static void Main( string [] args ) { FilgraphManagerClass graphClass = null; try { graphClass = new FilgraphManagerClass(); graphClass.RenderFile( args[0] ); graphClass.Run(); int evCode; graphClass.WaitForCompletion( -1, out evCode ); } catch( Exception ) {} finally { graphClass = null; } } }
利用前面说的csc编译器的使用,就可以播放args[0]参数代表的视频文件了。http://www.cnblogs.com/stemon/p/4562581.html
编译的命令:
csc /debug /t:exe /r:interop.quartztypelib.dll test.cs
这样就能生成对应的exe文件,然后在DOS下运行这个test.exe然后紧跟着视频文件的路径作为参数,就OK了。
There is also a window version of this program where this code is called from a button on a form (the code can be found in the following). We passed the file name to play on the command line instead of hardcoding the name in the source as the example from the SDK. Also the first argument to WaitForCompletion is -1 because INFINITE is defined as 0xfffffff.
The interop version uses the FilgraphManagerClass to do all the work.
.Net Master (at CodeProject) decided that the COM interfaces for DirectShow are sufficiantly simple to write wrappers for them using the IDL provided with the SDK. The advantage of this approach is that you have more control on which members of the interfaces that you can access. The disadvantages are the code can be rather ugly and you have to think in term of native COM and managed code at the same time. Here is a C# version using DShowNet from .Net Master at CodeProject:
using System; using System.Runtime.InteropServices; using DShowNET; class PlayFile { public static void Main( string [] args ) { IGraphBuilder graphBuilder = null; IMediaControl mediaCtrl = null; IMediaEvent mediaEvt = null; Type comtype = null; object comobj = null; try { comtype = Type.GetTypeFromCLSID( Clsid.FilterGraph ); comobj = Activator.CreateInstance( comtype ); graphBuilder = (IGraphBuilder) comobj; comobj = null; mediaCtrl = (IMediaControl) graphBuilder; mediaEvt = (IMediaEvent) graphBuilder; graphBuilder.RenderFile( args[0], null ); mediaCtrl.Run(); int evCode; mediaEvt.WaitForCompletion( -1, out evCode ); } catch(Exception) {} finally { if( comobj != null ) Marshal.ReleaseComObject( comobj ); comobj = null; mediaCtrl = null; mediaEvt = null; graphBuilder = null; } } }
The call to GetTypeFromCLSID of the Type class, the call to CreateInstance of the Activator class and ReleaseComOjbect of the Marshall class are examples of what I meant by having to keep track of native COM and managed code. If the DShowNet implementation give you access to what you need but not the plain Quartz interop. Then DShowNet is the way to go. At the time of writing this tutorial, there was a Beta version of an open source extension to the DShowNet library. The version 1.1 is now released, we‘ll show some uses of this new library in the following tutorials.
We‘ll stop here for our first look at DirectShow and C#.
the form code:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using QuartzTypeLib; namespace CsPlayer { /// <summary> /// Summary description for Form1. /// </summary> public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button button1; /// <summary> /// Required designer variable. /// </summary> private System.ComponentModel.Container components = null; public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button(); this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(16, 16); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(88, 32); this.button1.TabIndex = 0; this.button1.Text = "Play"; this.button1.Click += new System.EventHandler(this.button1_Click); // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(216, 86); this.Controls.AddRange(new System.Windows.Forms.Control[] { this.button1}); this.Name = "Form1"; this.Text = "Form1"; this.ResumeLayout(false); } #endregion /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.Run(new Form1()); } private void button1_Click(object sender, System.EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); if( ofd.ShowDialog() == DialogResult.OK ) { FilgraphManagerClass graphClass = null; try { graphClass = new FilgraphManagerClass(); graphClass.RenderFile( ofd.FileName ); graphClass.Run(); int evCode; graphClass.WaitForCompletion( -1, out evCode ); } catch( Exception ) {} finally { graphClass = null; } } } } }