7 posts tagged “audio”
Remember a long time ago we built a few neat audio tools together? Then if you remember even further back I talked a bit about my involvement with some helpdesk systems. Well, we're going to revisit pieces of those in order to build something spectacular. We are going to build our own home-grown conference software. Here's a few ideas I had in mind for this:
Chatters
Chatters are little dots that radiate sound. Whenever someone logs-in or is available to talk to, or is in the queue to be talked to, then the little dot pops up on your desktop. If you click on the dot you can get more information about the person and options to talk with them.
Group Chatter
When you want to hold a conference you simply have to group a few people up together into a roughly drawn circle. A brush on the desktop to draw that circlish figure and dragging your buddies in there. Of course, this would have to notify the intending parties that a conference is being held, to which they may or may not join.
Let's start with those first and work our way up.
In the absence of my coding articles I have created a starter kit for creating audio-visuals with the Bass.NET API. With this starter kit you will be able to have a nice starting point to work with Bass and different audio-visual techniques. The kit, once installed, can be run by hitting F5. You can immediately load up any of your favorite songs or a simple Shoutcast playlist. In a few of my future articles I will introduce some techniques for creating audio-visuals including: bars, waves and ambient lighting. Let's take a quick look at Adosi from where it stands.
Make sure that you have the following requirements before installing:
The Player
When you first start the program you will notice some of the interaction and animation touches that have been made. The entire interface has been designed so that when any audio-visual is added, any one of the components can be apart of the addition. There isn't much to it at this point, but we can however use it as a nice starting point. Let's go ahead and start modifying it. We are going to attempt to wire the bars-visual to the actual music.
Bars Audio-Visual
Before we can create our own audio-visual, we must first understand how most audio-visuals actually work. The idea is based on tracking two distinct variables in all audio: frequency and amplitude. In our case, to create an equalizer all we need to do is average amplitude values into a number of sections. The Bass api contains a simple method for obtaining all this data in the following snippet:
The first parameter refers to audio stream handle (known whenever you create a new channel stream) while the second parameter should be the buffer array created to hold the data itself. It's usually best to use a float array here but you can place doubles, bytes, ints or an IntPtr. The last parameter is the length of the data to receive from the stream. The best place to determine this amount is by using the following snippet:Bass.BASS_ChannelGetData(handle, buffer, length);
All you need to do to create the equalizer is create averages for the number of bars you want. Go ahead and check out the download for the completed version of the equalizer in my modifications below. The equalizer is the easiest to create, so in the future we'll have to get a little more serious in our visualizations.BASSData.BASS_DATA_FFT2048
- you can use any of the given values in the enumeration. Note the actual length in the comments (where 2048 FFT actually refers to 1024 allocated to a single buffer.
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.
Up until now, the tutorials on this blog have been about a few multimedia concepts including: streaming audio, recording from the microphone, playing internet radio, loading playlists and even networking. In this tutorial you will learn how to accomplish the following:
- Load a video from the file system and play it
- Visual C# Express
- Bass.NET 2.4 (click on forums under Bass, should be a topic on Bass.NET 2.4)
- Un4seen Bass api (version 2.4 beta) (click on forums, find bass 2.4 beta.)
- BassVideo (unofficial release of the bassvideo delphi api created by KSoft)
Once you have downloaded the above requirements you will notice that the BassVideo zip file contains a delphi library called BassVideo.dll as well as the delphi header. If you create a new C# Windows Form application and try to add a reference to this it won't work. Because the bassvideo library is not a managed library we can't simply add it directly. For this, we will need to create a wrapper according to the given delphi header. Open up the .pas file in notepad.
unit BassVideo;
interface
uses Windows;
const // video display
BassVideoDLL = 'bassvideo.dll';
BassVideo_Renderer = $100; // for BassVideo_GetConfig/SetConfigBassVideo_Default = 0;
BassVideo_VMR9 = 1;
BassVideo_VMR9_Windowless = 2;
BassVideo_EVR = 3;
BassVideo_Overlay = 4;
BassVideo_NOVIdeo = 5;
// action
BassVideo_OpenDone = 1;
BassVideo_Buffering = 2;
BassVideo_FoundVideo = 3;// BassVideo
type TVideoSync = procedure(Action, param1, param2, user : DWORD);
VIDEOPROC = procedure(Handle : DWORD; Action, param1, param2, user : DWORD); stdcall;
HSTREAM = DWORD;
TAudioInfo = record
Channels : integer;
Frequency : integer;
Bit : integer;
Float : BOOL;
SPDIF : BOOL;// reserverd, currently not supported!
DTS : BOOL;// reserverd, currently not supported!
end;TVideoType = (RGB8, RGB32, RGB24, RGB16, YUY2, YUV,
// reserved type, not supported at this time :(
YV12, YUV12, YVYU, UYVY, YUY9, Y800);//, 444P, 422P 411P );PCodecName = ^TCodecName;
TCodecName = array[0..100] of char;TVideoInfo = record
Height : integer;
Width : integer;
VideoType : TVideoType;
Codec : PCodecName;
end;QWORD = int64;
function BassVideo_ErrorGetCode : integer; stdcall; external BassVideoDLL;
function BassVideo_StreamCreate(FileName : Pointer; Flags, BassFlags : DWORD; CallBackProc : VIDEOPROC) : DWORD; stdcall; external BassVideoDLL;
procedure BassVideo_SetConfig(Option : DWORD; Value : DWORD); stdcall; external BassVideoDLL;
function BassVideo_GetConfig(Option : DWORD) : DWORD; stdcall; external BassVideoDLL;function BassVideo_Play(handle: DWORD; restart : BOOL) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_Stop(handle: DWORD) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_Pause(handle: DWORD) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_SetPosition(handle: DWORD; pos: QWORD): BOOL; stdcall; external BassVideoDLL;
function BASSVideo_GetPosition(handle: DWORD): QWORD; stdcall; external BassVideoDLL;
function BASSVideo_GetLength(handle: DWORD): QWORD; stdcall; external BassVideoDLL;
procedure BassVideo_StreamFree(handle: DWORD); stdcall; external BassVideoDLL;
function BassVideo_Init : BOOL; stdcall; external BassVideoDLL;
procedure BassVideo_Free; stdcall; external BassVideoDLL;// for video windows function
procedure BASSVideo_SetVideoWindow(handle : HSTREAM; VideoWindowHandle : THandle; VideoRect : TRect); stdcall; external BassVideoDLL;
procedure BASSVideo_Repaint(handle : HSTREAM; WinHandle : HWND; DC : HDC); stdcall; external BassVideoDLL;
procedure BassVideo_WindowResize(handle : HSTREAM; Rect : TRect); stdcall; external BassVideoDLL;
procedure BassVideo_WindowMove(handle : HSTREAM; Msg : DWORD; wParam, lParam : LongInt); stdcall; external BassVideoDLL;
function BassVideo_SetFullScreen(handle : HSTREAM; FullScreen : BOOL) : BOOL; stdcall; external BassVideoDLL;
implementationend.
Before I move into explaining what the above code is doing, I would first like to give a brief overview on the history of delphi and it's architect Anders Hejlsberg.
Delphi, officially Borland Delphi and chief architected by Anders Hejlsberg, is known as one of the first RAD tools when it was released in 1995 for 16-bit Windows 3.1. Delphi 2, released a year later, supported 32-bit Windows environments, and a C++ variant, C++ Builder, followed a few years after. Hejlsberg would later move to Microsoft in 1996, where he worked on Visual J++, and was a key participant in the creation of the Microsoft .NET Framework, becoming the chief designer of C#.
In 2001 a Linux version known as Kylix became available. However, due to low quality and subsequent lack of interest, Kylix was abandoned after version 3.
Support for Linux and Windows cross platform development (through Kylix and the CLX component library) was added in 2002 with the release of Delphi 6.
Delphi 8, released December 2003, was a .NET-only release that allowed developers to compile Delphi Object Pascal code into .NET CIL. It was also significant in that it changed its IDE for the first time, from the multiple-floating-window-on-desktop style IDE to a look and feel similar to Microsoft's Visual Studio.NET. But still, the new IDEs have the "Classic Undocked" windows layout available, for a classical look and feel.
Okay, now that that's underway, we can see that we can compare a few object-oriented concepts from C# to that of Delphi. The first on the list is constants.
All of the variables BassVideoDLL, BassVideo_Renderer, BassVideo_Default are considered constants in delphi. As such, we can create those constants in our own class, BassVideo.
In the delphi code, we see that it specifies a procedure with the parameters: Handle, Action, param1, param2 and user; followed by assigning it to the variable name VIDEOPROC. The equivalent is the delegate shown above. You'll notice that the HSTREAM is being assigned to DWORD (also known as our int in C#). This is simply allowing types to translate or cast implicitly. After that, there is a record being specified.
TAudioInfo = record
Channels : integer;
Frequency : integer;
Bit : integer;
Float : BOOL;
SPDIF : BOOL;// reserverd, currently not supported!
DTS : BOOL;// reserverd, currently not supported!
end;
A record, in the plainest since is the equivalent of a struct in C#. We can replicate this in the following code snippet.
Next, the delphi header specifies the equivalent of an enumeration with the following snippet:
TVideoType = (RGB8, RGB32, RGB24, RGB16, YUY2, YUV,
YV12, YUV12, YVYU, UYVY, YUY9, Y800);//, 444P, 422P 411P );
We can replicate this like we would create any enum as in the following code snippet.
Now that that's done we can move onto functions and procedures. A procedure in delphi is a method that does not return anything in C#. (Or it returns void). The only catch here is that in order to use each of these functions as a reference from C#, we need to use the DllImport attribute. We can import any unmanaged dll into C# by using this attribute in the following manner.
[DllImport("nameofdll.dll")]
Awesome! Now all we have to do is use the api in our media player. I'm going to use the previous MyRadio application and add a few things to get it working. Open up your previous project and add the bass (version 2.4) and bassvideo library to your solution. Make sure to set the Copy to Output Directory to Output if newer. Then, replace the Bass.NET library reference to the new 2.4 version. Your solution explorer should look something like the following.public class BassVideo
{
// Video display
public const string BassVideoDLL = "bassvideo.dll";
public const int BassVideo_Renderer = 100;public const int BassVideo_Default = 0;
public const int BassVideo_VMR9 = 1;
public const int BassVideo_VMR9_Windowless = 2;
public const int BassVideo_EVR = 3;
public const int BassVideo_Overlay = 4;
public const int BassVideo_NOVIdeo = 5;// Action
public const int BassVideo_OpenDone = 1;
public const int BassVideo_Buffering = 2;
public const int BassVideo_FoundVideo = 3;public delegate void VIDEOPROC(int Handle, int Action, int param1, int param2, int user);
public struct TAudioInfo
{
public int Channels;
public int Frequency;
public int Bit;
public bool Float;
public bool SPDIF;
public bool DTS;
}public enum TVideoType
{
RGB8, RGB32, RGB24, RGB16, YUY2, YUV, YV12, YUV12, YVYU, UYVY, YUY9, Y800
}public struct TVideoInfo
{
public int Height;
public int Width;
public TVideoType VideoType;
public IntPtr Codec;
}[DllImport("bassvideo.dll")]
public static extern int BassVideo_GetErrorCode();[DllImport("bassvideo.dll")]
public static extern int BassVideo_StreamCreate(string FileName, int Flags, int BassFlags,
VIDEOPROC CallBackProc);[DllImport("bassvideo.dll")]
public static extern void BassVideo_SetConfig(int Option, int Value);[DllImport("bassvideo.dll")]
public static extern int BassVideo_GetConfig(int Option);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Play(int handle, bool restart);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Stop(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Pause(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_SetPosition(int handle, long pos);[DllImport("bassvideo.dll")]
public static extern long BassVideo_GetPosition(int handle);[DllImport("bassvideo.dll")]
public static extern long BassVideo_GetLength(int handle);[DllImport("bassvideo.dll")]
public static extern void BassVideo_StreamFree(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Init();[DllImport("bassvideo.dll")]
public static extern void BassVideo_Free();[DllImport("bassvideo.dll")]
public static extern void BASSVideo_SetVideoWindow(int handle, IntPtr VideoWindowHandle, Rectangle rect);[DllImport("bassvideo.dll")]
public static extern void BASSVideo_Repaint(int handle, IntPtr WinHandle, IntPtr DC);[DllImport("bassvideo.dll")]
public static extern void BassVideo_WindowResize(int handle, Rectangle Rect);[DllImport("bassvideo.dll")]
public static extern void BassVideo_WindowMove(int handle, int Msg, long wParam, long lParam);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_SetFullScreen(int handle, bool FullScreen);
}
After that, open up the Form that you will use to display the video. Add a picture box to the form. Here is mine:
Now that we have something the display the video on, we need to modify our load code to be able to take in a video. Since there are plenty of types, we need to know whether a file is a video. Luckily, we can determine the supported types from the Bass api using the variable SupportedStreamName. This property shows the possible music extensions supported by the Bass api. I have created a function in the Player class below.
Now, in order to support this case when a file gets loaded we need to modify our original Load function. I have changed the original Load function to simply load in the playlist or add to it and the code that actually loads the file is in the LoadIndex function (called by the form when an index is selected on the playlist).
Since streaming video from the internet is currently unavailable to the api, I use the IsLocal to determine whether or not we need to use the LoadVideo, LoadSong, or LoadURL functions. Both the bassvideo and bass api use our stream declaration in the Player class. Hence, we need to declare a state when a video is loaded. Once we have that boolean set we can create the LoadVideo function below.
Notice the callback variable being used in the BassVideo.BassVideo_StreamCreate method call. The callback is a delegate reference whenever the video is processed on certain actions. If you remember, one of the parameters in the delegate is Action which is an integer. We can compare this to processing states also declared as constants in the api like BassVideo.BassVideo_FoundVideo. When we have that we can call the BassVideo.BASSVideo_SetVideoWindow to our picture box that we declare before. Since the picture box is something in the form, we need to create this function in our form code.
Since this code is in our form code, we need to let the Player class know about it. I have modified the constructor to include the BassVideo.VIDEOPROC as a parameter. I have also added a field variable called callback.
NOTE: Bass.NET version 2.4 updated the Bass_Init to use IntPtr when referring to the user. I have specified IntPtr.Zero.
In the constructor here, I am also initializing the BassVideo api by calling the BassVideo.BassVideo_Init method. The last thing we need to do is modify our Player to use either Bass or BassVideo depending on the videoLoaded boolean field. This is modified in the FreeStream, PlaySong and StopSong methods.
The very last thing we need to do is update our destructor to free the bassvideo api by calling the BassVideo.BassVideo_Free method.
And that's it! Hit F5, load up a video and see your new beauty shine. In this tutorial you learned about a new bass video api developed by a member of the Bass api community. You learned about delphi and even converted a delphi library header into C# not to mention you completed another aspect of multimedia within your media player.
[Source Code]
In the previous tutorial I taught you how to use Bass to stream audio from the internet. While our radio player is amazing enough, there is, however, a missing factor in it pertaining to playlists. In this tutorial you will learn:
- How to load a playlist from an internet radio station
- How to create your own playlist
This tutorial builds off of the previous one and so you will need the following requirements.
- 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)
Most internet radio stations like to play in playlist format. Well, if you look around, all internet radio stations use some form of playlists. The nice part about conforming here is that we can reuse the playlist again when we put together our polished C# Media Player.
While there are a few streaming formats for playlist (ASX, M3U, XSPF) the most common playlist format is PLS. PLS as a playlist format is a fairly expressive format. The specification is listed below (via Wikipedia):
Header
- [playlist] : This tag indicates that it is a Playlist File
- NumberOfEntries : This variable indicates the number of tracks
Track Entry
Assuming track entry #X
- FileX : Variable defining location of stream.
- TitleX : Defines track title.
- LengthX : Length in seconds of track. Value of -1 indicates indefinite.
Footer
- Version : Playlist version. Currently only a value of 2 is valid.
[playlist]
NumberOfEntries=3
File1=http://streamexample.com:80
Title1=My Favorite Online Radio
Length1=-1
File2=http://example.com/song.mp3
Title2=Remote MP3
Length2=286
File3=/home/myaccount/album.flac
Title3=Local album
Length3=3487
Version=2
Knowing this format, we can read it in with a simple PLS parser to load in any PLS supported radio station. Specifically, we can support Shoutcast stations in our little media player. First, I will create a new class called PLSHelper. Using the StreamReader class, we can read in the playlist and determine where all the songs are located as well as their associated titles. First, declare the variables that are included in a playlist.
Next, create the readonly property for each of the above variables. After that, we need to actually load the playlist. The following code snippet will accomplish this using the StreamReader class.
Essentially, we split the entire playlist string by the given name. Since there is only one of the given name in the entire playlist it is safe to assume that there will be two splits. So on NumberOfEntries=, the first split string will contain "[version]\n" and the second split will be the rest of the playlist string. If there is more than one of the given name it still won't matter since we're just interested in the second index of the array. Next, we just need to find the next index of the non integer in the second string. This is done in the IndexOfNonNumber method.
Pretty simple. We just check each character until we find a character that is not a number; followed by returning that index. We use that index as the length. After that we will simply substring the playlist split to get the variable. This is very similar to how we will obtain all the files. Next, we just need to make two calls back in the LoadPlaylist method.
Lastly, the CreateProperties method. This method will need to run a loop to parse out each variable of a playlist entry.
First, we create the array to store the playlist entry variable. Next, we will split on the given name. Since the first split is always going to be "[version]\nNumberOfEntries=3\n" we start at i + 1. Then, we need to remove the start of the split to exclude the number and the equals sign. So, the split on File will result in:
After that, we just need to substring until we find the break line sequence denoted by "\n". That will get the entire variable entry that we need at each instance in the playlist. Lastly, we just need to make the call to the method on each entry variable.1=http....
2=http...
3=http....
That's it! Now we can use this little helper to load any standard playlist. We just need to make a modification to our Player class to load in a playlist and use this class when it receives a location.
There, I've added the PLSHelper as a field level variable and I created it in the constructor of the Player. In this method, I am checking what is being loaded (either a playlist, local song, or online song). Also, I've added a readonly property to the PLSHelper in the Player class to allow access from our user interface code. Now that we have a playlist, we should add something to our user interface to make it useable. I've added a listbox. Your form may look similar.
Next, we need to change our load code. Right now, our load code is calling the LoadSong method; instead, we need to call the Load method we just created. After that, we need to update our listbox to display what's in the playlist.
Lastly, we need to update what's being played by the selected item in the listbox. Use the SelectedIndexChanged event on the listbox for this. Then add the following code snippet.
And there you go. The last thing we need to do is update our playlist to include single songs that we load instead of a whole playlist. Modify your Load code in the Player class to the following snippet.
Not much left. Whenever a song is loaded directly, we need to clear the playlist, followed by adding the song to the play list. This is done through two helper methods in the PLSHelper. First, the clear playlist:
public void ClearPlaylist()
{
locations = new string[0];
titles = new string[0];
lengths = new string[0];
version = 2;
pls = string.Empty;
count = 0;
}
And that's it! Call the Add method from your Player class and you should be good to go. Hit F5, load up a playlist and see your creation shine. In this tutorial you learned how to use internet radio playlist standards to load in any .pls file. As well, you were able to create your own playlist in the making of. As an extra bonus, add save functionality to your list; shouldn't be too difficult just remember to follow the playlist standard.
UPDATE: The source code also includes file loading with default program settings and it will run in a single process.
[Source code]
Internet radio, a phenomena that appeared in the early 90's that soon made it's way to it's grand stature today. Sure politics are trying to destroy it, but it's not going away any time soon. There is a great deal of history in Internet Radio from war protests to social networking. The latest advent in internet radio, Pandora, from the Music Genome Project, combines the analysis of each song to provide users the ability to listen to similar music; thus creating their own stations based on style, rhythms, lyrics and more. Nevertheless, I suggest you review the history on Wikipedia. I believe the next step in Internet Radio is playing from your mobile phone which doesn't seem that far off. [note project]
At any rate, our little music player doesn't seem to support internet radio at all. Well, I simply won't have that! Especially because supporting internet streams is so very simple with the Bass.NET api. In this tutorial you will learn to accomplish the following:
- Play streaming audio from an internet radio station.
- 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)
I am going to create a new Windows Form project called MyRadio. To start, you will need to add the reference to the Bass.NET api. Then copy the bass.dll to your output directory by including it in your project followed by setting the Copy to Output Directory property to Copy if newer. Your solution explorer should look like the following:
Once you have that setup look for the Player code from the previous tutorial. If you can't find it, you will find the code layout here.
We are going to modify this class to support internet radio streams. Open up the Player class and add the following code snippet.
public void LoadURL(string url)
{
stream = Bass.BASS_StreamCreateURL(url, 0,
BASSStream.BASS_SAMPLE_FLOAT, null, 0);
}
Bass provides this function for loading a stream from a url. In most cases, it won't matter if the stream is a file or a broadcasting stream; bass handles this for you.
You'll notice that this function looks a lot like our original. Except, instead of StreamCreateFile we are using StreamCreateURL. With the fourth parameter, specified as null, is a callback to the function to receive the stream as it's being downloaded. Also, the BASSStream contains a few flags that may allow us to obtain tags about the music including the title, author, genre and more. There are many flags that have a very specific purpose, but for now we are just going to use the default like in our previous case.
The only thing left for us to do is to provide access in our user interface. I have simply copied our previous UI and added a textbox to allow the url to be entered. When you're done, your user interface may look like the following.
There you go. Hit F5 type in an address to an internet radio station or an hosted song online and you should be good to go. In this tutorial you learned how to use Bass to stream internet radio. In my next tutorial I will be teaching you how to create your own playlist allowing for even more support for internet radio stations.
You will need the following requirements to complete this tutorial:
Recording from the Microphone
Recording: the process of capturing data or translating information to a format stored on a storage medium (or record). Lucky for us, analog-to-digital converters (ADC) take care of the translation for recording devices like a microphone. The only catch for us is providing a sampling rate against the analog wave. We used sampling when we streamed audio in our previous tutorial and we'll use it again here. The only difference about sampling from a recording device is that we are working with a different device. The code looks just about the same.
Setup a quick project that you can use for this tutorial. I created a new Windows Form project. You can create a whatever you like to complete the tutorial. Next, add a utility class called "RecordUtil.cs". Then add all the necessary references, make sure you set the Copy to Output Directory to "Copy if newer".
Add references
Taking a look at the left. Your solution explorer should contain the additions I mentioned before. That is:
- bass.dll
- bassenc.dll
- lame.exe
Having those in your project and output directory allows the final executing code to reference them without any issues running on different machines.
Next, we need to initialize the actual recording device. In the constructor of the RecordUtil place the following code snippet.
By initializing with the device 0, we will use the first recording device found by the Bass api.
Next, we're going to create the recording stream. You will need to set the sampling rate, the number of channels, BASSRecord flags, a callback function, and a user. The following code accomplishes this.
The first parameter is the sampling rate. This is the rate at which the analog wave is sampled (41000 times per second). The second parameter is the number of channels: 1 is mono, 2 is stereo. The third parameter is either the resolution (BASS_DEFAULT 16-bits, BASS_SAMPLE_8BITS 8-bit, BASS_SAMPLE_FLOAT - all of these choices will start the recording) BASS_RECORD_PAUSE starts the recording paused. Lastly, the fourth parameter is the callback function on each sample and the last parameter is the user associated with the recording.
Automatic versus manual recording
BASS has two methods of recording audio. When you do not specify null for the RECORDPROC callback function you must obtain the actual channel data using the BASS_ChannelGetData method. This is known as manual recording. Manual recording allows lower latency for full-duplex recording (i.e. playing the data as it's being recorded).
In an overloaded method for the BASS_RecordStart function, the third parameter is actually the period. The period defines the interval (in milliseconds) between calls to the callback function. The default is 100ms. (Minimum is 10ms, Maximum is 500ms).
If you do specify a callback to the RECORDPROC parameter, you are implementing automatic recording. All the data is obtained for you.
Then create the callback function and within the method you can simply return true. For the sake of this tutorial I will go into detail at a later post to explain the neat things you can access with the data.
LAME Encoder
In order to start recording with the parameter we chose, we need to use Channel_Play. But before we do that we are going to use the LAME encoder to stream the recording to an output file. Add the following code snippet:
I have added a field in the class called encoder. The EncoderLame class is in the Un4seen.Bass.Misc namespace. The first line simply constructs the encoder with the streaming channel (in our case the recording stream). Next, we set the OutputFile to the given path denoted output. The last lines of code are LAME specific variable configurations.
Aside from the bitrate and sample rate, Mode defines the encoding channel mode as in mono, stereo, join stereo.. etc. There are plenty of variables that you can configure, I have just configured the basics in this example. We want to sample at the same rate we created our recording channel at, use the default mode, encode according to quality, and have our bitrate at 192kbps (kilobits/sec).
Next, I have added four methods to handle different states in our recording program. They start out as the following:
The code here is pretty straight forward. When we call encoder.Start(null, 0) we are passing it a callback function and the associated user. The callback function allows us to work the encoding byte stream at each interval ourselves. This is very useful for us when we want to send the stream elsewhere like over a network.
The only thing left for us to do in this class is add the code for the Bass.Channel functions like we did in the previous tutorial on streaming audio. When you have that part done the final code should look like the following.
And there you go, the only missing piece is creating an interface the utilize this nice little recording utility. I've thrown a simple interface together to demonstrate this. When you're done with yours simply call the appropriate functions in the order: constructor, Initialize, Encode (with the output file), and setup a play, pause, and stop function.
Once you have your interface ready, you can make the calls to the functions you just created. When you're done your code should look something like the following code snippet.
Source code