PS2 Linux Programming
Using PS2 audio
Introduction
The audio drivers that come with the PS2 Linux kit are basic
Linux audio
drivers, and therefore most documents covering linux sound apply to the PS2 linux
sound. For example,
Making audio
complicated is a good source of information. The interface for the audio
device is through /dev/dsp, /dev/dsp1 and /dev/dsp2.
The devices
All three devices behave the same way: the incoming data is transformed into
48 KHz, 16-bit, stereo sound, and mixed with the other two devices and then
played through both the normal output and the optical output using the IOP
processor.
In addition to writing data, through the Linux driver interface it is possible
to set up various sound options (and this usually has to be done in advance,
because the default settings of the device driver are rather dubious). This
interface is through the ioctl function, similar to the pad device. There are
a number of ioctl functions,
some of which are used and described in this tutorial.
Using the audio device
Just like the pad device, it is first necessary to open it. As the audio device
is used for writing instead of reading, it is opened as follows:
#include
#include
#define DEVICE_NAME "/dev/dsp"
int audio_fd;
audio_fd=open(DEVICE_NAME,O_WRONLY,0);
Once the deice is open, data can be sent to it, though it is recommended to
setup the parameters first.
As a matter of good practice, the audio device should be closed at the end of
the program with:
close(audio_fd);
Setting up the audio parameters
Before using the audio device, it is useful to set up your audio parameters to
match the files you are planning to play. The order of thsse parameters is
important, as the audio device allocates buffers based on your selections, and
changing the order might make the buffers too small or large for proper use.
The first thing you need to set is the format. There are many formats to
choose from (see /usr/include/linux/soundcard.h, the formats start with
AFMT_). For our purpose, we choose the basic 8-bit format, AFMT_U8:
int format;
format=AFMT_U8;
if(ioctl(audio_fd,SNDCTL_DSP_SETFMT,&format)==-1) {
perror("SND_CTL_DSP_SETFMT");
exit(2);
}
After the format, it is necessary to set the number of channels that you wish
to use. The number of channels is indicated by setting the stereo parameter. If
stereo is 1, the number of channels is 2, if stereo is 0, the sound is mono and
the number of channels is 1. If a mono sound is connected to a stereo system,
the same sound will be played on both channels. To reduce the memory usage of
the sound samples, the example chooses mono:
int stereo;
stereo=0;
if(ioctrl(audio_fd,SNDCTL_DSP_STEREO,&format)==-1) {
perror("SNDCTL_DSP_STEREO");
exit(3);
}
Finally it is necessary to indicate the rate of the sound. This rate usually
ranges from 8000 (phone quality) to 48000 (better than CD quality). The higher
the rate, the more data needs to be moved. Rates that are usually used are:
8000, 11025, 22050, 44100 and 48000. In this example, 11025 is used.
int speed;
speed=11025;
if(ioctl(audio_fd,SNDCTL_DSP_SPEED),&speed)==-1) {
perror("SNDCTL_DSP_SPEED");
exit(4);
}
Sending data to the audio device
Sending data to the audio device is very simple: you use the write() function.
write(audio_fd,audiobuf,frag_size);
Where audiobuf is a buffer holding the audio data, and frag_size is the number
that needs to be sent. Note that the playstation 2 optical output sometimes
takes a few seconds to start. It is therefore wise to start by sending a series
of 0 values (what a zero value actually is depends on the audio format used)
and to keep sending 0 values even when there is no sound to be played.
If playing audio is enough, this will work. However, in a game other things
need to happen as well. The write() function has the problem that it can block,
and as long as it is blocked, it will stall the program (unless it happens in
a separate thread). To avoid the write() from blocking, it is necessary to
write only as many bytes as are available in the internal buffers of the
audio device. There are two aspects to these buffers: the size of the buffers,
and the number of buffers. The size of the buffers can be determined immediately
after setting up the audio device:
int frag_size;
if(ioctl(audio_fd,SNDCTL_DSP_GETBLKSIZE,&frag_size)==-1) {
perror("SNDCTL_DSP_GETBLKSIZE");
exit(5);
}
printf("Fragment size: %d\n",frag_size);
The number of empty buffers should be determined each frame, just before writing
to the audio device:
int check_free_fragments(int audio_fd) {
audio_buf_info info;
if(ioctl(audio_fd,SNDCTL_DSP_GETOSPACE,&info)==-1) return 0;
return info.fragments;
}
If this function return 0, do not call write() on the audio device. If
it is larger, call write with a block the size of frag_size times
the number returned. This way the write() will never block, and the game will
always continue.
Creating the audio samples
The audio device reads data in the so-called "raw" format. Most audio formats,
however, use a header and/or a special kind of encoding to store the data. These
will need to be converted before sending them to the audio device. Fortunately,
Linux has a command, sox, that can convert audio files to the "raw" format used
by the audio device. To create sound samples (from any type of file, sox
determines
the file type based on the extension) that work with this example, use the
following command:
sox gate.wav -r 11025 -b -u -c 1 gate.raw
Where gate.wav is the input file and gate.raw is the raw output file. Use
"man sox" to get information about parameters for other sample rates, channel
numbers and formats. There is a speed advantage at using 48000 16-bit stereo
samples, because these do not cause a processing increase by the audio device
drivers (all other types need to be converted to that type). However, using
that format can possibly create memory problems.
Maarten Hofman