Click here to Skip to main content
Click here to Skip to main content

Simple MP3 Player for Pocket PC using FMOD

, 14 Nov 2005
Rate this:
Please Sign up or sign in to vote.
How to create a very simple MP3 player, using Firelight Technologies' FMOD sound system.

Introduction

When I was asked to create an audio guide based on Pocket PC technology and MP3 sound tracks, I had no clue where to go and how to achieve this. So, I went for a little net-surfing and what I found was a neat library: FMOD Sound System from Firelight Technologies. They propose a more-than-complete set of functions, enabling you to create just about any sound recording/playing application, for desktops and Pocket PCs. You can read more about it here. Download the API and see for yourself! (You're just another WinAmp away...)

For my system, I needed of course, a CE version of their DLL. If you are developing in C++ for CE then it's all right. If you are coding in .NET, then start the troubles.

Managed vs. Unmanaged

Without even going into the definitions, one could realize at least the difficulties of communicating from managed code to unmanaged code. Actually it's the opposite way around that gives you headaches. On Pocket PCs, as well as normal desktop apps, it is possible to directly call a DLL function right from the .NET code, using the correct DLLImport attributes, like in this example:

    '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

Talking back is much harder, especially when it can occur any time, i.e. with event firing. I must admit that I have not succeeded in having the fmodce.dll talk back to my .NET CF app. This is a shame because it would allow the application to get event callbacks from that DLL, thus enabling the application to control the stream even more. A number of good coders on FMOD's forum have posted some hints, but none of them helped me out. The only event I was asking for, in this simple app was the "End of track" event. Well, I finally worked around this problem, which you can read later on.

FMOD managed

I did come across some very good posts that were too complex for my needs so, I decided to go for my own code. What is basically needed is the ability to call FMOD's functions, so using DLLImport statements is just what I need. No need for a complex wrapper here:

<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

The functions are I think quite self-explanatory. If you need more help, please read the API documentation.

Calling the functions

Calling these functions is just like calling any other function; you have to make sure that the arguments supplied are in the expected range, and of the expected type. That's it. Here is the sequence which needs to be followed, in order to play an MP3 file:

  • Initialize the sound device, with the desired mix rate.
  • Open the file.
  • Open the track with the specific output mode (mono, stereo, etc...).
  • Play the track.
  • Close the file and the device.

These simple steps can be achieved with the following code:

'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()

End of track and the Pause button

To work around the fact that there is nothing which fires back to my application when the end of the track is reached, I decided to go for an ugly timer, which would retrieve from time to time the actual position in the track. If the current position is greater than the total track length, then the track is finished! Here is the code to do this:

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

For the pause functionality, which I could not find in FMOD, I have used the possibility to start playing a track at a given position. When the user taps the pause button, I record the current position and stop the track. When the user taps the play again, I start playing the same track, starting at the old position.

Compact Framework version

Some good CodeProject contributors pointed out that my code wasn't working on Compact Framework 2.0. This behavior has nothing to do with FMOD's library, but that is due to a bug in .NET CF 1.0. In my code, I use a condense of what I could find on the net, to get a pointer to the stream:

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

What you need to know is that, in .NET CF 1.0, the function AddrOfPinnedObject does not return the address of the pinned object, but the address is shifted by 4 bytes, which corresponds to the size of the pointer itself. So to retrieve the correct address, you must point 4 bytes further! Look in the net for the AddrOfPinnedObject function and you'll get plenty of details. This workaround is not necessary in .NET CF 2.0 because the bug has been fixed in this version, so our bug-fix needs to be conditional. One more thing: the bug fix mentioned above is not a good bug-fix in fact, because although .NET CF 2.0 doesn't have this problem anymore, it might be solved as well in later releases of .NET CF 1.x (as Service Packs). Should this happen, the code above wouldn't work anymore. The only good way then, would be to allocate the needed memory yourself, and create your own pointer to that chunk of memory, as shown here (MSDN site) (par. 6.14). So the perfect way - free from version check - would be:

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

I have included this function in my code so you can give it a try.

Putting it all together

So what's left? Creating a nice front end is not the purpose here, so the design will stay sober. Add play/stop/pause buttons, and a browse file dialog and you have a quick MP3 player for your Pocket PC. One can try to combine this code with micahbowerbank's personal Mp3 Disc catalogue, and end up with something really cool! The source package is a regular VS project. I have included some comments in the code. The demo zip contains a CAB (armv4) file which you can copy to your PPC using ActiveSync. Once it's there, tap on it and it will extract itself and install.

Points of interest

The purpose is to have a starting point for interacting with the FMOD library. This is just a sample of what can be done with this library. I know there are other alternatives, but I couldn't find even one that gives you such a rapid result.

Updates

  • 26.10.2005
    • I decided to remove the external C# FMOD wrapper and include it in the main module, which makes it easier to understand.
  • 14.11.2005
    • AddrOfPinnedObject bug-fix for .NET CF 1.0.

Finale notice

Don't forget that using FMOD and/or MP3 material for commercial use is prohibited, unless you have valid license. FMOD Sound System is a copyright of © Firelight Technologies Pty, Ltd., 1994-2005. MP3 licensing information can be found here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Julien Hoffmann
Web Developer
France France
No Biography provided

Comments and Discussions

 
GeneralMy vote of 1 Pinmembermitul_sweetcool1-Nov-09 3:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140814.1 | Last Updated 14 Nov 2005
Article Copyright 2005 by Julien Hoffmann
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid