Advertisment

Programming Sound on Linux

author-image
PCQ Bureau
New Update

All programs, be they Windows managers, media player or games, involve some kind of audio feedback. So why not a program in Linux include audio capabilities? We have chosen SDL(Simple DirectMedia Layer)for developing two basic applications-creating your own CD-player and playing waveform audio-to bring out the capabilities of cross-platform development. SDL is a multimedia library that has been ported to virtually all OSs, from Windows to Linux and Mac. It is a brilliant library for wannabe game programmers to add some sound effects to their games. So when you need to switch platforms, all you have to do is recompile the source with the appropriate libraries on the target platform. SDL does not only deal with sound, but also with all other aspects of multimedia like video, sound and input. It even supports 3D-accelerated contexts using OpenGL. Though this article focuses on sound programming, before you dive into the code, you must know

programming C on Linux so that you can change the code according to your needs.

Advertisment

Playing an audio CD



Let's begin with playing some tracks from an audio CD. To some, this may seem tougher than playing a wave file (covered in the next section), the SDL library makes it simpler. The code shown here is present in sdlcd.c and must be compiled with the command line 'gcc -l SDL -l pthread sdlcd.c'. Here, we have included two libraries-the SDL library and pthread library for multithreading on Linux using POSIX threads.

Direct Hit!
Applies

to:
C programmers
USP:

Create your own CD-player, program to play .wav files
Links:

www.libsdl.org 
On CD:

systems/labs/sound programming

nt main()



{


SDL_CD *cdrom;


.


//initialize the SDL system


if(SDL_Init(SDL_INIT_AUDIO|SDL_INIT_CDROM)==0)


.


//get the CD handle. 0 is default drive.


cdrom=SDL_CDOpen(0);


.


//list out the tracks and their lengths.


for(i=0; inumtracks;i++)


{


min=cdrom->track.length/(CD_FPS*60);


sec=(cdrom->track.length/CD_FPS)%60;


printf("\n%d.\t%d:%d",i,min,sec);


}


.


SDL_CDPlayTracks(cdrom,tno,0,1,0);


.


while(SDL_CDStatus(cdrom)==CD_PLAYING)


{


//display a simple progress bar


.


}





printf("\nThank you for using SDL_CD player\n\n");


SDL_Quit();


return 0;


}



























Advertisment

For this, we will first create a pointer of type SDL_CDROM and then initialize the SDL sound and CD-ROM sub-systems using the SDL_Init() function. Once this is done, we get a handle to the CD-ROM drive using the SDL_CDOpen() function call which feeds information about the CD-ROM in the drive into the CD-ROM structure. The SDL_CDStatus() call can also be used to determine the status of the drive, whether it is empty, playing, stopped or paused. We then list all the tracks and their duration in minutes and seconds using the numtracks and tracks <>.length fields of the returned CD-ROM structure. The user input track is then played using the SDL_CDPlayTracks() function and the program is made to loop as long as the status is 'playing'. We do this to ensure that the program doesn't terminate while the CD is playing. Instead of putting the processor to sleep in this loop, we decided to waste a few processor cycles in displaying a progress bar that extracts its value from the cur_frame field of the CD-ROM structure. 

To run the program, first put in an audio CD and then execute the program. It will show you a simple menu from which you can choose the track you wish to run. You can play the tracks by entering your choice, and if you don't, make sure that the audio cable from your CD-ROM drive is connected into your motherboard/sound card. You must hear some sound once a set of headphones is plugged into the socket provided on your drive. Interestingly if you kill the program, the CD continues playing. This indicates that the decoding is going on in hardware in your drive and there is no wastage of precious RAM and processor cycles. How can you expand on this program and enhance its functionality? For this, port it to QT maybe, and give it a cool frontend, and you have your very own CD player. 

Playing waveform audio



Playing audio CDs may be easy, but the truth is that it isn't for most applications. Waveform audio is used most commonly in games because it is raw and needs no decoding, thus, reducing the overall workload on the processor. SDL supports various forms of waveform audio, including the famous windows wave (.wav) file format.

