×
Namespaces

Variants
Actions

Windows Phone上的声音录制与编码(四):使用Media Foundation编码音频

From Nokia Developer Wiki
Jump to: navigation, search
WP Metro Icon Multimedia.png
SignpostIcon WP7 70px.png
Article Metadata

代码示例
兼容于
文章
WS_YiLunLuo 在 08 Mar 2012 创建
最后由 hamishwillee 在 16 Jul 2013 编辑

Contents

简介

之前的几篇wiki介绍了如何在Windows Phone上录制声音,并且编码成wav格式,然后上传至服务器。Wav是一种很简单的格式,你只需要在Windows Phone录制的音频数据基础上加入一段头信息就可以了,这在包括Windows Phone在内的各种平台上都能轻松实现。

但是,大多数时候,声音文件都不是以wav格式存在的。这个世界上有很多很多种音频格式,人们常常听的mp3歌曲就是一个例子。常见的例子还有wma, aac (mp4),等等。这些格式往往都很复杂,不依靠现成的encoder(编码器),自己手工写算法来实现编码,是很痛苦的一件事。但是正像许多其它设备一样,Windows Phone只提供了解码器,没有提供编码器。想要编码,通常必须放到服务器上进行。这篇wiki就让大家看一看如何在Windows Server上使用Windows提供的现成的编码器进行编码。若是你使用非Windows平台的服务器,也可以自行寻找在对应平台上编码的方案。

本文的示例代码在这里:File:SimpleAudio4.zip

Media Foundation简介

Media Foundation是Windows内置的视频/音频编码API,它的功能非常强大,不仅仅可以帮助你将某种格式转换成另外一种格式,也可以让你动态创建视频/音频数据。例如,你可以将一幅静态的画作为视频数据源,对其像素进行操作,生成一些特效,加入一些动画,最终编码成为电影。你也可以对音频中的每一个sample进行修改,提高音量,修改频率(从而达成例如将女人的声音降低频率变成男人的声音这样的特效),等等。只要你了解相关算法,并且会使用Media Foundation提供的API,你就可以做很多很多通常只能购买昂贵的专业软件才能做到的事。使用Media Foundation,你唯一需要购买的就只有电脑硬件和Windows。

除了Media Foundation之外,Windows还提供了其它的视频/音频编码API,例如Direct Show,但是Media Foundation是最新的一套API,所以我们推荐大家在开发新程序时一律选择Media Foundation。事实上,在Windows 8中,一个Metro程序也可以使用Media Foundation(但是不能使用像Direct Show这样的老的API),这就意味着即使在平板电脑上也可以直接进行编码,而不一定需要将文件上传至服务器。今天选择Media Foundation,对于今后支持Windows 8是很有帮助的。如大家所知,若使使用.NET开发Windows 8 Metro应用程序,这个过程和开发Windows Phone程序是很像的。所以现在你用Windows Phone开发的前台程序可以轻松移植到Windows 8,而用C++和Media Foundation开发的服务器端程序也可以轻松移植到Windows 8作为一个自定义WinRT component直接被前台Metro程序调用。

本文不会介绍任何高级的算法,例如扩大音量,制作动画,等等。本文只是让大家看看如何使用Media Foundation的基本功能,对音频进行编码。想要使用高级功能,你可以在本文提供的示例基础上进行扩充。

前提条件

为了使用Media Foundation,你必须拥有Windows Vista,Windows Server 2008或更高版本。为了使用一些高级的API,你还需要Windows 7,Windows Server 2008 R2或更高版本。例如,本文介绍的sink writer就是Windows 7的新功能。在Windows 8中Media Foundation还会有更多的新功能。

此外,若是你使用的是server系统(通常我们推荐开发时使用Windows 7,但最终服务部署到Windows Server 2008 R2),你需要在server上安装Desktop Experience,否则Media Foundation的运行时默认是不会安装的。在client系统(例如Windows 7),这个运行时则是默认就自动安装好了。好在安装Desktop Experience是十分简单的一件事,因为它其实真的是Windows Server自带的一个组件,只是默认没有安装而已。

最后,有关硬件需求,若是你想要使用Media Foundation播放高清视频(也是Media Foundation的功能之一),你需要一块比较好的显卡。但是若是单纯想要进行编码,对显卡就没有任何要求了。不过推荐至少CPU要稍微好一点,毕竟编码是比较消耗计算资源的一个过程。Media Foundation内置了多线程处理机制,所以若是你有多个CPU的核,会很有帮助的。通常我们推荐把编码工作放在服务器上,因为服务器往往有较好的CPU。当然,就算CPU差一点,正常工作还是没有问题的,只是会比较花时间而已。

