Mercurial > sdl-ios-xcode
view src/audio/qsa/SDL_qsa_audio.c @ 3124:d7174e9f65ce
More checks for current GF state were added.
author | Mike Gorchak <lestat@i.com.ua> |
---|---|
date | Thu, 14 May 2009 11:51:42 +0000 |
parents | 60301ed80050 |
children | 7f684f249ec9 |
line wrap: on
line source
/* SDL - Simple DirectMedia Layer Copyright (C) 1997-2009 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Sam Lantinga slouken@libsdl.org QNX Sound Architecture (QSA) SDL driver Copyright (C) 2009 Mike Gorchak (mike@malva.ua, lestat@i.com.ua) */ #include "SDL_config.h" #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/types.h> #include <sys/time.h> #include <sched.h> #include <sys/select.h> #include <sys/neutrino.h> #include <sys/asoundlib.h> #include "SDL_timer.h" #include "SDL_audio.h" #include "../SDL_audiomem.h" #include "../SDL_audio_c.h" #include "SDL_qsa_audio.h" /* The tag name used by QSA audio framework */ #define DRIVER_NAME "qsa" /* default channel communication parameters */ #define DEFAULT_CPARAMS_RATE 44100 #define DEFAULT_CPARAMS_VOICES 1 #define DEFAULT_CPARAMS_FRAG_SIZE 4096 #define DEFAULT_CPARAMS_FRAGS_MIN 1 #define DEFAULT_CPARAMS_FRAGS_MAX 1 #define QSA_NO_WORKAROUNDS 0x00000000 #define QSA_MMAP_WORKAROUND 0x00000001 struct BuggyCards { char* cardname; unsigned long bugtype; }; #define QSA_WA_CARDS 3 #define QSA_MAX_CARD_NAME_LENGTH 33 struct BuggyCards buggycards[QSA_WA_CARDS]= { {"Sound Blaster Live!", QSA_MMAP_WORKAROUND}, {"Vortex 8820", QSA_MMAP_WORKAROUND}, {"Vortex 8830", QSA_MMAP_WORKAROUND}, }; /* List of found devices */ #define QSA_MAX_DEVICES 32 #define QSA_MAX_NAME_LENGTH 81+16 /* Hardcoded in QSA, can't be changed */ typedef struct _QSA_Device { char name[QSA_MAX_NAME_LENGTH]; /* Long audio device name for SDL */ int cardno; int deviceno; } QSA_Device; QSA_Device qsa_playback_device[QSA_MAX_DEVICES]; uint32_t qsa_playback_devices; QSA_Device qsa_capture_device[QSA_MAX_DEVICES]; uint32_t qsa_capture_devices; static inline void QSA_SetError(const char* fn, int status) { SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status)); } /* card names check to apply the workarounds */ static int QSA_CheckBuggyCards(_THIS, unsigned long checkfor) { char scardname[QSA_MAX_CARD_NAME_LENGTH]; int it; if (snd_card_get_name(this->hidden->cardno, scardname, QSA_MAX_CARD_NAME_LENGTH-1)<0) { return 0; } for (it=0; it<QSA_WA_CARDS; it++) { if (SDL_strcmp(buggycards[it].cardname, scardname)==0) { if (buggycards[it].bugtype==checkfor) { return 1; } } } return 0; } static void QSA_ThreadInit(_THIS) { struct sched_param param; int status; /* Increase default 10 priority to 25 to avoid jerky sound */ status=SchedGet(0, 0, ¶m); param.sched_priority=param.sched_curpriority + 15; status=SchedSet(0, 0, SCHED_NOCHANGE, ¶m); } /* PCM channel parameters initialize function */ static void QSA_InitAudioParams(snd_pcm_channel_params_t* cpars) { SDL_memset(cpars, 0, sizeof(snd_pcm_channel_params_t)); cpars->channel=SND_PCM_CHANNEL_PLAYBACK; cpars->mode=SND_PCM_MODE_BLOCK; cpars->start_mode=SND_PCM_START_DATA; cpars->stop_mode=SND_PCM_STOP_STOP; cpars->format.format=SND_PCM_SFMT_S16_LE; cpars->format.interleave=1; cpars->format.rate=DEFAULT_CPARAMS_RATE; cpars->format.voices=DEFAULT_CPARAMS_VOICES; cpars->buf.block.frag_size=DEFAULT_CPARAMS_FRAG_SIZE; cpars->buf.block.frags_min=DEFAULT_CPARAMS_FRAGS_MIN; cpars->buf.block.frags_max=DEFAULT_CPARAMS_FRAGS_MAX; } /* This function waits until it is possible to write a full sound buffer */ static void QSA_WaitDevice(_THIS) { fd_set wfds; fd_set rfds; int selectret; struct timeval timeout; if (!this->hidden->iscapture) { FD_ZERO(&wfds); FD_SET(this->hidden->audio_fd, &wfds); } else { FD_ZERO(&rfds); FD_SET(this->hidden->audio_fd, &rfds); } do { /* Setup timeout for playing one fragment equal to 2 seconds */ /* If timeout occured than something wrong with hardware or driver */ /* For example, Vortex 8820 audio driver stucks on second DAC because */ /* it doesn't exist ! */ timeout.tv_sec=2; timeout.tv_usec=0; this->hidden->timeout_on_wait=0; if (!this->hidden->iscapture) { selectret=select(this->hidden->audio_fd+1, NULL, &wfds, NULL, &timeout); } else { selectret=select(this->hidden->audio_fd+1, &rfds, NULL, NULL, &timeout); } switch(selectret) { case -1: { SDL_SetError("QSA: select() failed: %s\n", strerror(errno)); return; } break; case 0: { SDL_SetError("QSA: timeout on buffer waiting occured\n"); this->hidden->timeout_on_wait=1; return; } break; default: { if (!this->hidden->iscapture) { if (FD_ISSET(this->hidden->audio_fd, &wfds)) { return; } } else { if (FD_ISSET(this->hidden->audio_fd, &rfds)) { return; } } } break; } } while (1); } static void QSA_PlayDevice(_THIS) { snd_pcm_channel_status_t cstatus; int written; int status; int towrite; void* pcmbuffer; if ((!this->enabled) || (!this->hidden)) { return; } towrite = this->spec.size; pcmbuffer = this->hidden->pcm_buf; /* Write the audio data, checking for EAGAIN (buffer full) and underrun */ do { written=snd_pcm_plugin_write(this->hidden->audio_handle, pcmbuffer, towrite); if (written!=towrite) { /* Check if samples playback got stuck somewhere in hardware or in */ /* the audio device driver */ if ((errno==EAGAIN) && (written==0)) { if (this->hidden->timeout_on_wait!=0) { SDL_SetError("QSA: buffer playback timeout\n"); return; } } /* Check for errors or conditions */ if ((errno==EAGAIN)||(errno==EWOULDBLOCK)) { /* Let a little CPU time go by and try to write again */ SDL_Delay(1); /* if we wrote some data */ towrite-=written; pcmbuffer+=written*this->spec.channels; continue; } else { if ((errno==EINVAL)||(errno==EIO)) { SDL_memset(&cstatus, 0, sizeof(cstatus)); if (!this->hidden->iscapture) { cstatus.channel=SND_PCM_CHANNEL_PLAYBACK; } else { cstatus.channel=SND_PCM_CHANNEL_CAPTURE; } status=snd_pcm_plugin_status(this->hidden->audio_handle, &cstatus); if (status<0) { QSA_SetError("snd_pcm_plugin_status", status); return; } if ((cstatus.status==SND_PCM_STATUS_UNDERRUN) || (cstatus.status==SND_PCM_STATUS_READY)) { if (!this->hidden->iscapture) { status=snd_pcm_plugin_prepare(this->hidden->audio_handle, SND_PCM_CHANNEL_PLAYBACK); } else { status=snd_pcm_plugin_prepare(this->hidden->audio_handle, SND_PCM_CHANNEL_CAPTURE); } if (status<0) { QSA_SetError("snd_pcm_plugin_prepare", status); return; } } continue; } else { return; } } } else { /* we wrote all remaining data */ towrite -= written; pcmbuffer += written * this->spec.channels; } } while ((towrite>0) && (this->enabled)); /* If we couldn't write, assume fatal error for now */ if (towrite!=0) { this->enabled=0; } } static Uint8* QSA_GetDeviceBuf(_THIS) { return this->hidden->pcm_buf; } static void QSA_CloseDevice(_THIS) { if (this->hidden!=NULL) { if (this->hidden->audio_handle!=NULL) { if (!this->hidden->iscapture) { /* Finish playing available samples */ snd_pcm_plugin_flush(this->hidden->audio_handle, SND_PCM_CHANNEL_PLAYBACK); } else { /* Cancel unread samples during capture */ snd_pcm_plugin_flush(this->hidden->audio_handle, SND_PCM_CHANNEL_CAPTURE); } snd_pcm_close(this->hidden->audio_handle); this->hidden->audio_handle=NULL; } if (this->hidden->pcm_buf!=NULL) { SDL_FreeAudioMem(this->hidden->pcm_buf); this->hidden->pcm_buf=NULL; } SDL_free(this->hidden); this->hidden=NULL; } } static int QSA_OpenDevice(_THIS, const char* devname, int iscapture) { int status=0; int format=0; SDL_AudioFormat test_format=0; int found=0; snd_pcm_channel_setup_t csetup; snd_pcm_channel_params_t cparams; /* Initialize all variables that we clean on shutdown */ this->hidden=(struct SDL_PrivateAudioData*)SDL_calloc(1, (sizeof(struct SDL_PrivateAudioData))); if (this->hidden==NULL) { SDL_OutOfMemory(); return 0; } SDL_memset(this->hidden, 0, sizeof(struct SDL_PrivateAudioData)); /* Initialize channel transfer parameters to default */ QSA_InitAudioParams(&cparams); /* Initialize channel direction: capture or playback */ this->hidden->iscapture=iscapture; /* Find deviceid and cardid by device name for playback */ if ((!this->hidden->iscapture) && (devname!=NULL)) { uint32_t device; int32_t status; /* Search in the playback devices */ device=0; do { status=SDL_strcmp(qsa_playback_device[device].name, devname); if (status==0) { /* Found requested device */ this->hidden->deviceno=qsa_playback_device[device].deviceno; this->hidden->cardno=qsa_playback_device[device].cardno; break; } device++; if (device>=qsa_playback_devices) { QSA_CloseDevice(this); SDL_SetError("No such playback device"); return 0; } } while(1); } /* Find deviceid and cardid by device name for capture */ if ((this->hidden->iscapture) && (devname!=NULL)) { /* Search in the capture devices */ uint32_t device; int32_t status; /* Searching in the playback devices */ device=0; do { status=SDL_strcmp(qsa_capture_device[device].name, devname); if (status==0) { /* Found requested device */ this->hidden->deviceno=qsa_capture_device[device].deviceno; this->hidden->cardno=qsa_capture_device[device].cardno; break; } device++; if (device>=qsa_capture_devices) { QSA_CloseDevice(this); SDL_SetError("No such capture device"); return 0; } } while(1); } /* Check if SDL requested default audio device */ if (devname==NULL) { /* Open system default audio device */ if (!this->hidden->iscapture) { status=snd_pcm_open_preferred(&this->hidden->audio_handle, &this->hidden->cardno, &this->hidden->deviceno, SND_PCM_OPEN_PLAYBACK); } else { status=snd_pcm_open_preferred(&this->hidden->audio_handle, &this->hidden->cardno, &this->hidden->deviceno, SND_PCM_OPEN_CAPTURE); } } else { /* Open requested audio device */ if (!this->hidden->iscapture) { status=snd_pcm_open(&this->hidden->audio_handle, this->hidden->cardno, this->hidden->deviceno, SND_PCM_OPEN_PLAYBACK); } else { status=snd_pcm_open(&this->hidden->audio_handle, this->hidden->cardno, this->hidden->deviceno, SND_PCM_OPEN_CAPTURE); } } /* Check if requested device is opened */ if (status<0) { this->hidden->audio_handle=NULL; QSA_CloseDevice(this); QSA_SetError("snd_pcm_open", status); return 0; } if (!QSA_CheckBuggyCards(this, QSA_MMAP_WORKAROUND)) { /* Disable QSA MMAP plugin for buggy audio drivers */ status=snd_pcm_plugin_set_disable(this->hidden->audio_handle, PLUGIN_DISABLE_MMAP); if (status<0) { QSA_CloseDevice(this); QSA_SetError("snd_pcm_plugin_set_disable", status); return 0; } } /* Try for a closest match on audio format */ format = 0; /* can't use format as SND_PCM_SFMT_U8 = 0 in qsa */ found = 0; for (test_format = SDL_FirstAudioFormat(this->spec.format); !found;) { /* if match found set format to equivalent QSA format */ switch (test_format) { case AUDIO_U8: { format=SND_PCM_SFMT_U8; found=1; } break; case AUDIO_S8: { format=SND_PCM_SFMT_S8; found=1; } break; case AUDIO_S16LSB: { format=SND_PCM_SFMT_S16_LE; found=1; } break; case AUDIO_S16MSB: { format=SND_PCM_SFMT_S16_BE; found=1; } break; case AUDIO_U16LSB: { format=SND_PCM_SFMT_U16_LE; found=1; } break; case AUDIO_U16MSB: { format=SND_PCM_SFMT_U16_BE; found=1; } break; case AUDIO_S32LSB: { format=SND_PCM_SFMT_S32_LE; found=1; } break; case AUDIO_S32MSB: { format=SND_PCM_SFMT_S32_BE; found=1; } break; case AUDIO_F32LSB: { format=SND_PCM_SFMT_FLOAT_LE; found=1; } break; case AUDIO_F32MSB: { format=SND_PCM_SFMT_FLOAT_BE; found=1; } break; default: { break; } } if (!found) { test_format = SDL_NextAudioFormat(); } } /* assumes test_format not 0 on success */ if (test_format==0) { QSA_CloseDevice(this); SDL_SetError("QSA: Couldn't find any hardware audio formats"); return 0; } this->spec.format=test_format; /* Set the audio format */ cparams.format.format=format; /* Set mono/stereo/4ch/6ch/8ch audio */ cparams.format.voices=this->spec.channels; /* Set rate */ cparams.format.rate=this->spec.freq; /* Setup the transfer parameters according to cparams */ status=snd_pcm_plugin_params(this->hidden->audio_handle, &cparams); if (status<0) { QSA_CloseDevice(this); QSA_SetError("snd_pcm_channel_params", status); return 0; } /* Make sure channel is setup right one last time */ SDL_memset(&csetup, '\0', sizeof(csetup)); if (!this->hidden->iscapture) { csetup.channel=SND_PCM_CHANNEL_PLAYBACK; } else { csetup.channel=SND_PCM_CHANNEL_CAPTURE; } /* Setup an audio channel */ if (snd_pcm_plugin_setup(this->hidden->audio_handle, &csetup) < 0) { QSA_CloseDevice(this); SDL_SetError("QSA: Unable to setup channel\n"); return 0; } /* Calculate the final parameters for this audio specification */ SDL_CalculateAudioSpec(&this->spec); this->hidden->pcm_len = this->spec.size; if (this->hidden->pcm_len==0) { this->hidden->pcm_len=csetup.buf.block.frag_size * this->spec.channels * (snd_pcm_format_width(format) / 8); } /* * Allocate memory to the audio buffer and initialize with silence * (Note that buffer size must be a multiple of fragment size, so find * closest multiple) */ this->hidden->pcm_buf=(Uint8*)SDL_AllocAudioMem(this->hidden->pcm_len); if (this->hidden->pcm_buf==NULL) { QSA_CloseDevice(this); SDL_OutOfMemory(); return 0; } SDL_memset(this->hidden->pcm_buf, this->spec.silence, this->hidden->pcm_len); /* get the file descriptor */ if (!this->hidden->iscapture) { this->hidden->audio_fd=snd_pcm_file_descriptor(this->hidden->audio_handle, SND_PCM_CHANNEL_PLAYBACK); } else { this->hidden->audio_fd=snd_pcm_file_descriptor(this->hidden->audio_handle, SND_PCM_CHANNEL_CAPTURE); } if (this->hidden->audio_fd<0) { QSA_CloseDevice(this); QSA_SetError("snd_pcm_file_descriptor", status); return 0; } /* Prepare an audio channel */ if (!this->hidden->iscapture) { /* Prepare audio playback */ status=snd_pcm_plugin_prepare(this->hidden->audio_handle, SND_PCM_CHANNEL_PLAYBACK); } else { /* Prepare audio capture */ status=snd_pcm_plugin_prepare(this->hidden->audio_handle, SND_PCM_CHANNEL_CAPTURE); } if (status<0) { QSA_CloseDevice(this); QSA_SetError("snd_pcm_plugin_prepare", status); return 0; } /* We're really ready to rock and roll. :-) */ return 1; } int QSA_DetectDevices(int iscapture) { uint32_t it; uint32_t cards; uint32_t devices; int32_t status; /* Detect amount of available devices */ /* this value can be changed in the runtime */ cards=snd_cards(); /* If io-audio manager is not running we will get 0 as number */ /* of available audio devices */ if (cards==0) { /* We have no any available audio devices */ return 0; } /* Find requested devices by type */ if (!iscapture) { /* Playback devices enumeration requested */ for(it=0; it<cards; it++) { devices=0; do { status=snd_card_get_longname(it, qsa_playback_device[qsa_playback_devices].name, QSA_MAX_NAME_LENGTH); if (status==EOK) { snd_pcm_t* handle; /* Add device number to device name */ sprintf(qsa_playback_device[qsa_playback_devices].name + SDL_strlen(qsa_playback_device[qsa_playback_devices].name), " d%d", devices); /* Store associated card number id */ qsa_playback_device[qsa_playback_devices].cardno=it; /* Check if this device id could play anything */ status=snd_pcm_open(&handle, it, devices, SND_PCM_OPEN_PLAYBACK); if (status==EOK) { qsa_playback_device[qsa_playback_devices].deviceno=devices; status=snd_pcm_close(handle); if (status==EOK) { qsa_playback_devices++; } } else { /* Check if we got end of devices list */ if (status==-ENOENT) { break; } } } else { break; } /* Check if we reached maximum devices count */ if (qsa_playback_devices>=QSA_MAX_DEVICES) { break; } devices++; } while(1); /* Check if we reached maximum devices count */ if (qsa_playback_devices>=QSA_MAX_DEVICES) { break; } } } else { /* Capture devices enumeration requested */ for(it=0; it<cards; it++) { devices=0; do { status=snd_card_get_longname(it, qsa_capture_device[qsa_capture_devices].name, QSA_MAX_NAME_LENGTH); if (status==EOK) { snd_pcm_t* handle; /* Add device number to device name */ sprintf(qsa_capture_device[qsa_capture_devices].name + SDL_strlen(qsa_capture_device[qsa_capture_devices].name), " d%d", devices); /* Store associated card number id */ qsa_capture_device[qsa_capture_devices].cardno=it; /* Check if this device id could play anything */ status=snd_pcm_open(&handle, it, devices, SND_PCM_OPEN_CAPTURE); if (status==EOK) { qsa_capture_device[qsa_capture_devices].deviceno=devices; status=snd_pcm_close(handle); if (status==EOK) { qsa_capture_devices++; } } else { /* Check if we got end of devices list */ if (status==-ENOENT) { break; } } /* Check if we reached maximum devices count */ if (qsa_capture_devices>=QSA_MAX_DEVICES) { break; } } else { break; } devices++; } while(1); /* Check if we reached maximum devices count */ if (qsa_capture_devices>=QSA_MAX_DEVICES) { break; } } } /* Return amount of available playback or capture devices */ if (!iscapture) { return qsa_playback_devices; } else { return qsa_capture_devices; } } const char* QSA_GetDeviceName(int index, int iscapture) { if (!iscapture) { if (index>=qsa_playback_devices) { return "No such playback device"; } return qsa_playback_device[index].name; } else { if (index>=qsa_capture_devices) { return "No such capture device"; } return qsa_capture_device[index].name; } } void QSA_WaitDone(_THIS) { if (!this->hidden->iscapture) { if (this->hidden->audio_handle!=NULL) { /* Wait till last fragment is played and stop channel */ snd_pcm_plugin_flush(this->hidden->audio_handle, SND_PCM_CHANNEL_PLAYBACK); } } else { if (this->hidden->audio_handle!=NULL) { /* Discard all unread data and stop channel */ snd_pcm_plugin_flush(this->hidden->audio_handle, SND_PCM_CHANNEL_CAPTURE); } } } void QSA_Deinitialize(void) { /* Clear devices array on shutdown */ SDL_memset(qsa_playback_device, 0x00, sizeof(QSA_Device)*QSA_MAX_DEVICES); SDL_memset(qsa_capture_device, 0x00, sizeof(QSA_Device)*QSA_MAX_DEVICES); qsa_playback_devices=0; qsa_capture_devices=0; } static int QSA_Init(SDL_AudioDriverImpl* impl) { snd_pcm_t* handle=NULL; int32_t status=0; /* Clear devices array */ SDL_memset(qsa_playback_device, 0x00, sizeof(QSA_Device)*QSA_MAX_DEVICES); SDL_memset(qsa_capture_device, 0x00, sizeof(QSA_Device)*QSA_MAX_DEVICES); qsa_playback_devices=0; qsa_capture_devices=0; /* Set function pointers */ /* DeviceLock and DeviceUnlock functions are used default, */ /* provided by SDL, which uses pthread_mutex for lock/unlock */ impl->DetectDevices=QSA_DetectDevices; impl->GetDeviceName=QSA_GetDeviceName; impl->OpenDevice=QSA_OpenDevice; impl->ThreadInit=QSA_ThreadInit; impl->WaitDevice=QSA_WaitDevice; impl->PlayDevice=QSA_PlayDevice; impl->GetDeviceBuf=QSA_GetDeviceBuf; impl->CloseDevice=QSA_CloseDevice; impl->WaitDone=QSA_WaitDone; impl->Deinitialize=QSA_Deinitialize; impl->LockDevice=NULL; impl->UnlockDevice=NULL; impl->OnlyHasDefaultOutputDevice=0; impl->ProvidesOwnCallbackThread=0; impl->SkipMixerLock=0; impl->HasCaptureSupport=1; impl->OnlyHasDefaultOutputDevice=0; impl->OnlyHasDefaultInputDevice=0; /* Check if io-audio manager is running or not */ status=snd_cards(); if (status==0) { /* if no, return immediately */ return 1; } /* At this point we are definitely has an audio device */ return 2; } AudioBootStrap QSAAUDIO_bootstrap= { DRIVER_NAME, "QNX QSA Audio", QSA_Init, 0 }; /* vi: set ts=4 sw=4 expandtab: */