使用 FMOD 的 Pocket PC 简单 MP3 播放器






4.42/5 (18投票s)
如何使用 Firelight Technologies 的 FMOD 音频系统创建一个非常简单的 MP3 播放器。
引言
当我被要求创建一个基于 Pocket PC 技术和 MP3 音轨的音频指南时,我不知道该去哪里以及如何实现。于是,我上网冲浪,发现了一个很棒的库:Firelight Technologies 的 FMOD Sound System。他们提供了一套非常完整的函数,可以让你创建几乎任何桌面和 Pocket PC 的录音/播放应用程序。你可以在 这里 阅读更多关于它的信息。下载 API 自己去看看吧!(你离 WinAmp 只有一步之遥……)
对于我的系统,我当然需要它的 CE 版本 DLL。如果你是用 C++ 为 CE 开发,那没问题。如果你是用 .NET 编码,那么麻烦就开始了。
托管与非托管
即使不深入定义,人们也可以意识到从托管代码通信到非托管代码的困难。实际上,更让人生畏的是反过来的方式。在 Pocket PC 和普通的桌面应用程序上,可以直接从 .NET 代码调用 DLL 函数,使用正确的 `DLLImport` 属性,例如在这个例子中
'use to set PPC output volume :
'dim v as integer = waveSetVolume(IntPtr.Zero, CInt(&H00FF))
<DLLIMPORT("COREDLL")> _
Public Shared Function waveOutSetVolume(_
ByVal hMod As IntPtr, ByVal lVolume As Integer) As Integer
End Function
反过来通信要困难得多,尤其是在它可能随时发生的情况下,例如事件触发。我必须承认,我还没有成功地让 *fmodce.dll* 回复我的 .NET CF 应用程序。这很可惜,因为它允许应用程序从该 DLL 获取事件回调,从而使应用程序能够进一步控制流。FMOD 论坛上一些优秀的程序员提供了一些提示,但没有一个帮到我。在这个简单的应用程序中,我唯一需要的事件是“曲目结束”事件。好吧,我最终解决了这个问题,后面会讲到。
FMOD 托管
我确实看到了一些非常好的帖子,但它们对我来说太复杂了,所以我决定自己编写代码。基本需要的是调用 FMOD 函数的能力,所以使用 `DLLImport` 语句正是我需要的。这里不需要复杂的包装器
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Init", SetLastError:=True, _
CharSet:=CharSet.Unicode, _
CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_Init(ByVal mixrate As Integer, _
ByVal maxsoftwarechannels As Integer, _
ByVal flags As Integer) As Boolean
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_GetLength", SetLastError:=True, _
CharSet:=CharSet.Unicode, _
CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_GetLength(ByVal fstream As IntPtr) As Integer
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_GetPosition", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_GetPosition(ByVal fstream As IntPtr) As UInt32
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_Open", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi> _
Public Shared Function fmod_Open(ByVal data As IntPtr, ByVal mode As Integer, _
ByVal offset As Integer, ByVal length As Integer) As IntPtr
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_Play", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_Play(ByVal channel As Integer, _
ByVal fstream As IntPtr) As Integer
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_SetPosition", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_SetPosition(ByVal fstream As IntPtr, _
ByVal position As UInt32) As Boolean
End Function
<DllImport("fmodce.dll", _
EntryPoint:="FSOUND_Stream_Stop", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Function fmod_Stop(ByVal fstream As IntPtr) As Boolean
End Function
<DllImport("fmodce.dll", EntryPoint:="FSOUND_Close", SetLastError:=True, _
CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)> _
Public Shared Sub fmod_Close()
End Sub
我认为这些函数都很直观。如果你需要更多帮助,请阅读 API 文档。
调用函数
调用这些函数就像调用任何其他函数一样;你必须确保提供的参数在预期的范围内,并且类型正确。就是这样。以下是播放 MP3 文件需要遵循的顺序
- 用所需的混音速率初始化声音设备。
- 打开文件。
- 以特定的输出模式(单声道、立体声等)打开音轨。
- 播放音轨。
- 关闭文件和设备。
这些简单的步骤可以通过以下代码实现
'initialize fmod with mixrate equal to 44.1kHz
fmod_Init(44100, 16, 0)
'initialize the handle with the track file
Dim soundStream As IntPtr = fmod_getStream(currentSoundTrack)
'open the stream in Normal mode, which is
'combination of Mono sound, 16 bits, Signed
Dim soundHandle As IntPtr = _
fmod_Open(soundStream, &H10 Or &H20 Or &H100, 0, 0)
'start playing
fmod_Play(0, soundHandle)
'... later
'stop playing
fmod_Stop(soundHandle)
'release the ressources
fmod_Close()
曲目结束和暂停按钮
为了解决曲目结束时应用程序没有收到任何通知的问题,我决定使用一个粗糙的计时器,它会不时地检索曲目中的当前位置。如果当前位置大于总曲目长度,那么曲目就结束了!这是实现此目的的代码
Private Sub timSound_Tick(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles timSound.Tick
uPausePosition = fmod_GetPosition(soundHandle)
currentPosition = CInt(uPausePosition.ToString)
If currentPosition >= currentSoundLength Then
timSound.Enabled = False
stop_track()
End If
End Sub
对于我没能在 FMOD 中找到的暂停功能,我使用了从给定位置开始播放音轨的可能性。当用户点击暂停按钮时,我记录当前位置并停止播放。当用户再次点击播放时,我从旧位置开始播放同一音轨。
Compact Framework 版本
一些优秀的 CodeProject 贡献者指出我的代码在 Compact Framework 2.0 上无法正常工作。这种行为与 FMOD 库无关,而是由于 .NET CF 1.0 中的一个 bug。在我的代码中,我使用了一些从网上找到的代码的简化版本,来获取指向流的指针
Private Function fmod_getStream(ByVal filename As String) As IntPtr
'thanks to mattias73 and lonifasiko for the help on this !
Dim filenames As Byte() = Encoding.Default.GetBytes(filename & vbNullChar)
Dim hfile As GCHandle = GCHandle.Alloc(filenames, GCHandleType.Pinned)
If Environment.Version.Major = 1 Then
fmod_getStream = New IntPtr(hfile.AddrOfPinnedObject().ToInt32 + 4)
Else
fmod_getStream = hfile.AddrOfPinnedObject()
End If
End Function
你需要知道的是,在 .NET CF 1.0 中,`AddrOfPinnedObject` 函数不会返回被固定对象的地址,而是地址会偏移 4 个字节,这相当于指针本身的大小。因此,要检索正确的地址,你必须指向更靠后的 4 个字节!在网上搜索 `AddrOfPinnedObject` 函数,你将获得很多细节。在 .NET CF 2.0 中不需要这种解决方法,因为这个 bug 在该版本中已经修复,所以我们的 bug 修复需要是条件性的。还有一点:上面提到的 bug 修复实际上并不是一个好的 bug 修复,因为虽然 .NET CF 2.0 不再有这个问题,但它也可能在 .NET CF 1.x 的后续版本(如 Service Pack)中得到解决。如果发生这种情况,上面的代码将不再有效。那么唯一好的方法是自己分配所需的内存,并创建指向该内存块的指针,就像在这里(MSDN 站点)(第 6.14 段)所示的那样。因此,完美的解决方案——摆脱版本检查——将是
Private Function fmod_getStream(ByVal filename As String) As IntPtr
Dim filenames As Byte() =
Encoding.Default.GetBytes(filename & vbNullChar)
Dim p As IntPtr = LocalAlloc(_
Convert.ToUInt32(LMEM_FIXED Or LMEM_ZEROINIT), _
Convert.ToUInt32(filenames.Length))
If Not p.Equals(IntPtr.Zero) Then
Marshal.Copy(filenames, 0, p, filenames.Length)
'else "out of memory" !
End If
fmod_getStream_New = p
End Function
我已经将此函数包含在我的代码中,所以你可以试试。
整合所有内容
那么还剩下什么?创建一个漂亮的前端不是这里的目的,所以设计将保持简洁。添加播放/停止/暂停按钮以及一个浏览文件对话框,你就可以为你的 Pocket PC 创建一个快速的 MP3 播放器了。你可以尝试将此代码与 micahbowerbank 的个人 Mp3 Disc catalogue 结合起来,最终得到一些非常酷的东西!源代码包是一个常规的 VS 项目。我在代码中包含了一些注释。演示 zip 文件包含一个 CAB(armv4)文件,你可以使用 ActiveSync 将其复制到你的 PPC。一旦它在那里,点击它,它就会自动解压并安装。
关注点
目的是为与 FMOD 库交互提供一个起点。这只是该库可以做什么的一个样本。我知道还有其他选择,但我找不到一个能让你获得如此快速结果的。
更新
- 26.10.2005
- 我决定删除外部 C# FMOD 包装器,并将其包含在主模块中,这样更容易理解。
- 14.11.2005
- 针对 .NET CF 1.0 的 `AddrOfPinnedObject` bug 修复。
最终声明
请注意,除非您拥有有效许可证,否则禁止将 FMOD 和/或 MP3 材料用于商业用途。FMOD Sound System 是 Firelight Technologies Pty, Ltd. 的版权所有 © 1994-2005。MP3 许可信息可以在 这里 找到。