基本概念:Source和Sink

接下来我们简要介绍一下Media Foundation中的两个基本概念:source和sink。也许这两个名字稍微显得有点奇怪,不过大家可以简单把source想象成原始文件,而把sink想象成编码后的文件。但是请注意,事实上source和sink并不一定非要是文件,也可以使内存中的数据,网络数据,等等。而且往往source并不只有一个,编码的方式也有多种多样。

事实上关于source和sink的概念大家在生活中也有接触。例如,想象一个水斗,水龙头就是一个source,水从这个source流出。而落水管则是一个sink,最终水会流到那里去。但是source可以不止一个,例如,你也完全可以倒一杯水直接到水斗中。

若是你希望得到一个更技术一点的类比,想象一下一个简单的电场,其中包含一个正电荷和一个负电荷。我们可以把正电荷看成一个source,而把负电荷看成一个sink。如果你还记得高中物理,就会回想起通常画电场线时都是从正电荷(source)出发,到负电荷(sink)结束的。但是,这也并不意味着每一条电场线都必须起始于某个source。

Electric Field.png

如上图所示,对于一个sink而言,有些电场线起始于特定的source,也有一些不是。当然,大家知道,这是因为现实生活中有很多很多的电荷,所以对某个负电荷而言,它可以受到很多很多的正电荷共同形成的电场的影响。此外,每个电荷都会在其自身周围形成一个电场,当一个电荷从source出发到达sink,它可以走很多种不同的道路,在每条不同的道路上所受到的电场力也是不同的。尽管电场作为一个保守向量场,只要起点和终点相同,电场对该电荷所做的功就是相同的(因为保守向量场的曲线积分和路径无关),这一点和Media Founcation有所区别。

类似的,在Media Foundation中,为了创建一个sink,我们可以拥有一个或者多个source。而且从同一个source出发,有很多种方式可以到达sink,也就是说很很多种编码方式。不过请注意这里和电场有一点不同,那就是我们不能说Media Foundation是一个保守向量场。事实上虽然起点和终点相同,若是你在编码的过程中使用的算法不同,是可能会产生不同的效率,甚至不同的结果的。

Media Source Sink.png

无论如何,正如水斗的作用是让水最终到达落水管,以及电场线最终终结于负电荷那样,我们对视频/音频编码,最终目的也是为了创造出一个sink。

两种过程模型

总体上说来,Media Foundation提供了两种过程模型。

第一种是基于media pipeline的模型。简单说来就是Media Foundation会帮你创建一个media session(你不用太过于纠结这些术语,反正就是一个编码的过程),整个过程从source出发,经历了一个或多个media foundation transform,最终到sink结束。这个模型相对而言比较复杂,就连编程模型也较复杂,你需要处理很多的回调,而不像另外一个模型那么直观。尤其是如果你想要使用一些高级算法,你必须手工写一个media foundation transform,然后将其注册为一个COM组件。通常这个模型用于播放视频/音频。

另外一种过程模型使用了名为source readersink writer的东西。顾名思义,source reader是用来读取source的,sink writer是用来往sink里面写东西的。事实上正如上面的图画演示的那样,source reader和sink writer的偶合非常小,你甚至可以不使用source reader,直接给sink writer提供所需的数据。今天我们就使用这种模型,因为它更适合于编码(但不适合于播放)。请注意这种模型是Windows 7/Server 2008 R2的新功能,在早期版本的Windows中不能使用。

使用Media Foundation

创建Media Foundation程序

Windows Phone上的声音录制与编码(三):Windows C++简介 中我们介绍了如何创建一个简单的C++ dll项目。本文件继续这个话题,在那个dll项目中添加Media Foundation代码。在这之前请不要忘记先添加library引用,详细信息请参考上一篇wiki。

CppLibrary.png

这个dll中我们提供一个函数名为EncodeAudio,将它export出去(回想一下,项目模板会提供一个宏帮助我们export函数),于是这个函数就可以在其它地方,例如一个C#程序中,使用了。

NATIVEAUDIOENCODER_API HRESULT EncodeAudio(wchar_t* pInputFile, wchar_t* pOutputFile);

考虑到封装性,让我们创建一个类,将所有音频编码相关的代码都放到那个类中。我们将这个类取名为AudioEncoder。回想起上一篇wiki对C++的介绍,在这个类中,首先要做的第一件事是添加头文件和namespace引用。只有这样才能使用C++类库以及Windows和Media Foundation相关的功能。

