如果要对音频数据进行临时访问,该程序需要调用表示新数据块的 IMFMediaBuffer 对象上的 Lock,然后调用其上的 Unlock。 在这些调用之间,图
4 中的 GetNextBlock 方法将该块复制到一个新分配的字节数组中。
图
4 中的 SubmitBuffer 方法负责设置 XAUDIO2_BUFFER 结构中的字段,准备提交要播放的音频数据。 请注意,这种方法还如何将 pContext 字段设置为分配的音频缓冲区。 此指针传递给图
4 末尾的 OnBufferEnd 回调方法,这样就可以删除数组内存。
文件被完全读取后,下一个 ReadSample 调用设置一个 MF_SOURCE_READERF_ENDOFSTREAM 标志,并且 IMFSample 对象为 null。 该程序通过设置一个
endOfFile 字段变量进行响应。 此时,另一个缓冲区仍在播放,并且会出现对 OnBufferEnd 的最后一次调用,这样利用这次机会释放一些系统资源。
还有一个 OnStreamEnd 回调方法,该方法通过设置 XAUDIO2_BUFFER 中的 XAUDIO2_END_OF_STREAM 标志触发,但在这种情况下难以使用。 问题在于无法设置这个标志,直到从
ReadSample 调用收到 MF_SOURCE_READERF_ENDOFSTREAM 标志。 但 SubmitSourceBuffer 不允许缓冲区为空或缓冲区的大小为零,这意味着无论如何必须提交一个非空缓冲区,即使不再有可用数据!
旋转 Metaphor 乐队的唱片
当然,将音频数据从 Media Foundation 传递到 XAudio2 不像使用 Windows 8 Media-Element 那么容易,不值得这样做,除非要用音频数据做一些有趣的事情。 你可以使用
XAudio2 设置一些特殊效果(如回声或混响),在本专栏的下一期中,我会将 XAudio2 过滤器应用于声音文件。
同时,图
5 显示一个名为 DEEJAY 的程序,该程序在屏幕上显示一张唱片,并在音乐播放时以每分钟 33 1/3 转的默认速度旋转唱片。
![技术分享](https://msdn.microsoft.com/zh-cn/magazine/dn166936.Petzold_Figure5_hires(en-us,MSDN.10).png)
图 5 DeeJay 程序
此处未显示应用程序栏,该应用程序栏上有一个加载文件按钮和两个分别控制音量和播放速度的滑动条。 此滑动条的范围从
-3 到 3,这些值表示速度比。 默认值是 1。 值
0.5 表示以半速播放文件,值 3 表示以三倍速度播放文件,值 0 表示基本上暂停播放,负值表示向后播放文件(或许你会听到被编码在音乐中的隐藏声音)。
当然,这可是 Windows 8,你也可以用手指旋转唱片,这正体现了程序名称的由来。 DEEJAY
支持单指惯性旋转,所以可以向任一方向旋转唱片,动作要平稳。 你也可以点击唱片,将“唱针”移到该位置。
我非常,非常,非常想采用交替调用 ReadSample 和 SubmitSourceBuffer 的方法以类似于 StreamMusicFile 项目的方式实现此程序。 但是,在试图倒着播放文件时问题出现了。 我确实需要
IMFSourceReader 支持 ReadPreviousSample 方法,但它并不支持。
IMFSourceReader 确实支持的是 SetCurrentPosition 方法,这个方法允许你移动到文件中的先前位置。 但是,随后的
ReadSample 调用开始返回早于该位置的块。 在大多数情况下,一系列对 ReadSample 调用最终会返回到 SetCurrentPosition 之前最后一次 ReadSample
调用的位置,但有时并非如此,结果一团糟。
我最终放弃了,程序只是简单地将整个未压缩的音频文件加载到内存中。 为了降低内存占用量,我指定
16 位整数样本,而不是 32 位浮点样本,但每分钟音频仍然占用约 10 MB 内存,加载一首马勒交响曲的长乐章将占用约 300 MB。
这些马勒交响曲还要求整个文件加载方法在次级线程中执行,采用 Windows 8 中提供的 create_task 功能使其大大简化。
为了简化对单个样本的处理,我创建了一个名为 AudioSample 的简单结构: