Mercurial > sdl-ios-xcode
view src/audio/paudio/SDL_paudio.c @ 3846:66fb40445587 SDL-ryan-multiple-audio-device
Removed distinction between "available" and "init" in audio backends, since
both had to be checked for success as a pair at the higher level and several
of the Available methods were just always-succeed placeholders anyhow. Now
the availability check is done in the init code, and the higher level tries
all possible drivers until one manages to initialize successfully.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Tue, 17 Oct 2006 09:09:21 +0000 |
parents | ca74a71063ac |
children |
line wrap: on
line source
/* SDL - Simple DirectMedia Layer Copyright (C) 1997-2006 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Carsten Griwodz griff@kom.tu-darmstadt.de based on linux/SDL_dspaudio.c by Sam Lantinga */ #include "SDL_config.h" /* Allow access to a raw mixing buffer */ #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <sys/time.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include "SDL_timer.h" #include "SDL_audio.h" #include "SDL_stdinc.h" #include "../SDL_audiomem.h" #include "../SDL_audio_c.h" #include "SDL_paudio.h" #define DEBUG_AUDIO 0 /* A conflict within AIX 4.3.3 <sys/> headers and probably others as well. * I guess nobody ever uses audio... Shame over AIX header files. */ #include <sys/machine.h> #undef BIG_ENDIAN #include <sys/audio.h> /* The tag name used by paud audio */ #define PAUDIO_DRIVER_NAME "paud" /* Open the audio device for playback, and don't block if busy */ /* #define OPEN_FLAGS (O_WRONLY|O_NONBLOCK) */ #define OPEN_FLAGS O_WRONLY /* Get the name of the audio device we use for output */ #ifndef _PATH_DEV_DSP #define _PATH_DEV_DSP "/dev/%caud%c/%c" #endif static char devsettings[][3] = { {'p', '0', '1'}, {'p', '0', '2'}, {'p', '0', '3'}, {'p', '0', '4'}, {'p', '1', '1'}, {'p', '1', '2'}, {'p', '1', '3'}, {'p', '1', '4'}, {'p', '2', '1'}, {'p', '2', '2'}, {'p', '2', '3'}, {'p', '2', '4'}, {'p', '3', '1'}, {'p', '3', '2'}, {'p', '3', '3'}, {'p', '3', '4'}, {'b', '0', '1'}, {'b', '0', '2'}, {'b', '0', '3'}, {'b', '0', '4'}, {'b', '1', '1'}, {'b', '1', '2'}, {'b', '1', '3'}, {'b', '1', '4'}, {'b', '2', '1'}, {'b', '2', '2'}, {'b', '2', '3'}, {'b', '2', '4'}, {'b', '3', '1'}, {'b', '3', '2'}, {'b', '3', '3'}, {'b', '3', '4'}, {'\0', '\0', '\0'} }; static int OpenUserDefinedDevice(char *path, int maxlen, int flags) { const char *audiodev; int fd; /* Figure out what our audio device is */ if ((audiodev = SDL_getenv("SDL_PATH_DSP")) == NULL) { audiodev = SDL_getenv("AUDIODEV"); } if (audiodev == NULL) { return -1; } fd = open(audiodev, flags, 0); if (path != NULL) { SDL_strlcpy(path, audiodev, maxlen); path[maxlen - 1] = '\0'; } return fd; } static int OpenAudioPath(char *path, int maxlen, int flags, int classic) { struct stat sb; int cycle = 0; int fd = OpenUserDefinedDevice(path, maxlen, flags); if (fd != -1) { return fd; } /* !!! FIXME: do we really need a table here? */ while (devsettings[cycle][0] != '\0') { char audiopath[1024]; SDL_snprintf(audiopath, SDL_arraysize(audiopath), _PATH_DEV_DSP, devsettings[cycle][0], devsettings[cycle][1], devsettings[cycle][2]); if (stat(audiopath, &sb) == 0) { fd = open(audiopath, flags, 0); if (fd > 0) { if (path != NULL) { SDL_strlcpy(path, audiopath, maxlen); } return fd; } } } return -1; } /* This function waits until it is possible to write a full sound buffer */ static void PAUDIO_WaitDevice(_THIS) { fd_set fdset; /* See if we need to use timed audio synchronization */ if (this->hidden->frame_ticks) { /* Use timer for general audio synchronization */ Sint32 ticks; ticks = ((Sint32)(this->hidden->next_frame-SDL_GetTicks()))-FUDGE_TICKS; if (ticks > 0) { SDL_Delay(ticks); } } else { audio_buffer paud_bufinfo; /* Use select() for audio synchronization */ struct timeval timeout; FD_ZERO(&fdset); FD_SET(this->hidden->audio_fd, &fdset); if (ioctl(this->hidden->audio_fd, AUDIO_BUFFER, &paud_bufinfo) < 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Couldn't get audio buffer information\n"); #endif timeout.tv_sec = 10; timeout.tv_usec = 0; } else { long ms_in_buf = paud_bufinfo.write_buf_time; timeout.tv_sec = ms_in_buf / 1000; ms_in_buf = ms_in_buf - timeout.tv_sec * 1000; timeout.tv_usec = ms_in_buf * 1000; #ifdef DEBUG_AUDIO fprintf(stderr, "Waiting for write_buf_time=%ld,%ld\n", timeout.tv_sec, timeout.tv_usec); #endif } #ifdef DEBUG_AUDIO fprintf(stderr, "Waiting for audio to get ready\n"); #endif if (select(this->hidden->audio_fd+1,NULL,&fdset,NULL,&timeout) <= 0) { const char *message = "Audio timeout - buggy audio driver? (disabled)"; /* * In general we should never print to the screen, * but in this case we have no other way of letting * the user know what happened. */ fprintf(stderr, "SDL: %s - %s\n", strerror(errno), message); this->enabled = 0; /* Don't try to close - may hang */ this->hidden->audio_fd = -1; #ifdef DEBUG_AUDIO fprintf(stderr, "Done disabling audio\n"); #endif } #ifdef DEBUG_AUDIO fprintf(stderr, "Ready!\n"); #endif } } static void PAUDIO_PlayDevice(_THIS) { int written = 0; const Uint8 *mixbuf = this->hidden->mixbuf; const size_t mixlen = this->hidden->mixlen; /* Write the audio data, checking for EAGAIN on broken audio drivers */ do { written = write(this->hidden->audio_fd, mixbuf, mixlen); if ((written < 0) && ((errno == 0) || (errno == EAGAIN))) { SDL_Delay(1); /* Let a little CPU time go by */ } } while ((written < 0) && ((errno == 0) || (errno == EAGAIN) || (errno == EINTR))); /* If timer synchronization is enabled, set the next write frame */ if (this->hidden->frame_ticks) { this->hidden->next_frame += this->hidden->frame_ticks; } /* If we couldn't write, assume fatal error for now */ if (written < 0) { this->enabled = 0; } #ifdef DEBUG_AUDIO fprintf(stderr, "Wrote %d bytes of audio data\n", written); #endif } static Uint8 * PAUDIO_GetDeviceBuf(_THIS) { return this->hidden->mixbuf; } static void PAUDIO_CloseDevice(_THIS) { if (this->hidden != NULL) { if (this->hidden->mixbuf != NULL) { SDL_FreeAudioMem(this->hidden->mixbuf); this->hidden->mixbuf = NULL; } if (this->hidden->audio_fd >= 0) { close(this->hidden->audio_fd); this->hidden->audio_fd = -1; } SDL_free(this->hidden); this->hidden = NULL; } } static int PAUDIO_OpenDevice(_THIS, const char *devname, int iscapture) { const char *workaround = SDL_getenv("SDL_DSP_NOSELECT"); char audiodev[1024]; const char *err = NULL; int format; int bytes_per_sample; SDL_AudioFormat test_format; audio_init paud_init; audio_buffer paud_bufinfo; audio_status paud_status; audio_control paud_control; audio_change paud_change; int fd = -1; /* Initialize all variables that we clean on shutdown */ this->hidden = (struct SDL_PrivateAudioData *) SDL_malloc((sizeof *this->hidden)); if (this->hidden == NULL) { SDL_OutOfMemory(); return 0; } SDL_memset(this->hidden, 0, (sizeof *this->hidden)); /* Open the audio device */ fd = OpenAudioPath(audiodev, sizeof(audiodev), OPEN_FLAGS, 0); this->hidden->audio_fd = fd; if (fd < 0) { PAUDIO_CloseDevice(this); SDL_SetError("Couldn't open %s: %s", audiodev, strerror(errno)); return 0; } /* * We can't set the buffer size - just ask the device for the maximum * that we can have. */ if (ioctl(fd, AUDIO_BUFFER, &paud_bufinfo) < 0) { PAUDIO_CloseDevice(this); SDL_SetError("Couldn't get audio buffer information"); return 0; } if (this->spec.channels > 1) this->spec.channels = 2; else this->spec.channels = 1; /* * Fields in the audio_init structure: * * Ignored by us: * * paud.loadpath[LOAD_PATH]; * DSP code to load, MWave chip only? * paud.slot_number; * slot number of the adapter * paud.device_id; * adapter identification number * * Input: * * paud.srate; * the sampling rate in Hz * paud.bits_per_sample; * 8, 16, 32, ... * paud.bsize; * block size for this rate * paud.mode; * ADPCM, PCM, MU_LAW, A_LAW, SOURCE_MIX * paud.channels; * 1=mono, 2=stereo * paud.flags; * FIXED - fixed length data * * LEFT_ALIGNED, RIGHT_ALIGNED (var len only) * * TWOS_COMPLEMENT - 2's complement data * * SIGNED - signed? comment seems wrong in sys/audio.h * * BIG_ENDIAN * paud.operation; * PLAY, RECORD * * Output: * * paud.flags; * PITCH - pitch is supported * * INPUT - input is supported * * OUTPUT - output is supported * * MONITOR - monitor is supported * * VOLUME - volume is supported * * VOLUME_DELAY - volume delay is supported * * BALANCE - balance is supported * * BALANCE_DELAY - balance delay is supported * * TREBLE - treble control is supported * * BASS - bass control is supported * * BESTFIT_PROVIDED - best fit returned * * LOAD_CODE - DSP load needed * paud.rc; * NO_PLAY - DSP code can't do play requests * * NO_RECORD - DSP code can't do record requests * * INVALID_REQUEST - request was invalid * * CONFLICT - conflict with open's flags * * OVERLOADED - out of DSP MIPS or memory * paud.position_resolution; * smallest increment for position */ paud_init.srate = this->spec.freq; paud_init.mode = PCM; paud_init.operation = PLAY; paud_init.channels = this->spec.channels; /* Try for a closest match on audio format */ format = 0; for (test_format = SDL_FirstAudioFormat(this->spec.format); !format && test_format;) { #ifdef DEBUG_AUDIO fprintf(stderr, "Trying format 0x%4.4x\n", test_format); #endif switch (test_format) { case AUDIO_U8: bytes_per_sample = 1; paud_init.bits_per_sample = 8; paud_init.flags = TWOS_COMPLEMENT | FIXED; format = 1; break; case AUDIO_S8: bytes_per_sample = 1; paud_init.bits_per_sample = 8; paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED; format = 1; break; case AUDIO_S16LSB: bytes_per_sample = 2; paud_init.bits_per_sample = 16; paud_init.flags = SIGNED | TWOS_COMPLEMENT | FIXED; format = 1; break; case AUDIO_S16MSB: bytes_per_sample = 2; paud_init.bits_per_sample = 16; paud_init.flags = BIG_ENDIAN | SIGNED | TWOS_COMPLEMENT | FIXED; format = 1; break; case AUDIO_U16LSB: bytes_per_sample = 2; paud_init.bits_per_sample = 16; paud_init.flags = TWOS_COMPLEMENT | FIXED; format = 1; break; case AUDIO_U16MSB: bytes_per_sample = 2; paud_init.bits_per_sample = 16; paud_init.flags = BIG_ENDIAN | TWOS_COMPLEMENT | FIXED; format = 1; break; default: break; } if (!format) { test_format = SDL_NextAudioFormat(); } } if (format == 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Couldn't find any hardware audio formats\n"); #endif PAUDIO_CloseDevice(this); SDL_SetError("Couldn't find any hardware audio formats"); return 0; } this->spec.format = test_format; /* * We know the buffer size and the max number of subsequent writes * that can be pending. If more than one can pend, allow the application * to do something like double buffering between our write buffer and * the device's own buffer that we are filling with write() anyway. * * We calculate this->spec.samples like this because * SDL_CalculateAudioSpec() will give put paud_bufinfo.write_buf_cap * (or paud_bufinfo.write_buf_cap/2) into this->spec.size in return. */ if (paud_bufinfo.request_buf_cap == 1) { this->spec.samples = paud_bufinfo.write_buf_cap / bytes_per_sample / this->spec.channels; } else { this->spec.samples = paud_bufinfo.write_buf_cap / bytes_per_sample / this->spec.channels / 2; } paud_init.bsize = bytes_per_sample * this->spec.channels; SDL_CalculateAudioSpec(&this->spec); /* * The AIX paud device init can't modify the values of the audio_init * structure that we pass to it. So we don't need any recalculation * of this stuff and no reinit call as in linux dsp and dma code. * * /dev/paud supports all of the encoding formats, so we don't need * to do anything like reopening the device, either. */ if (ioctl(fd, AUDIO_INIT, &paud_init) < 0) { switch (paud_init.rc) { case 1: err = "Couldn't set audio format: DSP can't do play requests"; break; case 2: err = "Couldn't set audio format: DSP can't do record requests"; break; case 4: err = "Couldn't set audio format: request was invalid"; break; case 5: err = "Couldn't set audio format: conflict with open's flags"; break; case 6: err = "Couldn't set audio format: out of DSP MIPS or memory"; break; default: err = "Couldn't set audio format: not documented in sys/audio.h"; break; } } if (err != NULL) { PAUDIO_CloseDevice(this); SDL_SetError("Paudio: %s", err); return 0; } /* Allocate mixing buffer */ this->hidden->mixlen = this->spec.size; this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen); if (this->hidden->mixbuf == NULL) { PAUDIO_CloseDevice(this); SDL_OutOfMemory(); return 0; } SDL_memset(this->hidden->mixbuf, this->spec.silence, this->spec.size); /* * Set some paramters: full volume, first speaker that we can find. * Ignore the other settings for now. */ paud_change.input = AUDIO_IGNORE; /* the new input source */ paud_change.output = OUTPUT_1; /* EXTERNAL_SPEAKER,INTERNAL_SPEAKER,OUTPUT_1 */ paud_change.monitor = AUDIO_IGNORE; /* the new monitor state */ paud_change.volume = 0x7fffffff; /* volume level [0-0x7fffffff] */ paud_change.volume_delay = AUDIO_IGNORE; /* the new volume delay */ paud_change.balance = 0x3fffffff; /* the new balance */ paud_change.balance_delay = AUDIO_IGNORE; /* the new balance delay */ paud_change.treble = AUDIO_IGNORE; /* the new treble state */ paud_change.bass = AUDIO_IGNORE; /* the new bass state */ paud_change.pitch = AUDIO_IGNORE; /* the new pitch state */ paud_control.ioctl_request = AUDIO_CHANGE; paud_control.request_info = (char *) &paud_change; if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) { #ifdef DEBUG_AUDIO fprintf(stderr, "Can't change audio display settings\n"); #endif } /* * Tell the device to expect data. Actual start will wait for * the first write() call. */ paud_control.ioctl_request = AUDIO_START; paud_control.position = 0; if (ioctl(fd, AUDIO_CONTROL, &paud_control) < 0) { PAUDIO_CloseDevice(this); #ifdef DEBUG_AUDIO fprintf(stderr, "Can't start audio play\n"); #endif SDL_SetError("Can't start audio play"); return 0; } /* Check to see if we need to use select() workaround */ if (workaround != NULL) { this->hidden->frame_ticks = (float) (this->spec.samples * 1000) / this->spec.freq; this->hidden->next_frame = SDL_GetTicks() + this->hidden->frame_ticks; } /* We're ready to rock and roll. :-) */ return 1; } static int PAUDIO_Init(SDL_AudioDriverImpl *impl) { int fd = OpenAudioPath(NULL, 0, OPEN_FLAGS, 0); if (fd < 0) { SDL_SetError("PAUDIO: Couldn't open audio device"); return 0; } close(fd); /* Set the function pointers */ impl->OpenDevice = DSP_OpenDevice; impl->PlayDevice = DSP_PlayDevice; impl->PlayDevice = DSP_WaitDevice; impl->GetDeviceBuf = DSP_GetDeviceBuf; impl->CloseDevice = DSP_CloseDevice; impl->OnlyHasDefaultOutputDevice = 1; /* !!! FIXME: add device enum! */ return 1; } AudioBootStrap PAUDIO_bootstrap = { PAUDIO_DRIVER_NAME, "AIX Paudio", PAUDIO_Init, 0 }; /* vi: set ts=4 sw=4 expandtab: */