#include "windows.h"
#include "atlbase.h"
#include "Mfidl.h"
#include "Mfapi.h"
#include "Mfreadwrite.h"
#include "string"
 
using namespace std;

这里Mfidl.h,Mfapi.h,Mfreadwrite.h是Media Foundation相关功能的头文件。事实上Media Foundation还有很多其它头文件,但今天我们只需要使用这些功能。通常windows.h是每个Windows程序必须引用的,否则基本上寸步难行。atlbase.h是COM的一个头文件,上一篇wiki中我们曾经提到过,Media Foundation大量使用了COM。至于string,这是C++标准类库STL中的一个头文件,包括std这个namespace也是。

至此,基本的准备工作就完成了。

定义变量

请大家回想一下我们在Windows Phone上的声音录制与编码(一):录制与wav编码中介绍的wav格式,在那里我们看到了很多诸如sample rate和samples per second这样的概念。很自然,Media Foundation中也会用到这些概念。为此我们需要定义一些相关参数:

	GUID m_outputAudioFormat;
UINT32 m_samplesPerSecond;
UINT32 m_sourceSamplesPerSecond;
float m_samplesRatio;
UINT32 m_averageBytesPerSecond;
UINT32 m_numChannels;
UINT32 m_bitsPerSample;
UINT32 m_blockAlign;
UINT32 m_sourceAverageBytesPerSecond;

注意到C++中没有uint这样的关键字,我们必须使用UINT32。

除此之外还有很关键的两个变量就是source reader和sink writer。

	CComPtr<IMFSourceReader> m_pSourceReader;
CComPtr<IMFSinkWriter> m_pSinkWriter;

注意到这边我们使用了CComPtr这个智能指针,这是因为我们不想手工管理内存。上一篇wiki曾经提到过,CComPtr这样的智能指针虽然无法完全取代垃圾回收,但是的确可以让我们省下很多管理内存的心事。

再有,既然我们的source和sink都是文件,我们也需要两个变量代表文件。请不要忘记C++中没有属性,我们必须使用Get/Set方法,就像Java中那样。

	wstring m_inputFile;
wstring m_outputFile;
 
wstring GetInputFile()
{
return this->m_inputFile;
}
 
void SetInputFile(wstring value)
{
this->m_inputFile = value;
}
 
wstring GetOutputFile()
{
return this->m_outputFile;
}
 
void SetOutputFile(wstring value)
{
this->m_outputFile = value;
}

以上变量的定义都放在头文件中。最后,我们需要在构造函数中对这些变量进行初始化,以防止错误的初始值可能对今后的程序逻辑产生不良影响。通常构造函数的实现都放在cpp文件中。

AudioEncoder::AudioEncoder() :
m_inputFile(L""),
m_outputFile(L""),
m_outputAudioFormat(MFAudioFormat_AAC),
m_averageBytesPerSecond(12000),
m_sourceSamplesPerSecond(16000),
m_samplesPerSecond(48000),
m_samplesRatio(1.0),
m_numChannels(1),
m_bitsPerSample(16)
{
// Source audio properties will be recalculated when creating the source reader.
// Here's just a place holder.
this->m_blockAlign = this->m_bitsPerSample / 8 * this->m_numChannels;
this->m_sourceAverageBytesPerSecond = this->m_averageBytesPerSecond;
}

这里我们定义的一些初始值,例如samples per second,是Windows Phone麦克风默认的设置。但是原始音频是不是真正的使用了这些参数,这很难说,因此我们在通过source reader读取了原始音频的信息之后,有必要修改这些参数。 还请注意我们并没有对source reader和sink writer进行初始化,因为CComPtr会自动帮我们进行初始化,将它们的值设置成nullptr。

最后,我们还需要初始化Media Foundation本身。这件事情很简单,调用MFStartup就可以了。注意这个函数返回一个HRESULT,我们必须做好异常处理。这个初始化也可以在构造函数中进行。

	ThrowIfFailed(
MFStartup(MF_VERSION)
);

创建source reader和sink writer

虽然说CComPtr会自动把source reader和sink writer设置成nullptr,真正使用时显然要为它们创建实例。接下来我们就来做这件事。

为了创建source reader和sink writer,我们使用Media Foundation提供的一些全局函数:MFCreateSourceReaderFromURLMFCreateSinkWriterFromURL。这两个函数都使用COM的标准编程模型:返回值是一个HRESULT,而不是对象的实例。对象的实例通过一个out参数进行赋值。

