65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.42/5 (18投票s)

2005 年 10 月 2 日

CPOL

6分钟阅读

viewsIcon

212679

downloadIcon

2221

如何使用 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 许可信息可以在 这里 找到。

© . All rights reserved.