Tik-76.115 Individual Project: Guinea Pig
$Id: internals.html,v 1.2 1996/04/22 14:21:44 hynde Exp $

Sound Player internals

This documents describes the events during the lifetime of the gpPlayer program. It shows the order the parts of the system are called. Also some information about porting the player to other systems is given at the end.

1 · Initialization

Operations done when the program is started:
  1. First check whether it is running as SUID root. If it is, it switches to the less privileged ID until it needs the root privileges.
  2. The command options are interpreted and the player's settings are set accordingly.
  3. The player switches back to the root privileges if it was run as SUID root.
  4. The audio device is initialized and process priority is set (if wanted).
  5. If running as SUID root, switch to the nonprivileged ID. After this cannot change into root any more.
  6. Open communication socket, wait for connection and then initialize the interface part.
  7. Detached from process group if so desired.
  8. Start the main loop
When the initializations are done, the player proceeds to the main loop.

2 · Mainloop of the player

Below in figure 1 is the heart of the sound player, the main loop. The figure presents a state-machine like description of the workings of the main loop.

[image]
Fig. 1: Sound player's internal operations (main loop).
If your browser supports client side imagemaps, click on interesting parts of the figure for more information.

2.1 · Mainloop

First the main loop sets up information for select.

Then select(2) system call is used to wait for files to become available for reading or writing. If the audio device is ready to accept more sound data, audio data is written to the device. If the comm. socket is available for writing then data from the output queue to the socket and if there is incoming messages, they are read into input gueue and processed. Then the mainloop jumps back to the start.

When the player becomes inactive, it exits the main loop. The mainloop becomes inactive when input queue is disabled and the output queue is empty (all remaining messages have been sent to the remote user) or when an unrecoverable error has happened. The player program then does any cleanup activities necessary and then exits.

2.2 · Write audio

Depending on the current settings, the writing of the audio data can be done in two ways: If so selected (fill_before), the new audio data is calculated just before it is sent to the audio device. Otherwise (fill_after) the procedure is done in reverse: the audio data calculated at the end of the previous round is first written to the audio device and the new data is calculated after that and is used next time the device is ready to accept more data. After that, in both cases, the prefill operation is done.

Calculating the new data just before writing it to the device reduces the delay between writing the data and when it comes out of the loudspeakers. On the other hand, if the player takes too much time calculating the data (maybe due to paging or other processes), it may not be able to write the data in time and there will be a break (snap, cracle, pop, ...) in the audio output.

In the other case, the data buffer calculated after the last audio write is immediately available for writing to the device. After the data is written, the new buffer is calculated. This way the player has 'more room to manouver' because it doesn't have to calculate the new buffer immediately, instead it has the maximum possible time available. (This time is generally as long as the length of the output buffer in seconds. For a 4K (sample frames) buffer it is about 0.1s). This is the default mode used by the player.

2.3 · Fill buffers

The fill_audiodev_buffers() function is a function in the audio system dependent audio module (the 'audio[msnd|sgi].c' files). Its duty is to prepare a buffer of audio data in a format accepted by the audio system. It first calls the fill output buffers function that calculates the new audio data in the player system's internal format (floating point numbers). Then the output buffer is converted to the audio device's format and other conversions are also possible (mono->stereo, stereo->mono, etc.) although they are rarely used. On Linux/MSND the conversion is done by converting the float data into short intergers (16 bits) and interleaving the channel data into the data buffer. On SGI, the floating point data is converted to 24-bit integers right justified (and sign extended) into 32-bit integers and channels are interleaved in the same ways as with MSND.

The player has been optimized so that if the new and the previous buffers were both empty (no samples were playing), there is no need to convert the new buffer because the previous buffer contains the same data (zeros). Fill output buffers too has the same optimization, and therefore the player uses less CPU time when no samples are playing.

2.4 · Write audio buffers

Write_audiodev_buffers() is also a function in the audio system dependent audio module (the 'audio[msnd|sgi].c' files). It writes the the audio data calculated by fill buffers to the audio device. In case of an unrecoverable error, this function return a nonzero value and noticing that, the main loop exits.

2.5 · Fill output buffers

The fill_outputs() function (in file 'outputs.c') calls the samples_fillbuffer_all() function (in file 'samples.c') that, for each sound sample that is playing, calls the sample's fillbuffer function that writes the samples data to the output buffers.

2.6 · Prefill

The samples_prefill_all() function ('samples.c') calls for each sample that is playing the sample's prefill function (if the sample type provides one). The prefill function is used to make sure that enough sample data is loaded in memory for the output buffer filling to succeed without having to page the pages in then (which could take too long and breaks would be induces into the audio output).

With file samples the prefill operation simply touches a certain amount of pages in advance before they are used. This increases the probability of the pages being in main memory when they are needed, but it doesn't guarantee it.

3 · Communication

The player communicates with the outside world using a socket. To aid the communication, the input and output through the socket is handled by to queues: the input queue handles the receiving of commands from the user and the output queue handles the messages sent to the user. The socket is used in non-blocking mode.

3.1 · Receiving messages

When the main loop notices that the communication socket has data available for reading (with select(2)), the data available is copied to the end of the input queue. Then the interface module which handles the player's protocol checks if there are complete messages in the input queue. If there are, it processes all complete messages and removes them from the queue.

3.2 · Sending messages

If there is data in the output queue (messages waiting to be sent), then in the main loop select is set so that it will indicate when we can write data to the socket. When socket is ready to accept data, as much data from the output queue is written to the socket as possible. Then the written data is removed from the output queue and any remaining unsent data remains in the queue.

When messages are sent from the player, the message is first copied to the output queue and is written out only when select indicates that the socket can accept more data. If the socket were unable to send data, the write would block until the data could be delivered. During that time the audio device might need more data and because we are waiting for the socket write to complete, we couldn't handle the audio output and there would be breaks in the audio stream.

4 · Termination

When the main loop of the player program returns the player will shut down and exit. It closes the audio device, releases memory allocated for buffers, unloads samples, etc.

The player is normally terminated by sending the QUIT command the the player via the interface.

5 · Porting the sound player to a different audio device

The player is designed to work on UNIX systems and should port nicely to any UNIX OS (and it works for both little- and big-endian systems. However, PDP-endian systems are not supported). Primarily the player was written for Linux but there is a working version for Silicon Graphics machines (tested with IRIX 5.3 on Indy and Indigo^2 machines). The player compiled for NetBSD too a while back but I haven't checked it for a while. I you want to port to a non-UNIX system, you are on your own.

For porting the player to a different audio device (on UNIX), look at the write audio part in figure 1 to see how the audio output is handled. Then look at the sources of the audio device specific files, 'audio_msnd.c' and 'audio_sgi.c' to see how they are implemented. Much of the code is the same or almost the same for both systems. Systems mainly differ in how the audio device is opened and initialized. If you have experience programming your audio device and the device is somewhat similar to ours, it shouldn't take long to port the player, maybe an hour or two. (It took me initially about three hours to port the player to SGI, even without any SGI audio programming experience).


· Sound Player Index · Document index · Guinea Pig ·