|
How to write an MPEG audio player using mpega.library and AHI Why write your own mpeg player code? Four years ago I had undertaken the job of making an HTTP client to handle Shoutcast radio stations. The idea was to look at the only streamer program at the time, an AREXX script that sent audio to the MPEGA command-line player through a pipe. A pipe is a first-in-first-out buffer that can be handled as a file. In other words, you can fread() and fwrite() to a pipe by open() ing a pipe file for read or write: FILE *fp=NULL; The pipe works by using a special handler called l:queue-handler and you can change how much data the PIPE device will buffer and other options by editing DEVS:dosdrivers/PIPE. By default, pipe buffers 4KB. When the 4KB is filled, PIPE will hang onto it until something reads the data. As the data is read, pipe disposes it and you can write more data into the pipe. With simultaneous reading and writing, the PIPE functions as a 'scratchpad' for data. This is a helpful tool for streaming data from the Internet, because you do not need to save the data to a file and play the file after all of the data has arrived; you can start a player and tell it to open a file from PIPE: so that it can play as data is being downloaded. The first version of streamer relied on an external program to play the audio. This has advantages and disadvantages. The advantage being that you could use a PPC audio player, which will take advantage of your PowerPC processor if you have one. The disadvantage is that streamer does not have control over the program. You can't handle any error messages that the program generates, and if the player exits for some reason, streamer will not know and continue streaming data while you hear nothing. It was pretty clear that I needed to write an integrated MPEG player process, but how? Luckily, there are libraries that can make things easier on you by performing some of the basic tasks for you, thereby simplifying your code. Why add hundreds of lines of code to a program when you can re-use code that was written by an expert-in-the-field already? The two libraries are mpega.library and ahi.device. mpega.library basics mpega.library is an external library that converts MPEG layer 1,2, and 3 to Pulse Code Modulation (PCM) audio. PCM essentially means raw audio, either in 8 or 16-bit samples. It's a very common misconception in the user community that mpega.library plays audio. Using mpega.library involves a few steps. First, we fill in an MPEGA_CTRL struct. This tells the library how to behave. MPEGA_CTRL
mpa_ctrl = { Next, we open the library using OpenLibrary, and tell mpega.library to open the stream: mps = MPEGA_open( in_filename, &mpa_ctrl ); mps is an MPEGA_STREAM structure that can tell us the volume, bitrate, and a bunch of other stuff about the stream. It really important for us to know the number of channels because AHI needs this information, and we need to change the way the data is buffered for stereo and mono streams. We decode the audio by looping MPEGA_decode_frame: pcm_count = MPEGA_decode_frame( mps, pcma ); Looking at the code, you'll notice that pcma is an array of two buffers: pcma[0] and pcma[1]. mpega.library is going to write the data from the left channel into pcma[0] and the right channel into pcma[1]. Unfortunately, this is incompatible with AHI! If the stream is stereo, we set our AHI's PCM buffer (pcmLR) by alternately writing from the left and right channels: pcm0 = pcma[ 0 ]; for (iCount=pcm_count;
iCount > 0; iCount--) { If this stream is in mono, pcma[1] is empty and we can just copy pcm0 to pcmLR for AHI to use. The language of MPEG audio deals with 'frames' of audio. The size, or number or 16-bit samples in a frame, depends on the frequency division and the number of channels. The maximum number of samples in a frame is 1152. If the MPEG stream is in mono, you can half that to 576. Furthermore, if you've set a frequency division of 2, your stream has 288 samples per frame. This is why halving or quartering the frequency division variable can have such an effect on CPU usage. AHI.device basics To get the PCM data pumping through your speakers, you need to write the data to the audio hardware. While it would be possible to write directly to the Amiga's audio hardware, this is not desirable because lots of people have got sound cards, and also the ancient Paula chip does not support neat things like mixing as it can only play one audio stream at a time. With AHI, you're provided with a level of abstraction. You write to AHI device, and AHI will write to a sound card, the native hardware, or even to a file. These options are user-configurable. AHI also performs the software mixing duties so that more than one sound can be played simultaneously. AHI provides four 'units' for audio. This makes it possible to have a program play on the native hardware, and another play on a sound card by attaching the appropriate AHI driver to a unit number. For the software developer, AHI provides two ways to play audio. One is the AUDIO: DOS device. AHI can create a volume called AUDIO: that works like an AmigaDOS volume. You can read and write data directly to it and it plays through the speakers. This is the easiest way to write PCM, but it's not the best. First of all, if a user takes the AUDIO: entry out of the mountlist, your program won't work and you get bombarded with stupid support questions like 'I've got AHI, why doesn't it work?'. The better option is to send IORequests to AHI. This allows you to control volume and balance settings while the program runs (with AUDIO: you set these when you open the file and you can't change them without closing and re-opening AUDIO:) and you can use a neat trick called double-buffering to improve efficiency. Double-buffering allows you to fill one audio buffer while another one is playing. This kind of asynchronous operation can prevent 'choppy' audio on slower systems. You can see how we initialize AHI in the code. Once that's done, the real work is done by preparing and sending the AHI request to the ahi.device. It's very important to calculate the number of bytes you want AHI to read from the buffer. You can cause a nasty crash indeed if it's incorrect! To do this, multiply the PCM count by the number of channels by the number of AHI buffers (this is defined earlier in the code). You can see that a lot of the information that AHI needs can be obtained from the mps structure we got from MPEGA_Open... if (pcm_count>0)
length = pcm_count*mps->dec_channels*sizeof(WORD)*AHI_BUFFERS; A quick note about volume and position: AHI uses a fairly arcane datatype called Fixed. A Fixed number consists of 32 bits: a sign bit, a 15-bit integer part, and a 16-bit fractional part. In order to make the volume and balance 'human readable', I've set MPEG_bal as a float where 0 is the left channel and 1 is the right channel, 0.5 being centre. When I construct the AHI request, I multiply this number by 0x00010000 to convert it to the fixed value. If I use this code as part of a DOS background process, I can change the volume and balance on the fly so the next sample that's queued will be played louder or quieter. It's also possible to interrupt AHI so the change takes effect immediately, but I won't go into that. Once the request is sent, we put the requisite bits in to check for CTRL-C and any AHI interrupt messages. Then it's time to swap buffers around: link = AHIio; I hate to admit it, but I'm pretty ignorant as to what this actually means, but it sets the second IORequest up to be executed in place of the first. This should
explain what's going on in my code.
There is a lot of information in the code's comments, but the hard-won
knowledge that is not very well explained in the autodocs and not
particularly well-documented in the header files is hopefully explained
well enough here.
|
|