回想一下为了在C++中使用out参数,我们需要一个指针,甚至可能是双重指针(例如在这种场合下),也就是指向指针的指针。CComPtr的另外一个功能就在于它本身是一个指针(毕竟智能指针说到底还是指针嘛),所以我们只需要传入该CComPtr的地址即可(使用取地址符&)。至于检查返回的HRESULT值,我们使用上一篇wiki中介绍的方法,写一个全局函数ThrowIfFailed。若是HRESULT的值代表操作失败,我们就throw一个exception出来。

创建好对象实例之后还远远不够。还有一个很重要的任务是设置参数。

对于source reader而言,我们至少要告诉它我们希望使用什么东西,是音频还是视频,原始文件是什么样的编码。此外还要告诉它要读取source中的哪个流,毕竟一个文件可能包括多个流(例如一个视频流和一个音频流)。只要给定了这些基本参数,其它参数Media Foundation都会自动分析源数据已获得。

对于sink writer而言,就稍微有点复杂了,因为sink writer不可能预测你要达到什么样的效果,你必须告诉它。每个sink writer都有一个input和一个output。之前曾经说过,source reader和sink writer是松耦合的,也就说事实上sink writer的input不一定非要来自于source reader(虽然我们的例子是如此)。

Sink writer的input需要设置source reader的那些关键参数(视频还是音频,编码,等等),还需要设置诸如samples per second,number channels等众多我们在第一篇wiki中看到过的参数。请注意这里要十分小心,这些参数必须真正设置成source中相应参数的值,否则很可能编码出来的效果会不正确。例如我第一次在进行编码时,就是因为samples per second设置错误,导致原来是一首女性唱的歌,编码出的结果不管让谁听上去都认为是男性唱的了。当然,如果这就是你想要的效果,那请自便。可是若是想要保持原声,请一定要设置准确这些参数。这些参数都可以从source reader中获取。

Sink writer的output需要设置成你希望获得的编码结果。例如,我们希望将音频编码成AAC,单声道,于是就设置相关的参数。

注意有些参数(例如channel mask)你可能认为不重要,可是对于Media Foundation而言却是必须的。若是你没有设置,运行时会抱错。

为了设置参数的值,我们可以使用source reader和sink writer上对应的方法。例如选择流就使用SetStreamSelection。不过它们提供的具体的方法并不是很多,更多的参数可以通过SetGUID,SetUINT32这样的方法设置。这些方法的第一个参数是要设置的属性的代号(通常用GUID表示),第二个参数是真正的值。这是Media Foundation的一个设计,这样做的好处是今后方便扩展,例如今后如果Media Foundation要提供更多的属性,不需要提供更多的方法,只要加几个GUID就可以了。当然缺点是,你不太可能记得住每个参数的GUID是什么。为此,Media Foundation提供了很多宏,用具体的名字作为GUID的代号。若是Media Foundation本身没有提供一个宏,你也可以自己写。

类似的,如果想要获取某个属性的值,也可以使用GetGUID和GetUINT32这样的方法。例如,我们的程序中在设置好source reader的基本属性之后,会调用GetCurrentMediaType,从而让source reader自动分析源数据。之后我们通过Get***方法获取诸如samples per second这样的信息。

说了那么多,以下是具体的代码:

void AudioEncoder::CreateSouceReader()
{
CComPtr<IMFMediaType> pInMediaType;
CComPtr<IMFMediaType> pActualMediaType;
ThrowIfFailed(
MFCreateSourceReaderFromURL(this->m_inputFile.data(), nullptr, &(this->m_pSourceReader))
);
ThrowIfFailed(
this->m_pSourceReader->SetStreamSelection((DWORD)MF_SOURCE_READER_ALL_STREAMS, false)
);
ThrowIfFailed(
this->m_pSourceReader->SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, true)
);
ThrowIfFailed(
MFCreateMediaType(&pInMediaType)
);
ThrowIfFailed(
pInMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
);
ThrowIfFailed(
pInMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM)
);
ThrowIfFailed(
this->m_pSourceReader->GetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, &pActualMediaType)
);
ThrowIfFailed(
pActualMediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &this->m_sourceSamplesPerSecond)
);
ThrowIfFailed(
pActualMediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &this->m_numChannels)
);
UINT32 bitsPerSample = 0;
ThrowIfFailed(
pActualMediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &bitsPerSample)
);
 
ThrowIfFailed(
this->m_pSourceReader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, nullptr, pInMediaType)
);
}
 