Advertisment

The code to play, a wave file, is shown here and is part of sdlwav.c. As usual it has to be compiled with the '-l SDL -l pthread' flags. Also we need to make sure the wave file is in the same directory as the program file (as this sample program does virtually no error checking).

//globals



static Uint8 *buffer,*ptr,*ptr2;


Uint32 length;


Uint32 left;


int loop = 3;


//callback to fill audio buffer


void fillbuffer(void *userdata, Uint8 *buf, int len)


{


static Uint32 temp,temp2;


.


memcpy(ptr2,ptr,left);





}


loop--;


}


else


{


memcpy(ptr2,ptr,len);


ptr+=len;


left-=len;


}


memcpy(buf,ptr2,len);


return;


}


int main()


{


SDL_AudioSpec spec,*hwspec,*desired;


ptr2=(Uint8 *)malloc(16385);


//initialize the SDL system


.


//load the wave file into memory and set all parameters


if(!SDL_LoadWAV("./type.wav",&spec,&buffer,&length))


.


spec.size=length;


spec.callback=fillbuffer;


ptr=buffer;


left=length;


.


//open the audio device


desired=&spec


if(SDL_OpenAudio(desired,NULL) < 0)


{


.


}


SDL_PauseAudio(0);


while(loop>0)


sleep(0);


SDL_CloseAudio();


SDL_FreeWAV(buffer);


.


}
















































The above program consists of two basic parts-the main program and the callback function. In main() we do all necessary initialization and then load the desired wave file into memory using the SDL_LoadWAV() function. This function call loads the sound information into memory and also fills up an SDL_AudioSpec structure with all the details of the loaded wave file. The audio device is then SDL_OpenAudio()'d with the desired parameters which were obtained from the wave file. A NULL is passed to SDL_OpenAudio to make sure that we obtain the audio device in the exact format that we want, or else the call will fail. Once this call succeeds the audio device will plays silence so that we can fill the audio buffer in that time. We can play sound by calling SDL_PauseAudio(0). This is only half the program though. The more important part of the program here is the callback.

Advertisment

Every time the sound card is ready for more data, the callback function registered in the SDL_AudioSpec structure is called for. It sends the function a pointer to the buffer to be filled and the amount of data needed. Now all the callback function has to do is to memcpy() the required amount of bytes from the wave file buffer to the audio buffer in time. Once all the wave data is transferred, the system stops playing. So what should we do if we want the sound to repeat or loop? Simple, we make sure that the audio buffer never gets empty, ie once the wave data gets over, we simply shift the pointer to the beginning of the wave data and continue transferring. A word of caution (!!) would be to use block memory transfer functions and resist the temptation of byte-wise transfer using a loop. This is because such a transfer eats up too much time on the CPU. Further, you can also mix two audio streams with the SDL_MixAudio() function to get your own custom sounds. At the end we must admit that there was an easier way to play wave audio using the SDL mixer library, but those functions are built on the aforementioned functions and so they take away some power from the hands of the programmer. 

Although abstraction is good, directly accessing SDL's audio interface gives us more power, in a way that we can load the audio data, perform some custom processing on it and then play it, all of which is not possible using the mixer library. 

All of these examples need the SDL library installed on your system. Personally we have run all the code shown here on PCQLinux 2004; which has the entire SDL library installed. All header files are in the usr/include/SDL directory. If you are using any other distribution, you can check for sdl by simply typing 'locate sdl' on the command line (you may have to run 'updatedb' first to make the locate command run for the first time) and it will show you where SDL is installed. Under PCQLinux 2004, the entire documentation can be found in the KDE help pages under the UNIX man pages > Subroutines sections. If you prefer an HTML-based version, you can access this in /usr/share/doc/ directory where there are a few SDL directories which are of interest. Finally if you are really interested in learning more about SDL, contributing to the entire project or simply wish to download the latest version of SDL for Linux/Windows/Mac or even BeOS or Solaris you may visit www.libsdl.org, the official website for SDL and do so. 

Rakesh Iyer

Advertisment