2 posts tagged “streaming audio”
If you remember from my previous tutorial on Recording audio in C#, streaming from the microphone was quite simple. Within about 10 minutes of coding you were able to create a recording application that worked with any recording device plugged into or connected to your machine. In another article, I talked about my XNA TCP Networking API. In this article, I'm going to show you how easy it is to create your own voice chat application using the two apis. In this article I will be teaching you the following techniques:
- Streaming audio from the microphone
- Sending audio data over the network
- Playing network audio data back
- Visual C# Express
- TCP Networking API
- Bass.NET 2.4 (download the zip in the forum)
- Un4seen Bass 2.4 (download the zip in the forum)
It's been a while, but in my Recording audio in C# sample I used an encoder (specifically the LAME Encoder) to encode data to an Mp3 via the recording stream. Since we will not be needing any encoding we won't need the lame encoder, as well we will not need the bassenc library. To start create a new Windows Form Project, VoiceChat.
Since we need the bass.dll included in our solution, add it as en existing item. Be sure to set the property Copy to Output Directory to Copy if newer. Finally, add the Bass.NET 2.4 reference located in the .NET References tab by the Add Reference dialog. You will also need to add a reference to the Network API that you downloaded as a requirement. You can browse for the library to where you unzipped it to.
- Init - Should initialize recording and audio devices
- Record - Should create the playback and recording streams
- Play - Should play the playback and recording streams
- Stop - Should stop all playback and recording
- Free - Should free all resources and streams
- PlayBuffer - Should play a byte stream buffer
- SendBufferHandler - Delegate for sending a buffer.
This method will first initialize the default recording device followed by initializing the default audio playback device and allocating that device stream by the 8Bit flag or byte flag. The 8Bit flag correlates to our byte output stream that we will later deal with on the recording callback function. Next, the Record method.
Within this method there are three field level variables being used: myRecordProc, recordStream, and playbackStream. The first, myRecordProc, is the delegate that refers to the callback on each sample recorded from Bass. The second, recordStream, refers to the actual handle created when starting the recording channel and it is designated as an int. You can see that in Bass.BASS_RecordStart the first parameter refers to the sampling rate (44100), the second as mono output, the third as the starting flags, the fourth as the period (in milliseconds) between each sample callback, the fifth referring to the callback, and the user.
Lastly, playbackStream, uses a new feature in Bass 2.4 called push streaming. Push streaming gives us the ability to create a stream and effectively push a byte stream buffer in order to create playback. Nice, huh!? We simply pass in the frequency (sample rate) of 44100, set the channels to mono, pass in the default bass flag, and the user.
At each sample callback, we will reallocate the local myBuffer byte array when either the local buffer is null or the sample buffer length is greater than the local. It would be unwise to allocate at every interval as new can be quite costly. Next, we use the Marshal.Copy method to copy the IntPtr buffer memory into our local myBuffer at index 0 and the length being the parameter length. Lastly, we simply trigger our callback function passing in the two variables.
As I mentioned before Bass provides a new push streaming feature to actually provide the stream with a buffer in order to playback. This is done with the Bass.BASS_StreamPutData method.
The last thing we need to do for our RecordUtil is provide the Play, Stop, Free and destructor methods. We will use the corresponding Bass calls to perform each action.
And there we have it! A fully functional recording utility that we can use for the rest of this project. We need a user interface to support what we want from our application, let's design that now. At the very least, our application will be able to connect to an existing voice chat session or create a local one.
Each button refers to a call associated with the Session class located in the Networking api. Let's move into that now.
Networking
This TCP Networking API was built with bare minimum requirements for a stable TCP Network implementation. It is enhanced with a few features like multithreaded callbacks with the ability to throttle expected transfer rates. The newest addition is the Session class. This class allows you to create servers and clients fairly easily. Create a new class called NetworkUtil. We will add our network layer to the application within it. We will accomplish the following:
- Constructor - Should call Session.Init to initialize the network api.
- Join - Should call Session.Join passing in the destination ip and port
- Leave - Should call Session.Leave passing in the session id
- Create - Should call Session.Create passing in the local service port to start on
- Close - Should call Session.Close passing in the session id to close the server
- Send - Should call Session.SendData passing in the byte stream buffer and session id (as a client)
- Send - Should call Session.SendData passing in the byte stream buffer, client, and session id (as the server)
As you can see, it makes since to stop recording and sending data when we're not connected and vice-versa we should start sending and recording as soon as we are connected. The only issue here is that we would be coupling our RecordUtil with our NetworkUtil if we coded it this way. We need to create two events. Start and Stop.
With these two events, we can reuse them with our AsyncServer component. To finish, we need to replace the comments with the triggering of these two events. We can also fill in our Leave method at this time.
Next on the list is the Session Create method. Creating your own session works very much like joining one. The only difference is that you will be creating the session on your local ip address and you pass in the port. We can use the same StateChange event in the AsyncServer class located in the Session.OpenSessions dictionary. However, we will only need to worry about the ServerState.Shutdown to Stop. Starting will depend on when a client joins, as well stopping when our only client disconnects. Wire to the events and fill in the code below.
Almost done, we need to fill in the Send method. Since the data will be virtually the same, we will follow the delegate heading for SendBufferCallback in the RecordUtil class. The next step will be to write the length into a new packet along with the buffer being sent. After that, we can send the data to it's rightful owner.
The nice part about the Networking API is that sending data can be as seamless as you like. The SendData in the Session class is overloaded. You may send the data to a specific destination, or send the data to the the endpoint. The key on this method is that if you attempt to send data as a server, it assumes you mean to Broadcast the data. You can access the all the clients within a given created server with the OpenSessionClients property. Also, if you're not connected the session class will simply throw your packet away before even getting to the sockets.
In the last part, receiving the data, we will create one final event called Receive which will effectively just pass the byte stream buffer. If you noticed in the Send method we wrote the length to the buffer and then we wrote the actual byte stream based on the length provided.
To receive the actual data, we need to first find out the length of the buffer. Then once we know that, we simply grab the bytes from the data and trigger the Receive event. (Simply declare a new EventHandler called Receive at the top). You'll notice that in each of the Receive and Send I am using the number 4. I use 4 because an int is four bytes.
Finale
Excellent! I think we're ready to finish off the last details in our application. Open up the code-behind for Form1.cs. Declare two variables at the top for RecordUtil, and NetworkUtil. Next, override the onload method. In this method, create initialize the two variables and wire to each of their events. As well, we will call the RecordUtil's Init method. Be sure to wire NetworkUtil's SendBuffer event to NetworkUtil's Send method.
To ensure that our UI looks superb we will disable portions when the session has started, and enable them when the session has stopped. We can also change the text of the buttons from "Join" to "Leave" (or "Create to Close") and vice-versa. With each event in the Form code there won't be much code to insert.
When the networker determines that the session can start, the recorder can begin recording. As well, the ui can then be disabled via a method we will create in a moment. When the networker stops, the recorder should stop the recording and free the streams; as well, the ui can be enabled.
NOTE: Because this is called asynchronously we need to use the BeginInvoke method to switch to the UI thread when we are using it. To do this we simply use the EmptyDelegate that we declared at the top and use anonymous syntax to invoke.
Now we can fill in the Enable and Disable ui methods. The buttons will change text depending on which was pressed and all the other fields will be enabled or disabled.
The ui is changed based on which button is enabled (btnJoin or btnCreate). In order to make the right calls to the networking class, we need to first add the button click methods to our code. The easiest way to do this is check the text of the button to determine what action to take. ("Create" should call create, "Close" should call close, "Join" should call join, and "Leave" should call leave.
We need to call network_start when we join a session because the api will already have finished it's code structure before we can even wire to the event. As well, we need to call disable ui when btnCreate is pressed because the start event is triggered when the client joins.
We're Done!
Congratulations! You have successfully created your own Voice Chat program. To test it, put the executing assemblies on one computer and another on your computer. To determine what your IP address is, just type ipconfig in the command prompt. (Your firewall may ask you to allow the service to run.) If you both have microphones you should be hearing each other talk with an incredible quality of sound. There are plenty of upgrades we can make to this program, but we got the basics across.
There are plenty of instances where playing sounds, music or virtually anything else sound related is an absolute necessity. The problem, however, is the availability in C#.NET is limited. Fear not, I have a solution!
The following post introduces the BASS.NET Api for streaming sound. In this tutorial you will learn how to:
- Load Mp3 files and play them
- Visual C# Express (sorry, Visual C# Express 2005 is no longer available)
- Bass.NET 2.3 (scroll down until you find the .NET 2.0 link)
- Un4seen Bass api (top download link for Windows)
Within the .NET development environment the availability of playing audio is limited. For one, the SoundPlayer that ships with the .NET framework will currently work with wav files (understandable because of license agreements). Once more with WMA you have to use the Windows Media Player control that ships as a Com object. Nevertheless, there are altenatives. As stated on there website:
BASS is an audio library for use in Windows and Mac OSX software. Its purpose is to provide developers with powerful and efficient sample, stream (MP3, MP2, MP1, OGG, WAV, AIFF, custom generated, and more via add-ons), MOD music (XM, IT, S3M, MOD, MTM, UMX), MO3 music (MP3/OGG compressed MODs), and recording functions. All in a tiny DLL, under 100KB* in size.
On Windows, BASS requires DirectX 3 or above for output, and takes advantage of DirectSound and DirectSound3D hardware accelerated drivers, when available. On OSX, BASS uses CoreAudio for output, and OSX 10.3 or above is recommended. Both PowerPC and Intel Macs are supported.
If you have everything downloaded and installed in order then you are ready to begin. Start by creating a new windows project.I've named my project BassAudio. Right-click on your project and add the following reference.
This dll should be wherever you downloaded it to on your machine. If you forgot to download it see the third download link I mentioned above.
Next, you will want to add whatever song you like. I'm using an Mp3 song that I have on my machine. Just make sure the file will copy to the output directory.
Now that everything has been setup. We can start coding to the api.
Within the Bass api there are a number of varying things you can do with audio devices. For instance, you can work with 3D audio including velocity, position of the emitting sound, position of the listener, doppler effects... and much more.
I recommend taking a look at all that's available to you in the Un4seen.Bass.Bass functions. The nice part is that the majority of all the methods in this api are within the Bass class.
Whenever you're ready, initialize the default device with -1 for the first parameter.
The second parameter is the sampling rate. I recommend looking up the definition on either Wikipedia or HowStuffWorks since I'd rather not explain this part. Basically your audio device has a rate at which it can sample whatever particular input you're working with. Speakers will sample your song at such a rate, or your microphone will sample the audio coming in at such a rate. 44100 is just about the best quality for sampling you can get.
Creating the stream is pretty easy. All you have to do is call the function:
The parameters to this function are pretty much self-explanatory when you read the comments. The first is the location of the file, the second parameter is the offset to begin the streaming from, the length is how long to stream (0 is all), the last parameter is the BassStream flags. I recommend taking a look at the comments in the enumerations. They are all very helpful.
Note: When you work with unmanaged code, you have to be sure you save your references; otherwise, the .NET garbage collector will destroy the reference. To do this, I've simply placed the int stream at the top of the class.
Add a PlaySong, StopSong, and a destructor to your Player class.
The destructor in this class makes sure that all the resources are let go from the unmanaged part of the bass api. Okay, now that we have a simple player setup, let's actually use it! :D
By the end of your form you might have something like this:
And there you have it. Hit F5, load your song and click play. You should now be hearing you song.
In this tutorial you learned how to use an alternative sound api to stream sound to a audio device of your choosing. You figured out what sampling meant and you created a nice reusable sound player. In the my next audio tutorial, I'll show you how to use BASS to show a list of all your audio devices. Then we'll get into some microphone recording later on. Leave and questions and comments here if you like.