How to play sound files in VB.NET via the Media Control Interface (MCI) from the windows API.
This tutorial is meant to provide a non-comprehensive introduction - tailored to the beginner - to playing audio with the MCI, and assumes some basic knowledge about functions, arguments, variables, etc...
If information on these subjects is required, I would recommend taking a look at the nice tutorial series by Vwse here:
http://forum.codecall.net/vb-tutoria...tallation.html
Why did I write this tutorial?
Rewind to about a week ago. I had a vision for a little text based game I was going to try to create for fun, and had some ideas for music and sound effects in it. What I had in mind was a variety of sound effects being played/stopped at precise times and at different volumes, and different background musics which looped, and changed at different locations in the game. I quickly realized that this was not feasible via the audio functionality built into VB. I discovered the mci functions soon after, but found that the amount of online information about their use was quite pathetic. The information there was, excluding the online documentation, was not the easiest to find. I thought I'd try to compile the things I'd learned, into an easily accessible guide.
What is the MCI, and why would I want to use it?
The MCI is an interface in the windows API, which is used to control various media items.(sound files, video files, dvd drives, audio controllers, recording, etc…) It is useful because, from my current understanding (feel free to correct me on this), there is no built in way to play any sound file other than a wave (.wav extension) in Visual Basic .NET. A .wav file is an uncompressed, and therefore high quality and space consuming audio file. It is not a sound format which I often use for importing music, sound effects, or anything on my computer. It is possible to convert various audio file formats to .wav and play them that way, but that would be the easy way out wouldn’t it? Another problem is that I believe the built in code only supports playing one sound file at a time, which is no good for something like a game, where you might want to have background music, people talking, sound effects, etc. If you are only interested in playing solitary wave files, check out the following links:
Playing Sounds
http://forum.codecall.net/vb-tutoria...basic-net.html
Feel free to start a new console program and follow along.
To get started, we’re going to put this in our code:
Code:Private Declare Function mciSendString Lib "winmm.dll" Alias "mciSendStringA" (ByVal lpszCommand As String, ByVal lpszReturnString As String, ByVal cchReturn As Integer, ByVal hwndCallback As Integer) As Integer
As I previously stated, this function is not a built in function in vb, it is in fact stored in a dynamic link library called “winmm.dll”, located at “C:\windows\system32\winmm.dll”. A .dll file is essentially a library of code/functions which provides simultaneous functionality to many different programs. The alias keyword refers to how the function is named in the .dll file. The declare keyword is used to utilize procedures in external files in vb. Anyway, this function is used to control the MCI. I’ll do a breakdown of the intimidating looking parameters this function has in ascending order from least to most important. First though, it's return value.
Return Value - As you know, this function returns an integer. If your command succeeded, it returns 0. Otherwise, it returns a number which corresponds to an error. A different function can interpret these errors for us, more on this later.
This parameter is only used if you used the “notify” flag at the end of a command string. For the most part, this can be left as 0 or null. I have never used it, and don’t know how useful it is. (I’ve read that it’s not especially useful, and I’ve never had a reason to look it up) Refer to the following page for more info on this subject.Code:ByVal hwndCallback As Integer
The Wait, Notify, and Test Flags (Windows)
This parameter is only used if you are requesting return information. If your command is “open”, or “play”, you don’t need any return information, you would use an empty string, or just make the argument null (keyword “nothing” in VB.NET). However, you cannot use just any string, you must use a buffered string. From my current knowledge, a buffered string is essentially a string that has a set number of characters at all times. Say you requested the playing status of the sound file you were playing, and it returns "playing" in the string variable specified in the lpszReturnString argument. The rest of the characters in the buffered string would just be spaces. Anyway, if you are requesting return information, you use a string with a buffer of 128 (I'm not sure if any other size buffers work, I've never tried them) as the argument, which will receive the return information. This is easy to do:Code:ByVal lpszReturnString As String
This simply fills the string "returnData" with 128 spaces, so it can receive the return information. This all probably sounds confusing at the moment, but I'll soon give a few examples.Code:Private returnData As String = Space(128)
Very simple – the size, in characters, of the lpszReturnString buffer. If that parameter is not being used, just set it to 0. If you are requesting return information, set it to 128.Code:ByVal cchReturn As Integer
Code:ByVal lpszCommand As String
This is the command sent as a string to an MCI device - the main way we're going to be loading, playing, pausing, etc... The syntax for this string is always as so:
(Command verb) + (target device) + (Other command specific flags (generally only used if requesting information))
This section is going to cover the actual commands (play, stop, seek, etc...)
Using the Open command
Syntax: (in string form)
"Open " & (path to sound file in wrapped in quotes) & " Alias " & (future device name)
– Opens a device (sound file) so it can be used/manipulated via the MCI. After opening a file, it is assigned the name after the alias keyword. This becomes the device name, and the sound file is now referenced to by this name. Any string not containing a space can be used for this, I’ll give some examples further below.
Note - If playing 2+ audio files simultaneously, a different alias should be used for each one. The same is true if you want to play different parts of the same audio file simultaneously, they must be separately opened, and be assigned different aliases.
Note - it is possible to leave out the alias keyword, and simply keep referring to the sound file by its path. However, I wouldn't recommend it. Seems like it would make it more confusing, and easier to make a mistake.
Important - the path to the sound file must be wrapped in quotes. This is slightly easier said than done, as a quote would generally end the string. There are a few ways to do this, all pretty much the same:
In this example I assign the device name "thesong" to the opened audio file.Code:Private Const Path As String = Chr(34) & "C:\medeski.mp3" & Chr(34) Private Const Path2 As String = """C:\medeski.mp3""" Private Const Path3 As String = ControlChars.Quote & "C:\medeski.mp3" & ControlChars.Quote
Using the Play commandCode:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias thesong" , "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Syntax: (in string form)
"Play " & (device or "all") & (optional “ repeat”) or
"Play " & (device or "all") & " from " & (position in milliseconds) & " to " & (position in milliseconds) & (optional “ repeat”)
– Can be used to play an audio file after it has been loaded, or to play all loaded audio files. Can also be used to play from a certain start position to a certain end position in a file, and to loop playing of a file. Generally should not be used for resuming. Unfortunately, the repeat keyword will only repeat from the beginning of a song. That means it is not directly possible to, say, loop a 10 second portion in the middle of a song. More on this in the examples below.
An example of looping an audio fileCode:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias thesong" , "", 0, 0) mciSendString("play thesong", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
This doesn't workCode:Private Const Path As String = """C:\song1.mp3""" Sub Main() mciSendString("open " & Path & " alias theSong" , "", 0, 0) 'That's 2000 milliseconds, this will loop the first 2 seconds of a song mciSendString("play theSong from 0 to 2000 repeat", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Using the Close commandCode:Private Const Path As String = """C:\song1.mp3""" Sub Main() mciSendString("open " & Path & " alias theSong" , "", 0, 0) 'At first, this looks like it might loop this 3 second stretch, but unfortunately 'it does not. It will in fact play the song from the 5th second to the 8th 'second, and then start back from the beginning of the song, continuing to loop the first 8 seconds mciSendString("play theSong from 5000 to 8000 repeat", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Syntax: (in string form)
"Close " & (device or "all")
Closes one or all opened devices (files). To use them again, they must be reopened. Good practice (in my opinion) is to always close after opening something, although this might not be necessary.
Using the Pause commandCode:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias thesong" , "", 0, 0) mciSendString("play thesong", "", 0, 0) Console.ReadLine() mciSendString("close thesong" , "", 0, 0) End Sub
Syntax: (in string form)
"Pause " & (device or "all") – Can be used to pause an audio file after it has been loaded and is playing, or to pause all playing audio files. I tend to use stop instead of pause, because of an issue I talk about below.
Using the Stop commandCode:mciSendString("pause thesong", "", 0, 0) mciSendString("pause all", "", 0, 0)
Syntax: (in string form)
"Stop " & (device or "all") - Can be used to stop an audio file after it has been loaded and is playing, or to stop all playing audio files. After an audio file has been loaded but not played, it is in the “stopped” mode. Otherwise, I do not know how this differs from pause save a bug which I will mention later.
Using the Resume commandCode:mciSendString("stop thesong", "", 0, 0) mciSendString("stop all", "", 0, 0)
Syntax: (in string form)
"Resume " & (device or "all") – Used to resume audio when it is paused, or when it is stopped (cannot be used to start playing the initial time). Good to note is that something like "resume all repeat" does not work. Repeat can only be used with the play command.
In this example, a song is opened, played for 10 seconds, paused for 2 seconds, and then resumed.
Note – the play command can be used to resume playback of a file in the “stopped” position, and sometimes works to resume paused files. However, there is a bug when using the play command to resume paused files, which can result in the song restarting, and other strange issues. For this reason I tend to use stop more than pause, as then if needed I can resume a song which isn't currently looping, and have it loop.Code:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias theSong" , "", 0, 0) mciSendString("play all", "", 0, 0) 'halts the code for 10000 milliseconds (10 seconds) System.Threading.Thead.Sleep(10000) mciSendString("pause all", "", 0, 0) System.Threading.Thead.Sleep(2000) mciSendString("resume all", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
For instance:
Using the Seek commandCode:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias medeski" , "", 0, 0) mciSendString("play medeski", "", 0, 0) System.Threading.Thead.Sleep(10000) mciSendString("stop medeski", "", 0, 0) System.Threading.Thead.Sleep(5000) 'If I wanted to resume using the "resume" command, I would have no way 'to make the song loop, but if I use play... mciSendString("play medeski repeat", "", 0, 0) 'This will resume from the stopped position, and loop Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Syntax: (in string form)
"Seek " & (device name) & " to " & (position in milliseconds)
– Goes to a specified location in a sound file, and stops. 0 is the beginning of the song, the end will vary. This should only be used when the file is paused or stopped, otherwise there is a strange issue that can cause the file to not play. Another thing to remember when using the seek command, is that after seeking, the play command must be used, not resume.
"seek alias to start" and "seek alias to end" - work predictably
Example: plays the first 4 seconds of a song, then starts playing from the
40th second
Using the Status commandCode:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias medeski" , "", 0, 0) mciSendString("play medeski", "", 0, 0) System.Theading.Thead.Sleep(4000) mciSendString("pause all", "", 0, 0) mciSendString("seek medeski to 40000", "", 0, 0) mciSendString("play medeski", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Syntax: (in string form)
"Status " & (device name) & (“ mode”, or “ position”, or “ length”, or " volume")
– This command requests some information about the playing status of the sound file. “Mode”, will request the mode of the given device name. It will leave the info in the string given to it in the lpszReturnString parameter, and it will contain “stopped”, “paused”, “playing”, or nothing at all if it did not recognize the device name. (There are a couple other returns, but I've never encountered them. I think they might only be used for non audio) Remember, the string you supply to the lpszReturnString argument must be buffered with 128 spaces. "position" will give the current position in the audio file in milliseconds. "Length" will give the length of the audio file in milliseconds. "Volume" will give the current volume level. More on this later.
Code:Private Const Path As String = """C:\medeski.mp3""" Sub Main() Dim returnData As String = Space(128) Dim returnData2 As String = Space(128) Dim returnData3 As String = Space(128) Dim returnData4 As String = Space(128) mciSendString("open " & Path & " alias medeski" , "", 0, 0) mciSendString("play medeski", "", 0, 0) mciSendString("status medeski mode", returnData, 128, 0) mciSendString("status medeski position", returnData2, 128, 0) mciSendString("status medeski length", returnData3, 128, 0) mciSendString("status medeski volume", returnData4, 128, 0) 'The .trim gets rid of all of the excess leading and trailing spaces 'This should display "playing", the position in milliseconds in the song, and the ' length of the song (in milliseconds), and the volume level of the song, in the 'console, if a real path was used Console.WriteLine(returnData.Trim) Console.WriteLine(returnData2.Trim) Console.WriteLine(returnData3.Trim) Console.WriteLine(returnData4.Trim) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Using the Setaudio command
Syntax: (in string form)
"Setaudio " & (device) & (Many different possibilities)
The "setaudio" command is extremely versatile. It can be used to change the volume immediately or over a period, to manipulate the volume of the left or right channel, to do recording, set the bass/treble, etc...
All we will cover in this tutorial is how to change the overall volume, as this is the only thing I've really used. Refer to a full list of possibilities here:
setaudio (Windows)
Anyway, if you have played any sound with the MCI commands, you've probably noticed one thing. The volume is extremely loud! This is because the default volume is a level of 1000 - the maximum. The minimum volume is 0 - completely off. The syntax for changing the volume is:
"Setaudio " & device & " volume to " & (a number 0-1000)
Code:Private Const Path As String = """C:\medeski.mp3""" Sub Main() mciSendString("open " & Path & " alias medeski" , "", 0, 0) 'Lowers the volume to half the default mciSendString("setaudio medeski volume to 500", "", 0, 0) mciSendString("play medeski", "", 0, 0) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
This has barely scratched the surface of the various commands that can be used. For a full list, check here:
Multimedia Command Strings (Windows)
Troubleshooting
Remember when I was talking about the return value of the mciSendString function? It returns a 0 if successful, otherwise it returns a number corresponding to a specific error. We can use a related api function to interpret this error for us, and give us an error message.
A quick breakdown:Code:Private Declare Function mciGetErrorString Lib "winmm.dll" Alias "mciGetErrorStringA" (ByVal fdwError As Integer, ByVal lpszErrorText As String, ByVal cchErrorText As Integer) As Boolean
Return Value - returns a boolean representing whether or not the function successfully interpreted the error
This parameter takes the integer return value of the mciSendString function.Code:ByVal fdwError As Integer
Very similar to the lpszReturnString parameter of mciSendString. This takes a similarly buffered string, which will receive the error messageCode:ByVal lpszErrorText As String
Just like cchReturn - the number of characters the buffed string contains. Make this 128Code:ByVal cchErrorText As Integer
For the example, I'll try to open a file at a path that doesn't exist
This is very useful if you're trying to figure out how to do something, and aren't sure why it isn't working.Code:Sub Main() 'Will receive the integer return of mciSendString Dim mciReturn As Integer 'Will receive the error message Dim errorString As String = Space(128) 'bogus path mciReturn = mciSendString("open fdassh alias medeski" , "", 0, 0) mciGetErrorString(mciReturn, errorString, 128) 'Will write an error message in the console Console.WriteLine(errorString.Trim) Console.ReadLine() mciSendString("close all" , "", 0, 0) End Sub
Anyway, this is the end of the tutorial. I hope I've been able to successfully pass on some knowledge about using the MCI to play audio. If there are any questions, put them in this thread.
Note - there may be typing errors
Some additional resources:
mciSendString Function (Windows)
mciGetErrorString Function (Windows)
VB Helper: Tutorial: Introduction to MCI
Last edited by foedan; 01-11-2010 at 05:10 PM. Reason: Formatting, typos, improved phrasing
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks