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 <unistd.h> #include <fcntl.h> #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