// Create the sink writer. Also returns the stream index.
void AudioEncoder::CreateSinkWriter(DWORD* pStreamIndex)
{
HRESULT hr = S_OK;
 
// Create the sink writer.
CComPtr<IMFMediaType> pOutputMediaType;
CComPtr<IMFMediaType> pInMediaType;
ThrowIfFailed(
MFCreateSinkWriterFromURL(this->m_outputFile.data(), nullptr, nullptr, &(this->m_pSinkWriter))
);
 
// Create and configure the output media type.
ThrowIfFailed(
MFCreateMediaType(&pOutputMediaType)
);
ThrowIfFailed(
pOutputMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
);
ThrowIfFailed(
pOutputMediaType->SetGUID(MF_MT_SUBTYPE, this->m_outputAudioFormat)
);
ThrowIfFailed(
pOutputMediaType->SetGUID(MF_MT_AM_FORMAT_TYPE, AAC_FORMAT_TYPE)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, this->m_samplesPerSecond)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 12000)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, this->m_numChannels)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, this->m_bitsPerSample)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_AUDIO_PREFER_WAVEFORMATEX, 1)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_COMPRESSED, 1)
);
ThrowIfFailed(
pOutputMediaType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, 1)
);
DWORD streamIndex;
ThrowIfFailed(
this->m_pSinkWriter->AddStream(pOutputMediaType, &streamIndex)
);
 
// Set the input media type.
ThrowIfFailed(
MFCreateMediaType(&pInMediaType)
);
ThrowIfFailed(
pInMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
);
ThrowIfFailed(
pInMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, this->m_sourceSamplesPerSecond)
);
this->m_sourceAverageBytesPerSecond = this->m_sourceSamplesPerSecond * this->m_blockAlign;
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, this->m_sourceAverageBytesPerSecond)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, this->m_numChannels)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, this->m_bitsPerSample)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, this->m_blockAlign)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK, 0x4)
);
ThrowIfFailed(
pInMediaType->SetUINT32(MF_MT_AUDIO_VALID_BITS_PER_SAMPLE, this->m_blockAlign)
);
ThrowIfFailed(
this->m_pSinkWriter->SetInputMediaType(streamIndex, pInMediaType, nullptr)
);
// Start to write.
ThrowIfFailed(
this->m_pSinkWriter->BeginWriting()
);
*pStreamIndex = streamIndex;
}

在创建sink writer时,我们顺便通过一个指针返回一个stream index,代表接下来音频要被写入那个流。

还请注意在创建完sink writer之后,我们调用了BeginWriting这个方法,这告诉sink writer现在开始可以往sink中写东西了。这个方法是必须调用的,而且一旦调用,就意味着sink writer已经定型了,你不能再修改它的参数。

编码

现在我们可以真正地进行编码了。本示例的编码过程很简单,就是把source中的一个个sample读出来,并且原封不动地写到sink中去。在这个过程中,sink writer会自动处理编码以及各种参数,我们什么也不用担心! 需要注意,有可能source中会存在空的sample。这种情况下我们一律跳过,无视它们。当source reader读完整个source流之后,会通过一个flag返回MF_SOURCE_READERF_ENDOFSTREAM,这给了我们一个机会知道什么时候编码应该结束。

void AudioEncoder::Encode()
{
this->CreateSouceReader();
DWORD streamIndex = 0;
this->CreateSinkWriter(&streamIndex);
CComPtr<IMFMediaBuffer> pMediaBuffer;
 
while (true)
{
CComPtr<IMFSample> pSample;
DWORD flags = 0;
ThrowIfFailed(
this->m_pSourceReader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, &pSample)
);
if (pSample != nullptr)
{
ThrowIfFailed(
this->m_pSinkWriter->WriteSample(streamIndex, pSample)
);
}
 
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
break;
}
}

至此,编码就全部完成了!可以看到,使用Media Foundation其实并不困难,我们大多数的工作都放在了配置source reader和sink writer之上,一旦配置好,什么诸如解析源文件,编码和压缩算法,等等问题,都完全不同去考虑!而那些事情其实才是真正复杂的,例如,关于AAC,wikipedia有一个非技术性的介绍,但那篇文章已经很长了。如果你要自己实现AAC编码,可以想象工作量是非常庞大的。

若是你想要实现更多的效果,那么你要做的事情就是对于读出来的每一个sample进行分析,并且修改音波的诸如频率(控制音高)和振幅(控制音量)等数据。本文就不再具体描述这些了。

