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:
- 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.
- The command options are
interpreted and the player's settings are set accordingly.
- The player switches back to the root privileges if it was
run as SUID root.
- The audio device is initialized and process priority is set (if wanted).
- If running as SUID root, switch to the nonprivileged ID. After
this cannot change into root any more.
- Open communication socket, wait for connection and then
initialize the interface part.
- Detached from process group if so desired.
- 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.
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
·