Code Listing 53 mpdmac

#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <sys/soundcard.h>

#include <sys/mman.h>

/* Plays a sound with OSS (/dev/dsp), using direct DMA access. Returns 0 on successful playback, nonzero on error. */

int PlayerDMA(u_int8_t *samples, int bits, int channels, int rate, int bytes) {

unsigned int requested, ioctl_format, ioctl_channels, ioctl_rate, ioctl_caps, ioctl_enable, ioctl_frag; audio_buf_info ioctl_info;

/* Buffer information. */ int frag_count, frag_size; u_int8_t *dmabuffer = NULL; int dmabuffer_size = 0; int dmabuffer_flag = 0;

/* Playback status variables. */ int position = 0, done = 0;

/* Attempt to open /dev/dsp for playback. We need to open for read/write in order to mmap() the device file. */ dsp = open("/dev/dsp",O_RDWR);

/* This could very easily fail, so we must handle errors. */ if (dsp < 0) {

perror("DMA player: error opening /dev/dsp for playback"); goto error;

/* Select a 1k fragment size, with 4 fragments. */

ioctl_frag = 10; /* fragment size is 2"10 = 1024 bytes */

if (ioctl(dsp,SNDCTL_DSP_SETFRAGMENT,&ioctl_frag) != 0) {

perror("DMA player: fragment configuration request failed"); goto error;

/* Select the appropriate sample format. */ switch (bits) {

case 8: ioctl_format = AFMT_U8; break; case 16: ioctl_format = AFMT_S16_NE; break; default: printf("DMA player: unknown sample size.\n"); goto error;

/* We've decided on a format. We now need to pass it to OSS. ioctl is a very generalized interface. We always pass data to it by reference, not by value, even if the data is a simple integer. */ requested = ioctl_format;

if (ioctl(dsp,SNDCTL_DSP_SETFMT,&ioctl_format) == -1) { perror("DMA player: format selection failed"); goto error;

/* ioctl's usually modify their arguments. SNDCTL_DSP_SETFMT sets its integer argument to the sample format that OSS actually gave us. This could be different than what we requested. For simplicity, we will not handle this situation. */ if (requested != ioctl_format) {

printf("DMA player: unsupported sample format.\n"); goto error;

/* We must inform OSS of the number of channels (mono or stereo) before we set the sample rate. This is due to limitations in some (older) sound cards. */ ioctl_channels = channels;

if (ioctl(dsp,SNDCTL_DSP_CHANNELS,&ioctl_channels) == -1) {

perror("DMA player: unable to set the number of channels"); goto error;

/* OSS might not have granted our request, even if the ioctl succeeded. */ if (channels != ioctl_channels) {

printf("DMA player: unable to set the number of channels.\n"); goto error;

/* We can now set the sample rate. */ ioctl_rate = rate;

if (ioctl(dsp,SNDCTL_DSP_SPEED,&ioctl_rate) == -1) { perror("DMA player: unable to set sample rate"); goto error;

/* OSS sets the SNDCTL_DSP_SPEED argument to the actual sample rate, which may be different from what the requested. In this case, a production-quality player would upsample or downsample the sound data. We'll simply report an error. */ if (rate != ioctl_rate) {

printf("DMA player: unable to set the desired sample rate.\n"); goto error;

/* Now check for DMA compatibility. It's quite possible that the driver won't support this. It would be a *very* good idea to provide a fallback in case DMA isn't supported - there are some sound cards that simply don't work with the DMA programming model at all. */ if (ioctl(dsp,SNDCTL_DSP_GETCAPS,&ioctl_caps) != 0) {

perror("DMA player: unable to read sound driver capabilities"); goto error;

/* The MMAP and TRIGGER bits must be set for this to work.

MMAP gives us the ability to access the DMA buffer directly, and TRIGGER gives us the ability to start the sound card's playback with a special ioctl. */ if (!(ioctl_caps & DSP_CAP_MMAP) || !(ioctl_caps & DSP_CAP_TRIGGER)) {

printf("DMA player: this sound driver is not capable of direct DMA."); goto error;

/* Query the sound driver for the actual fragment configuration so that we can calculate the total size of the DMA buffer. We cannot assume that the driver granted our previous buffering requests. */

if (ioctl(dsp,SNDCTL_DSP_GETOSPACE,&ioctl_info) != 0) {

perror("DMA player: unable to query buffer information"); goto error;

frag_count = ioctl_info.fragstotal; frag_size = ioctl_info.fragsize; dmabuffer_size = frag_count * frag_size;

/* We're good to go. Map a buffer onto the audio device. */ dmabuffer = mmap(NULL, dmabuffer_size, /* length of region to map */

PROT_WRITE | PROT_READ, /* select the output buffer

(PROT_READ alone selects input) */

MAP_FILE | MAP_SHARED, /* see the mmap manual page for more info about these flags. */

/* This could fail for a number of reasons. */ if (dmabuffer == (u_int8_t *)MAP_FAILED) {

perror("DMA player: unable to mmap a DMA buffer"); goto error;

/* The DMA buffer is ready! Now we can start playback by toggling the device's PCM output bit. Yes, this is a very hacky interface. We're actually using the OSS "trigger" functionality here. */ ioctl_enable = 0;

if (ioctl(dsp, SNDCTL_DSP_SETTRIGGER, &ioctl_enable) != 0) {

perror("DMA player: unable to disable PCM output"); goto error;

ioctl_enable = PCM_ENABLE_OUTPUT;

if (ioctl(dsp, SNDCTL_DSP_SETTRIGGER, &ioctl_enable) != 0) { perror("DMA player: unable to enable PCM output"); goto error;

/* The done variable simply makes sure that the last chunk actually gets played. */

struct count_info status; int i;

/* Find the location of the DMA controller within the buffer.

This will be exact at least to the level of a fragment. */ if (ioctl(dsp, SNDCTL_DSP_GETOPTR, &status) != 0) {

perror("DMA player: unable to query playback status"); goto error;

/* Our buffer is comprised of several fragments. However, in DMA mode, it is safe to treat the entire buffer as one big block. We will divide it into two logical chunks. While the first chunk is playing, we will fill the second with new samples, and vice versa. With a small buffer, we will still enjoy low latency.

status.ptr contains the offset of the DMA controller within the buffer. */

/* Do we need to refill fragments 3 and 4? */ if (status.ptr < dmabuffer_size/2) { int amount;

/* Copy data into the DMA buffer. */ if (bytes - position < dmabuffer_size/2) {

amount = bytes-position; } else amount = dmabuffer_size/2;

dmabuffer[i+dmabuffer_size/2] = samples[position+i];

/* Zero the rest of this half. */ for (; i < dmabuffer_size/2; i++) {

dmabuffer[i+dmabuffer_size/2] = 0;

/* Update the buffer position. */ position += amount;

/* Next update will be fragments 1 and 2 */ dmabuffer_flag = 1;

/* Have we reached the end? */ if (position >= bytes) done++;

/* Do we need to refill fragments 1 and 2? */ if (status.ptr >= dmabuffer_size/2) { int amount;

/* Copy data into the DMA buffer. */ if (bytes - position < dmabuffer_size/2) {

amount = bytes-position; } else amount = dmabuffer_size/2;

dmabuffer[i] = samples[position+i];

/* Zero the rest of this half. */ for (; i < dmabuffer_size/2; i++) { dmabuffer[i] = 0;

/* Update the buffer position. */ position += amount;

/* Next update will be fragments 3 and 4 */ dmabuffer_flag = 0;

WritePlaybackStatus(position, bytes, channels, bits, rate);

fflush(stdout);

/* Wait a while. A game would normally do the rest of its processing here. */ usleep(100);

munmap(dmabuffer,dmabuffer_size); close(dsp);

return 0;

/* Error handler. goto's are normally bad, but they make sense here. */ error:

munmap(dmabuffer,dmabuffer_size); if (dsp > 0) close(dsp);

return -1;

The program begins as usual, loading a sound file and setting a few OSS parameters via ioctl. It then queries the OSS device's capabilities and checks for DMA compatibility. If the sound card is capable of this type of DMA access, the SNDCTL_DSP_MMAP and SNDCTL_DSP_TRIGGER bits will be set (however, initialization can still fail). The program queries OSS for the size and number of buffer fragments, multiplying them to obtain the total size of the sound buffer in bytes. Next it mmaps a buffer onto the sound device.

mmap is a strange beast. It can be used for a variety of purposes, but its most common use is to map buffers of memory onto files (so that accesses to the memory will result in accesses to the file). This provides a very convenient way to load large data structures from disk; you can simply map the file into memory and grab the data with memcpy. OSS uses it to provide a convenient way to map the sound card's DMA buffer into a program's address space. We will leave a full discussion of mmap to other sources, but its use in this case is fairly straightforward.

With a DMA buffer in place and the card properly configured, the program "triggers" the sound card's DMA by toggling the driver's PCM output enable bit. The trigger feature is designed to allow applications to gain precise control over playback timing, but it doubles as a way to set off DMA transfers. Once the DMA controller has been started, it cannot be stopped without shutting down OSS. This is a flaw, in my opinion, but it is only a minor issue (to effectively stop playback, simply fill the buffer with zeroes). After this bit has been cleared and then reset, the DMA controller (either part of the sound card or a component of the motherboard's chipset) will repeatedly loop over the DMA buffer and send whatever it finds directly to the sound card. To play sound, we simply have to copy our samples into the DMA buffer.

This is the tricky part. The DMA controller is a separate piece of hardware, and we have little control over its operation once it has been started. It sweeps across the DMA buffer at a predictable rate, returning to the start of the buffer when it reaches the end. We need to make sure that we always keep a fresh set of sample data in front of the DMA controller's path. We have chosen a simple method that seems to work fairly well. The DMA buffer contains several (probably four or five) fragments of data, each of 1024 bytes. We disregard this organization and treat the buffer as one chunk. While the DMA controller is busy scanning the first half of the buffer, we (quickly) fill the second half with new samples. When the controller crosses the halfway mark, we update the first half of the buffer. We could reduce latency by dividing the buffer into more sections, but this should rarely be necessary. When the entire sound clip has been played, our program unmaps the DMA buffer and shuts down OSS.

In conclusion, direct DMA buffer access provides a powerful tool for squeezing performance out of OSS, but you should not count on its availability or even reliable detection. Don't be surprised if it doesn't work on any given sound card, or under different operating systems. The SDL toolkit uses DMA when it can, but it supports several other methods in case DMA is not supported by the sound driver.

ioctl call

Purpose

SNDCTL_DSP_SETFMT

Sets OSS to use a particular sample format. Takes a pointer to an integer containing an AFMT_format constant (defined in sys/soundcard.h). Changes this integer to reflect the format that OSS was able to obtain.

SNDCTL_DSP_CHANNELS

Sets the number of channels. Takes a pointer to an integer containing 1 for mono, 2 for stereo.

SNDCTL_DSP_SPEED

Sets the sound device's sampling rate. Takes a pointer to an integer containing the desired sampling rate. Changes this integer to reflect the closest match that OSS could obtain.

SNDCTL_DSP_SETFRAGMENT

Sets the sound driver's fragment size. Takes a pointer to an integer. The top 16 bits define the number of fragments desired, and the lower 16 bits define the size of each fragment as a power of two (for instance, a value of 10 would result in a fragment size of 210 = 1024 bytes). There are reasonable limits on the minimum and maximum number of fragments and the size of each, and OSS reserves the right to reject your selection.

SNDCTL_DSP_GETOSPACE

Queries the amount of available buffer space in the sound driver. Takes a pointer to an audio_buf_info structure (defined in sys/soundcard.h). Fills the structure with information about the driver's buffer fragments.

SNDCTL_DSP_GETCAPS

Queries the sound driver's capabilities. Takes a pointer to an integer. Sets the integer to a bitmask of supported DSP_CAP_func functions (defined in sys/soundcard.h).

Table 5-1: A few important OSS ioctl calls

Table 5-1: A few important OSS ioctl calls

Was this article helpful?

0 0
The Ultimate Computer Repair Guide

The Ultimate Computer Repair Guide

Read how to maintain and repair any desktop and laptop computer. This Ebook has articles with photos and videos that show detailed step by step pc repair and maintenance procedures. There are many links to online videos that explain how you can build, maintain, speed up, clean, and repair your computer yourself. Put the money that you were going to pay the PC Tech in your own pocket.

Get My Free Ebook


Post a comment