测试编码

为了测试编码,我们可以写一个简单的console程序,或者使用Visual Studio自带的Unit Test功能。上一篇wiki曾经介绍过如何在.NET中调用C++ dll,这里不再复述。

    class Program
{
[DllImport("NativeAudioEncoder.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr EncodeAudio(string inputFile, string outputFile);
 
static void Main(string[] args)
{
IntPtr hr = EncodeAudio(@"test.wav", @"test.mp4");
}
}

注意到HRESULT在C#中对应于IntPtr。 顺带一提,若是你希望在.NET程序中能够调试C++代码,在项目属性的Debug页面中需要钩上“Enable unmanaged code debugging”这个选项。

若是编码成功,你会在C#项目的debug=>bin目录下看到生成的mp4文件。

在service中使用C++ dll

现在我们可以正式在service中使用C++ dll了。这个过程和之前在console程序中使用是完全一样的。不过有一点要注意,默认情况下C++ dll和.NET console应用程序都是针对x86编译的。而ASP.NET程序则是针对Any CPU编译的。C++不支持Any CPU,你必须要么选择x86,要么选择x64(Windows 8的Metro应用程序还有ARM选项)。很多服务器都是64位的操作系统,所以Any CPU编译出来的程序会运行于x64环境下。可是在一个x64程序中,你是不能调用x86 dll的。因此你有必要将C++项目针对x64也编译一分。

在Visual Studio的工具栏中你可以看到Debug旁边有一个下拉框,里面默认的文字通常是Mixed Platforms。

Change Platform.png

选择它,再点击configuration manager,你会看到solution中所有项目的编译针对的平台。对于一个C++程序而言,默认是针对Win32,也就是x86编译的(x86程序都是32位程序)。

Enable Native Debug.png

选择旁边的下拉框,点击New,就会弹出下面的窗口,供你选择该项目要编译的平台。我们这边选择x64。

New Platform.png

现在,再次编译C++项目,你会在项目文件夹中看到一个x64文件夹,里面是针对x64编译出来的dll。

为了使用这个dll,还是要将它作为一个existing item添加到C#项目中,并且将Copy to Output Directory设置成Copy if newer。 请特别注意上述步骤只需要在server为64位环境的场合下才进行。若是server为32位,就一定不能够针对x64进行编译。例如,Visual Studio 2010自带的ASP.NET Development Server就是一个32位server,即使你装载64位操作系统上它还是32位的。这一点一定要小心。 接下来无非就是依样画葫芦了。注意编码通常需要较长的时间,因此最好放到一个后台线程执行,并且让response在编码还在进行的过程中就予以返回。我们的示例程序采取了开始编码后就不管的策略。在你真实的项目中,你需要定一个一套逻辑,让客户端能够跟踪编码的进程和结果。 下面是修改后的Post方法,用来处理文件上传。

        public HttpResponseMessage Post([FromUri]string filename)
{
var task = this.Request.Content.ReadAsStreamAsync();
task.Wait();
Stream requestStream = task.Result;
 
try
{
string inputFilePath = HttpContext.Current.Server.MapPath("~/" + filename);
string outputFilePath = inputFilePath.Substring(0, inputFilePath.LastIndexOf('.')) + ".mp4";
Stream fileStream = File.Create(HttpContext.Current.Server.MapPath("~/" + filename));
requestStream.CopyTo(fileStream);
fileStream.Close();
requestStream.Close();
 
// Encode the audio on a background thread.
Task task2 = new Task(() =>
{
IntPtr hr = EncodeAudio(inputFilePath, outputFilePath);
});
task2.Start();
}
catch (IOException)
{
throw new HttpResponseException("A generic error occured. Please try again later.", HttpStatusCode.InternalServerError);
}
 
HttpResponseMessage response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.Created;
return response;
}

这样一来,我们的整个程序就完成了。接下来你可以提供诸如让用户自拟的服务器上直接播放mp4的功能(Windows Phone支持从网络播放文件),不过我们就不再具体描述了。

总结

至此,这一整个系列的wiki就结束了。这个系列不仅仅介绍了Windows Phone相关的技术(使用麦克风,调用REST service),也介绍了使用Windows进行开发的一些方式(COM,Media Foundation),同时还介绍了一些业界标准(wav,AAC)。在真正开发一个项目的过程中,Windows Phone很可能只是很小的一部分,作为一个solution开发人员,我们还是需要了解相关平台才行。

This page was last modified on 16 July 2013, at 07:46.
120 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×