C# Windows Media Format SDK 翻译






4.78/5 (39投票s)
2004年4月23日
7分钟阅读

848073

3073
在本文中,我将介绍 WMF SDK 接口、数据结构、常量和函数的大部分内容翻译成 C#。
引言
在我之前的文章(Windows Media Audio Compressor)中,我向您展示了如何创建 Windows Media 压缩器。我使用了托管 C++ 来与 Windows Media Format (WMF) SDK 进行交互,该 SDK 只公开了非托管 COM 接口。虽然这可能是一个可接受的解决方案,但实现起来有点复杂,特别是当您需要比简单地与 WMF 对象、数据和函数交互更多功能时。在本文中,我将介绍 WMF SDK 接口、数据结构、常量和函数的大部分内容翻译成 C#。注意:此翻译不包含数字版权管理 (DRM) 支持。
虽然有些类可以在不深入了解 WMF SDK 的情况下使用,但本文附带的代码假设您已经熟悉 WMF SDK。此外,可能需要扎实的 COM 互操作和互操作封送知识才能使用一些翻译后的接口和结构。
关于翻译的一些说明
有人问我关于如何翻译 IWMSyncReader
接口以将其用于连接两个 WMA 文件。我告诉他,一个想法是创建一个 IDL 文件来发布所需的接口,并将其编译为类型库,然后使用 TLBIMP 或仅使用 VS.NET IDE 将其添加为引用。这种方法可能适用于使用许多 COM 相关对象和接口,但对于 WMF 而言,存在许多非自动化兼容的结构,以及使用 C 风格数组而不是 SAFEARRAY
等。
当您尝试导入此类类型库时,您会发现许多类型和函数在某些情况下不能代表托管定义的概念。某些类型和函数可能无用,或者需要过多的封送工作才能使用。在这种情况下,.NET Framework 文档建议在使用了 TLBIMP 之后,您应该使用 ILDASM 获取中间语言文件,然后对其进行修改和重新编译以获得所需的结果。然而,就 WMF SDK 而言,我们讨论的是超过 50 个接口。
我认为使用 WMF SDK 的最佳解决方案是在托管代码中声明所有需要的定义,而我正是这样做的。不过,还有另一种解决方案:等待微软发布其 SDK 的托管版本。
在此翻译中,我尽量以托管的视角来处理所有定义和函数原型,避免使用指针。然而,有时这是不可能的。例如,方法 IWMHeaderInfo3.GetAttributeIndices
的 IDL 定义如下:
...
interface IWMHeaderInfo3 : IWMHeaderInfo2
{
...
HRESULT GetAttributeIndices( [in] WORD wStreamNum,
[in] LPCWSTR pwszName,
[in] WORD *pwLangIndex,
[out, size_is( *pwCount )] WORD *pwIndices,
[in, out] WORD *pwCount );
...
};
我将其翻译如下:
...
interface IWMHeaderInfo3 : IWMHeaderInfo2
{
...
void GetAttributeIndices( [In] ushort wStreamNum,
[In, MarshalAs(UnmanagedType.LPWStr)] string pwszName,
IntPtr pwLangIndex,
[Out, MarshalAs(UnmanagedType.LPArray)]ushort[] pwIndices,
[In, Out] ref ushort pwCount );
...
};
在这里,将参数 pwLangIndex
翻译为 ref ushort pwLangIndex
可能是更好的选择。我使用了 IntPtr
,因为该参数(指针值)可以为 NULL
,而如果我们使用 ref
关键字,则无法传递 null 引用。
每当您想调用此方法并需要传递 null 值时,只需传递 IntPtr.Zero
;如果您需要值的引用,那么您必须使用 GCHandle.Alloc
和 GCHandle.AddressOfPinnedObject
的组合。如果您使用这些翻译后的接口,在许多情况下您需要进行手动封送,因此更明智的选择是编写包装类来封装这些“低级”工作。这就是我编写了一些包装 INSSBuffer
、IWMProfile
、IWMHeaderInfo
和 IWMStreamConfig
接口的帮助类。
关于翻译的另一个重要方面是,在 WMF SDK 中,错误是通过 HRESULT
处理的。在翻译版本中,每当出现错误时,都会抛出 COMException
。有时,我们需要知道返回的 HRESULT
的值。在这种情况下,您必须处理 COMException
并检查其 ErrorCode
属性。以下代码摘自 WmaStream 类的 Read
方法,演示了如何执行此操作:
try
{
m_Reader.GetNextSample(m_OuputStream, out sample, out SampleTime,
out Duration, out Flags, out m_OutputNumber,
out m_OuputStream);
}
catch (COMException e)
{
if (e.ErrorCode == WM.NS_E_NO_MORE_SAMPLES)
{
// No more samples, the stream end have been reached, there is not an error
// Code to handle the end of the stream.
}
else
{
throw (e); //Re-throw, an error that must be catch in an upper level.
}
}
注意:此翻译可能存在一些错误,我没有测试所有接口和函数。如果您计划将此库用于任何严肃的工作,您必须仔细检查每一段翻译过的代码,并与 WMF SDK 头文件和文档进行对照。
使用代码
我没有在下载文件中包含任何演示项目,而是在这里评论一些简单的代码使用示例。作为翻译的一部分,我包含了一个名为 WmaStream
的类,该类允许读取任何 ASF 文件的音频流并从中提取未压缩的 PCM 数据。作为补充,有一个名为 WmaWriter
的类,它允许您从 PCM 音频数据创建 Windows Media Audio 文件,该类与我在文章 Windows Media Audio Compressor 中描述的 WmaWriter
类非常相似,但这次是用 C# 实现的。您可以比较这两个版本,以了解在有 WMF SDK 的托管版本时实现有多么容易。
WmaStream 类
此类继承自 System.IO.Stream
,因此可以像任何其他只读流一样使用。以下代码展示了一种简单的方法,可以将 WMF 对象可以读取的任何文件(* .mp3、* .wma、* .mpe、* .asf、* .wmv 等)的音频内容复制到 WAV 文件:
using System;
using System.IO;
using Yeti.MMedia;
using Yeti.WMFSdk;
using WaveLib;
...
using (WmaStream str = new WmaStream("Somefile.wma"))
{
byte[] buffer = new byte[str.SampleSize*2];
AudioWriter writer = new WaveWriter(new FileStream("Somefile.wav",
FileMode.Create),
str.Format);
try
{
int read;
while ( (read = str.Read(buffer, 0, buffer.Length)) > 0)
{
writer.Write(buffer, 0, read);
}
}
finally
{
writer.Close();
}
} //str.Close() is automatically called by Dispose.
WaveWriter
是一个 BinaryWriter
,它通过其 Write
方法接收 PCM 数据来创建 WAV 文件(或流)。属性 WmaStream.Format
返回一个 WaveFormat
类,该类描述通过其 Read
方法读取的数据的 PCM 格式。此格式必须与 WAV 文件格式相同,因此将其作为参数传递给 WaveWriter
构造函数。
作为另一个示例,您可以下载 C# 底层音频播放器的文章源代码,由 Ianier Munoz 编写,并进行以下更改:
将此库的引用添加到 cswavplay 项目。在 MainForm.cs 文件中,找到:
...
[STAThread]
static void Main()
并将其更改为:
...
//MTA threading is needed is WMF object are planed to be used in
//multithreaded APPs
[MTAThread]
static void Main()
在同一文件中,找到 OpenFile()
方法并进行如下更改:
private void OpenFile()
{
//Add this line to open the files that can be processed by the
//WMF sync reader object
OpenDlg.Filter = "Windows Media Files (*.mpe,*.wma, *.asf, *.wmv, *.mp3)
|*.mpe; *.wma;*.asf;*.wmv;*.mp3|All files (*.*)|*.*";
if (OpenDlg.ShowDialog() == DialogResult.OK)
{
CloseFile();
try
{
//WaveLib.WaveStream S = new WaveLib.WaveStream(OpenDlg.FileName);
//Change the previous line by this one:
Yeti.WMFSdk.WmaStream S = new Yeti.WMFSdk.WmaStream(OpenDlg.FileName);
//The rest of the code remains the same
经过这几个简单的更改,您将拥有一个可以播放任何 ASF 文件和 MP3 文件的播放器。
WmaStream
目前的一个不足之处在于它不允许读取 ASF 文件中的压缩数据。这在复制流而不重新压缩、合并文件等时可能很有用。无论如何,扩展 WmaStream
以包含此功能很容易。
WmaWriter 类
有关此类更多详细信息,请参阅我的文章 Windows Media Audio Compressor。它具有几乎相同的接口,不同之处在于现在它使用真正的 WMF 接口实现,并且我添加了定义元数据的可能性,这些元数据将包含在最终的 ASF 流中。
以下代码演示了如何从 Windows Media Video (WMV) 文件中提取音频信息,并使用 WmaStream
和 WmaWriter
类将其写入 Windows Media Audio 文件。
using System;
using System.IO;
using Yeti.MMedia;
using Yeti.WMFSdk;
using WaveLib;
...
using (WmaStream str = new WmaStream("Somefile.wmv"))
{
byte[] buffer = new byte[str.SampleSize*2];
//Extract the WMF profile from the original file
WMProfile profile = new WMProfile(str.Profile);
while (profile.StreamCount > 1)//Remove any non-audio stream from the profile
{
WMStreamConfig config = new WMStreamConfig(profile.GetStream(0));
if (config.StreamType == MediaTypes.WMMEDIATYPE_Audio)
{
profile.RemoveStream(profile.GetStream(1));
}
else
{
profile.RemoveStream(config.StreamConfig);
}
}
System.Collections.ArrayList List = new System.Collections.ArrayList();
//Check if the original file had a title
string AttrValue = str[WM.g_wszWMTitle];
if (AttrValue != null)
{ //The title will be added to the destination file
WM_Attr Attr = new WM_Attr(WM.g_wszWMTitle,
WMT_ATTR_DATATYPE.WMT_TYPE_STRING,
string.Copy(AttrValue));
List.Add(Attr);
}
//Check by the author metadata
AttrValue = str[WM.g_wszWMAuthor];
if (AttrValue != null)
{ //The actor will be added to the destination file
WM_Attr Attr = new WM_Attr(WM.g_wszWMAuthor,
WMT_ATTR_DATATYPE.WMT_TYPE_STRING,
string.Copy(AttrValue));
List.Add(Attr);
}
AudioWriter writer = new WmaWriter(new FileStream("SomeFile.wma",
FileMode.Create),
str.Format,
profile.Profile,
(WM_Attr[])List.ToArray(typeof(WM_Attr)));
try
{
int read;
while ( (read = str.Read(buffer, 0, buffer.Length)) > 0)
{
writer.Write(buffer, 0, read);
}
}
finally
{
writer.Close();
}
}
在上面的代码中,我使用了帮助类 WMProfile
和 WMStreamConfig
。没有它们,我将不得不编写更多的代码。WM_Attr
结构是一个帮助结构,用于以更简单的方式定义元数据信息。将 WM_Attr
数组传递给 WmaWriter
构造函数,以定义将存在于结果文件中的元数据信息。
代码 str[WM.g_wszWMTitle]
封装了 IWMHeaderInfo.GetAttributeByName
的功能,并返回一个表示所需元数据属性的字符串,该属性的名称作为索引值(在本例中为常量 WM.g_wszWMTitle
)传递。上面的代码仅作为演示,执行相同操作的首选方法是使用 WMF 读取器和写入器对象的功能,它们分别允许您读取和写入压缩数据样本。
WMF 的使用可以有很多例子。在这里,我只展示了一些简单的案例。您可以通过查看帮助类的实现以及代码中包含的注释来找到更多信息。我没有注释任何 WMF 接口、结构、枚举和函数;您可以在 WMF SDK 文档中找到这些信息。
结论
这里有一个在托管世界中使用 WMF SDK 的简单解决方案(我已经完成了繁重的工作)。但是,您应该考虑两个主要方面:向前兼容性和性能。
关于兼容性,我不知道微软对 WMF SDK 的托管版本有什么计划,但我猜当他们提供一个时,它将与当前 WMF SDK 有很多不同。
另一个问题是性能:这不是 WMF SDK 的托管版本,而是翻译,因此每次使用任何 WMF 接口的方法时,都会涉及 COM 互操作操作。对于实时应用程序或处理视频内容的流,性能损失可能很严重。您需要自行决定此解决方案是否适合您的需求。