diff options
author | Chris Robinson <[email protected]> | 2012-02-29 10:01:45 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2012-02-29 10:01:45 -0800 |
commit | e88f954cbf8549e78d477de1588608c10a2487a2 (patch) | |
tree | a1f6bd64a30a6a40afdd6daa49b652c4f3103006 | |
parent | fe897f291b2cf03fcb8dd4bfb8a6e31a26e271ee (diff) |
Allow reading audio from ALSA directly if the requested length is supported
-rw-r--r-- | Alc/backends/alsa.c | 169 |
1 files changed, 142 insertions, 27 deletions
diff --git a/Alc/backends/alsa.c b/Alc/backends/alsa.c index 150ebc72..7b46a9c2 100644 --- a/Alc/backends/alsa.c +++ b/Alc/backends/alsa.c @@ -82,6 +82,7 @@ MAKE_FUNC(snd_pcm_mmap_commit); MAKE_FUNC(snd_pcm_readi); MAKE_FUNC(snd_pcm_writei); MAKE_FUNC(snd_pcm_drain); +MAKE_FUNC(snd_pcm_drop); MAKE_FUNC(snd_pcm_recover); MAKE_FUNC(snd_pcm_info_malloc); MAKE_FUNC(snd_pcm_info_free); @@ -148,6 +149,7 @@ MAKE_FUNC(snd_card_next); #define snd_pcm_readi psnd_pcm_readi #define snd_pcm_writei psnd_pcm_writei #define snd_pcm_drain psnd_pcm_drain +#define snd_pcm_drop psnd_pcm_drop #define snd_pcm_recover psnd_pcm_recover #define snd_pcm_info_malloc psnd_pcm_info_malloc #define snd_pcm_info_free psnd_pcm_info_free @@ -232,6 +234,7 @@ static ALCboolean alsa_load(void) LOAD_FUNC(snd_pcm_readi); LOAD_FUNC(snd_pcm_writei); LOAD_FUNC(snd_pcm_drain); + LOAD_FUNC(snd_pcm_drop); LOAD_FUNC(snd_pcm_recover); LOAD_FUNC(snd_pcm_info_malloc); LOAD_FUNC(snd_pcm_info_free); @@ -267,6 +270,8 @@ typedef struct { ALboolean doCapture; RingBuffer *ring; + snd_pcm_sframes_t last_avail; + volatile int killNow; ALvoid *thread; } alsa_data; @@ -837,9 +842,9 @@ static ALCenum alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) snd_pcm_hw_params_t *hp; snd_pcm_uframes_t bufferSizeInFrames; snd_pcm_uframes_t periodSizeInFrames; + ALboolean needring = AL_FALSE; snd_pcm_format_t format; const char *funcerr; - ALuint frameSize; alsa_data *data; int err; @@ -906,7 +911,7 @@ static ALCenum alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) funcerr = NULL; bufferSizeInFrames = maxu(pDevice->UpdateSize*pDevice->NumUpdates, 100*pDevice->Frequency/1000); - periodSizeInFrames = minu(bufferSizeInFrames, 50*pDevice->Frequency/1000); + periodSizeInFrames = minu(bufferSizeInFrames, 25*pDevice->Frequency/1000); snd_pcm_hw_params_malloc(&hp); #define CHECK(x) if((funcerr=#x),(err=(x)) < 0) goto error @@ -920,32 +925,39 @@ static ALCenum alsa_open_capture(ALCdevice *pDevice, const ALCchar *deviceName) /* set rate (implicitly constrains period/buffer parameters) */ CHECK(snd_pcm_hw_params_set_rate(data->pcmHandle, hp, pDevice->Frequency, 0)); /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ - CHECK(snd_pcm_hw_params_set_buffer_size_near(data->pcmHandle, hp, &bufferSizeInFrames)); + if(snd_pcm_hw_params_set_buffer_size_min(data->pcmHandle, hp, &bufferSizeInFrames) < 0) + { + TRACE("Buffer too large, using intermediate ring buffer\n"); + needring = AL_TRUE; + CHECK(snd_pcm_hw_params_set_buffer_size_near(data->pcmHandle, hp, &bufferSizeInFrames)); + } /* set buffer size in frame units (implicitly sets period size/bytes/time and buffer time/bytes) */ CHECK(snd_pcm_hw_params_set_period_size_near(data->pcmHandle, hp, &periodSizeInFrames, NULL)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(data->pcmHandle, hp)); /* retrieve configuration info */ - CHECK(snd_pcm_hw_params_get_period_size(hp, &bufferSizeInFrames, NULL)); + CHECK(snd_pcm_hw_params_get_period_size(hp, &periodSizeInFrames, NULL)); #undef CHECK snd_pcm_hw_params_free(hp); hp = NULL; - frameSize = FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType); - - data->ring = CreateRingBuffer(frameSize, pDevice->UpdateSize*pDevice->NumUpdates); - if(!data->ring) + if(needring) { - ERR("ring buffer create failed\n"); - goto error2; - } + data->ring = CreateRingBuffer(FrameSizeFromDevFmt(pDevice->FmtChans, pDevice->FmtType), + pDevice->UpdateSize*pDevice->NumUpdates); + if(!data->ring) + { + ERR("ring buffer create failed\n"); + goto error2; + } - data->size = snd_pcm_frames_to_bytes(data->pcmHandle, bufferSizeInFrames); - data->buffer = malloc(data->size); - if(!data->buffer) - { - ERR("buffer malloc failed\n"); - goto error2; + data->size = snd_pcm_frames_to_bytes(data->pcmHandle, periodSizeInFrames); + data->buffer = malloc(data->size); + if(!data->buffer) + { + ERR("buffer malloc failed\n"); + goto error2; + } } pDevice->szDeviceName = strdup(deviceName); @@ -994,26 +1006,88 @@ static void alsa_start_capture(ALCdevice *Device) data->doCapture = AL_TRUE; } -static void alsa_stop_capture(ALCdevice *Device) -{ - alsa_data *data = (alsa_data*)Device->ExtraData; - snd_pcm_drain(data->pcmHandle); - data->doCapture = AL_FALSE; -} - static ALCenum alsa_capture_samples(ALCdevice *Device, ALCvoid *Buffer, ALCuint Samples) { alsa_data *data = (alsa_data*)Device->ExtraData; - ReadRingBuffer(data->ring, Buffer, Samples); + + if(data->ring) + { + ReadRingBuffer(data->ring, Buffer, Samples); + return ALC_NO_ERROR; + } + + data->last_avail -= Samples; + while(Device->Connected && Samples > 0) + { + snd_pcm_sframes_t amt = 0; + + if(data->size > 0) + { + /* First get any data stored from the last stop */ + amt = snd_pcm_bytes_to_frames(data->pcmHandle, data->size); + if((snd_pcm_uframes_t)amt > Samples) amt = Samples; + + amt = snd_pcm_frames_to_bytes(data->pcmHandle, amt); + memmove(Buffer, data->buffer, amt); + + if(data->size > amt) + { + memmove(data->buffer, data->buffer+amt, data->size - amt); + data->size -= amt; + } + else + { + free(data->buffer); + data->buffer = NULL; + data->size = 0; + } + amt = snd_pcm_bytes_to_frames(data->pcmHandle, amt); + } + else if(data->doCapture) + amt = snd_pcm_readi(data->pcmHandle, Buffer, Samples); + if(amt < 0) + { + ERR("read error: %s\n", snd_strerror(amt)); + + if(amt == -EAGAIN) + continue; + if((amt=snd_pcm_recover(data->pcmHandle, amt, 1)) >= 0) + { + if(data->doCapture) + amt = snd_pcm_start(data->pcmHandle); + if(amt >= 0) + amt = snd_pcm_avail_update(data->pcmHandle); + } + if(amt < 0) + { + ERR("restore error: %s\n", snd_strerror(amt)); + aluHandleDisconnect(Device); + break; + } + /* If the amount available is less than what's asked, we lost it + * during recovery. So just give silence instead. */ + if((snd_pcm_uframes_t)amt < Samples) + break; + continue; + } + + Buffer = (ALbyte*)Buffer + amt; + Samples -= amt; + } + if(Samples > 0) + memset(Buffer, ((Device->FmtType == DevFmtUByte) ? 0x80 : 0), + snd_pcm_frames_to_bytes(data->pcmHandle, Samples)); + return ALC_NO_ERROR; } static ALCuint alsa_available_samples(ALCdevice *Device) { alsa_data *data = (alsa_data*)Device->ExtraData; - snd_pcm_sframes_t avail; + snd_pcm_sframes_t avail = 0; - avail = (Device->Connected ? snd_pcm_avail_update(data->pcmHandle) : 0); + if(Device->Connected && data->doCapture) + avail = snd_pcm_avail_update(data->pcmHandle); if(avail < 0) { ERR("avail update failed: %s\n", snd_strerror(avail)); @@ -1031,6 +1105,15 @@ static ALCuint alsa_available_samples(ALCdevice *Device) aluHandleDisconnect(Device); } } + + if(!data->ring) + { + if(avail < 0) avail = 0; + avail += snd_pcm_bytes_to_frames(data->pcmHandle, data->size); + if(avail > data->last_avail) data->last_avail = avail; + return data->last_avail; + } + while(avail > 0) { snd_pcm_sframes_t amt; @@ -1069,6 +1152,38 @@ static ALCuint alsa_available_samples(ALCdevice *Device) return RingBufferSize(data->ring); } +static void alsa_stop_capture(ALCdevice *Device) +{ + alsa_data *data = (alsa_data*)Device->ExtraData; + ALCuint avail; + int err; + + /* OpenAL requires access to unread audio after stopping, but ALSA's + * snd_pcm_drain is unreliable and snd_pcm_drop drops it. Capture what's + * available now so it'll be available later after the drop. */ + avail = alsa_available_samples(Device); + if(!data->ring && avail > 0) + { + /* The ring buffer implicitly captures when checking availability. + * Direct access needs to explicitly capture it into temp storage. */ + ALsizei size; + void *ptr; + + size = snd_pcm_frames_to_bytes(data->pcmHandle, avail); + ptr = realloc(data->buffer, size); + if(ptr) + { + data->buffer = ptr; + alsa_capture_samples(Device, data->buffer, avail); + data->size = size; + } + } + err = snd_pcm_drop(data->pcmHandle); + if(err < 0) + ERR("drop failed: %s\n", snd_strerror(err)); + data->doCapture = AL_FALSE; +} + static const BackendFuncs alsa_funcs = { alsa_open_playback, |