适用平台
Windows Mobile 5.0
Windows CE 5.0
开发工具
Microsoft Visual Stuido 2005
摘要
通过本文可以了解如何在.NET CF 2.0中使用COM调用来使用DirectShow对象来实现简单的视音频播放。
正文
.NET CF 1.0中对COM对象的调用本人知道有三种:第一种:使用Odyssey公司的CFCOM控件来调用COM对象。缺点是需要交纳一定费用。第二种:使用本地代码来实现对COM对象操作的逻辑功能并封装成各个函数,并通过P/Invoke来在托管代码中调用这些功能块函数。此中方法比较简单,但是在托管代码中使用不太自由,不能访问到COM对象和接口。第三种:也是使用本地代码做个包装,将COM对象和其接口还有方法均分别曝光给调用者,同时在托管代码中使用P/Invoke来做一一对应。这种方法代码量巨大,很繁琐。但在托管代码中使用方便灵活,例如微软公司的曾经的Poom Sample例子。
由于.NET CF 2.O 对COM调用的支持,使得我们很容易操纵COM对象。
我们以下面一个例子开头来具体介绍。
本例子在托管代码中通过调用DirectShow来播放简单的视音频格式的文件。
1.新建一个Windows mobile 5.0的DeviceApplication项目,项目名称改为DirectShow,如下图所示
2.使用窗口编辑器修改Form1如下图所示
所作改动为Form1的背景色改为黑色,MinimizeBox设置为false方便调试,添加一个菜单名为Play。
3.给工程添加一个代码文件,命名为DSInterface.cs。在这个空白代码文件中复制如下代码进去,这些代码的作用是声明我们用到的DirectShow的一些接口。
using System;
using System.Runtime.InteropServices;
namespace DirectShow
{
[ComVisible(false)]
public enum PinDirection // PIN_DIRECTION
{
Input, // PINDIR_INPUT
Output // PINDIR_OUTPUT
}
public enum DsEvCode
{

}
[StructLayout(LayoutKind.Sequential, Ch***t = Ch***t.Unicode), ComVisible(false)]
public class FilterInfo // FILTER_INFO
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string achName;
[MarshalAs(UnmanagedType.IUnknown)]
public object pUnk;
}
[StructLayout(LayoutKind.Sequential), ComVisible(false)]
public class AMMediaType // AM_MEDIA_TYPE
{
public Guid majorType;
public Guid subType;
[MarshalAs(UnmanagedType.Bool)]
public bool fixedSizeSamples;
[MarshalAs(UnmanagedType.Bool)]
public bool temporalCompression;
public int sampleSize;
public Guid formatType;
public IntPtr unkPtr;
public uint cbFormat;
public IntPtr formatPtr;
}
[ComVisible(true), ComImport,
Guid("56a86891-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPin
{

}
[ComVisible(true), ComImport,
Guid("56a86897-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IReferenceClock
{
[PreserveSig]
int GetTime(out long pTime);
[PreserveSig]
int AdviseTime(long baseTime, long streamTime, IntPtr hEvent, out int pdwAdviseCookie);
[PreserveSig]
int AdvisePeriodic(long startTime, long periodTime, IntPtr hSemaphore, out int pdwAdviseCookie);
[PreserveSig]
int Unadvise(int dwAdviseCookie);
}
[ComVisible(true), ComImport,
Guid("56a8689f-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFilterGraph
{

}
[ComVisible(true), ComImport,
Guid("56a86892-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IEnumPins
{
[PreserveSig]
int Next(
[In] int cPins,
[Out, MarshalAs(UnmanagedType.LPArray)] out IPin[] ppPins,
[Out] out int pcFetched);
[PreserveSig]
int Skip([In] int cPins);
void Reset();
void Clone([Out] out IEnumPins ppEnum);
}
[ComVisible(true), ComImport,
Guid("56a86895-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IBaseFilter
{

}
[ComVisible(true), ComImport,
Guid("56a868a9-0ad4-11ce-b03a-0020af0ba770"),//56a868a9-0ad4-11ce-b03a-0020af0ba770
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IGraphBuilder
{

}
// ---------------------------------------------------------------------------------------
/// <summary>
/// The IFilterGraph::EnumFilters method returns the enumerator interface. It is based on the COM IEnum style of enumerators.
/// </summary>
[ComVisible(true), ComImport,
Guid("56a86893-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IEnumFilters
{
[PreserveSig]
int Next(
[In] int cFilters,
[Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] out IBaseFilter[] ppFilter,
[Out] out int pcFetched);
[PreserveSig]
int Skip([In] int cFilters);
void Reset();
void Clone([Out] out IEnumFilters ppEnum);
}
[ComVisible(true), ComImport,
Guid("56a868c0-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMediaEventEx
{

}
[ComVisible(true), ComImport,
Guid("56a868b5-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IBasicVideo
{

}
[ComVisible(true), ComImport,
Guid("56a868b4-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IVideoWindow
{

}
[ComVisible(true), ComImport,
Guid("56a868b3-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IBasicAudio
{
[PreserveSig]
int put_Volume(int lVolume);
[PreserveSig]
int get_Volume(out int plVolume);
[PreserveSig]
int put_Balance(int lBalance);
[PreserveSig]
int get_Balance(out int plBalance);
}
[ComVisible(true), ComImport,
Guid("56a868b1-0ad4-11ce-b03a-0020af0ba770"),
InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMediaControl
{

}
}
4.在Form1的构造函数之前添加如下变量
Guid FilterGraph = new Guid("e436ebb3-524f-11ce-9f53-0020af0ba770");
object comObject = null;
//要播放的文件路径
string clipFile = @"/clock.avi";
IGraphBuilder graphBuilder;
private IVideoWindow videoWin;
private IBasicVideo basicVideo;
private IBasicAudio basicAudio;
private IMediaControl mediaCtl;
private IMediaEventEx mediaEvt;
private const int WM_GRAPHNOTIFY = 0x00008001;
private const int WS_CHILD = 0x40000000;
private const int WS_CLIPCHILDREN = 0x02000000;
private const int WS_CLIPSIBLINGS = 0x04000000;
5.在窗口编辑器中双击Play菜单,在生成的事件处理方法体中添加如下代码
private void menuItem1_Click(object sender, EventArgs e)
{
try
{
//获得DirectShow的接口
GetInterface();
//使用GraphBuilder 的智能链接来自动播放音视频文件。
int hr = graphBuilder.RenderFile(clipFile, null);
//每一次调用Com方法要检查返回的int类型的数据,如果小于0,一般意味着有错误发生
//并通过放回值可以知道何种错误。
CHK(hr);
hr = mediaEvt.SetNotifyWindow(this.Handle, WM_GRAPHNOTIFY, IntPtr.Zero);
CHK(hr);
hr = videoWin.put_Owner(this.Handle);
CHK(hr);
//设置视频播放窗口类型
hr = videoWin.put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
CHK(hr);
Rectangle rc = this.ClientRectangle;
//设置视频播放窗口大小位置
hr = videoWin.SetWindowPosition(0, 0, rc.Right, rc.Bottom);
CHK(hr);
//通过IMediaControl接口的Run方法来使得整个Filter Graph运行从而播放文件
hr = mediaCtl.Run();
CHK(hr);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
同时在Form1.cs文件的最上面添加这段代码
using System.Runtime.InteropServices;
6.添加如下方法
private void CHK(int hr)
{
if (hr < 0)
{
Marshal.ThrowExceptionForHR(hr);
}
}
private void GetInterface()
{
try
{
comObject = CreateComObj(FilterGraph);
graphBuilder = (IGraphBuilder)comObject;
comObject = null;
//通过获得的IGraphBuilder接口来获得其他接口
mediaCtl = (IMediaControl)graphBuilder;
mediaEvt = (IMediaEventEx)graphBuilder;
videoWin = graphBuilder as IVideoWindow;
basicVideo = graphBuilder as IBasicVideo;
basicAudio = graphBuilder as IBasicAudio;
}
finally
{
if (comObject != null)
Marshal.ReleaseComObject(comObject);
comObject = null;
}
}
private object CreateComObj(Guid ClsID)
{
object com = null;
try
{
//通过Com对象的Guid来生成Com对象
Type clsType = Type.GetTypeFromCLSID(ClsID);
com = Activator.CreateInstance(clsType);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return com;
}
7.代码完成,这时候使用VS2005自带的Remote File Viewer上传视频文件到模拟器或者实机的根文件夹下面,这个视频文件请务必使用桌面机器上面Windows目录夹里面的Clock.avi文件。当上传完毕后,运行程序。程序运行效果如下图所示(此时没有声音播出,原因稍后再讲)
我们现在关闭程序并上传一段wav音频文件到设备中,并修改
string clipFile = @"/clock.avi"指向你上传的wav文件路径,此时再点击Play菜单即可播放出音频文件。如果我们想上传一个Wmv格式的文件并用此程序来播放的话,应用程序总是提示一串错误表示无法播放。这其中的原因是,我们使用DirectShow的智能链接技术来播放视音频,此时GrahpManager会通过要播放的文件格式来选择相应的编解码器(Filter),对于大部分的Windows Mobile 5.0设备来说都没有WMV 解码器,所以自然而然不能播放此种文件。我们检查Clock文件,它的视频格式为MS-RLE,音频格式为DSP group TrueSpeech(TM).我们也可以从Platform Builder中可以推出
由于RLE Video Codec的footprint很小,所以在大部分的设备中都支持此中codec,而由于
WMV的footprint'较大,所以可能在mobile 5.0的设备中很少有支持的。而刚才Clock.avi文件没有播放出声音,也可以推测是没有相关支持其解码的DirectShow Filter的存在而导致。我们也可以用同样的
代码在桌面程序中进行运行将会发现能播放的文件种类将会大大增加。
读者可能会有疑问,到底我的设备中有什么filter,没有什么filter呢,一个简单的方法是使用 VS2005自带的Remote Registry Editor连接到设备中,展开HKEY_CLASSES_ROOT,找到CLSID并单击,然后使用菜单栏中的Registry.Export Registry到桌面机器上面,最后用记事本打开,使用查找功能搜索相关字符串来进行确认。例如我想知道Decoder有关的filter信息,用关键字Decoder进行搜索,会发现有
[HKEY_CLASSES_ROOT/CLSID/{86A495AC-64CE-42DE-A13A-321ACC0F02DB}]
@="MPEG-1 Layer 3 Decoder DMO"
"DMOGuid"="6b928210-84e7-4930-9b33-1eb6f02b526e"
"DMOCategory"="57f2db8b-e6bb-4513-9d43-dcd2a6593125"
"Merit"=dword:00800001
这个表示设备支持MP3 音频解码,但由于是Microsoft® DirectX® Media Objects (DMO). 所以
我们还需要一些额外工作才能进行mp3解码。
再搜索Encoder,会搜索到
[HKEY_CLASSES_ROOT/CLSID/{d23b90d0-144f-46bd-841d-59e4eb19dc59}]
@="WMVideo9 Encoder DMO"
"Merit"=dword:00800000
这个表示wmv编码DMO,我们可以使用这个DMO来对摄像头采集到的数据进行编码从而生成视频文件。
8.现在回顾这个程序,可以发现除了DirectShow的接口的声明很麻烦以外,其他主程序的编写实际上很简单而且也跟本地代码操纵DirectShow很相似。至于DirectShow的接口,网上有一个很好的开源项目
DirectShow.NET 提供大部分的接口声明,虽然是针对桌面机,但是稍加改动就可以为我们所用。
附注:由于本人知识有限,并且时间仓促,难免有不妥之处,敬请指正。另外并不是所有的DirectShow接口进行过验证。附件中示例的源代码。