×
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 10:46.
172 page views in the last 30 days.