diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_alsa.c')
-rw-r--r-- | media/libcubeb/src/cubeb_alsa.c | 560 |
1 files changed, 429 insertions, 131 deletions
diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index 72a6acfb1..a564fbfc6 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -14,10 +14,58 @@ #include <limits.h> #include <poll.h> #include <unistd.h> +#include <dlfcn.h> #include <alsa/asoundlib.h> #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#ifdef DISABLE_LIBASOUND_DLOPEN +#define WRAP(x) x +#else +#define WRAP(x) cubeb_##x +#define LIBASOUND_API_VISIT(X) \ + X(snd_config) \ + X(snd_config_add) \ + X(snd_config_copy) \ + X(snd_config_delete) \ + X(snd_config_get_id) \ + X(snd_config_get_string) \ + X(snd_config_imake_integer) \ + X(snd_config_search) \ + X(snd_config_search_definition) \ + X(snd_lib_error_set_handler) \ + X(snd_pcm_avail_update) \ + X(snd_pcm_close) \ + X(snd_pcm_delay) \ + X(snd_pcm_drain) \ + X(snd_pcm_frames_to_bytes) \ + X(snd_pcm_get_params) \ + X(snd_pcm_hw_params_any) \ + X(snd_pcm_hw_params_get_channels_max) \ + X(snd_pcm_hw_params_get_rate) \ + X(snd_pcm_hw_params_set_rate_near) \ + X(snd_pcm_hw_params_sizeof) \ + X(snd_pcm_nonblock) \ + X(snd_pcm_open) \ + X(snd_pcm_open_lconf) \ + X(snd_pcm_pause) \ + X(snd_pcm_poll_descriptors) \ + X(snd_pcm_poll_descriptors_count) \ + X(snd_pcm_poll_descriptors_revents) \ + X(snd_pcm_readi) \ + X(snd_pcm_recover) \ + X(snd_pcm_set_params) \ + X(snd_pcm_start) \ + X(snd_pcm_state) \ + X(snd_pcm_writei) \ + +#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; +LIBASOUND_API_VISIT(MAKE_TYPEDEF); +#undef MAKE_TYPEDEF +/* snd_pcm_hw_params_alloca is actually a macro */ +#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof +#endif + #define CUBEB_STREAM_MAX 16 #define CUBEB_WATCHDOG_MS 10000 @@ -36,6 +84,7 @@ static struct cubeb_ops const alsa_ops; struct cubeb { struct cubeb_ops const * ops; + void * libasound; pthread_t thread; @@ -76,13 +125,15 @@ enum stream_state { }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ pthread_mutex_t mutex; snd_pcm_t * pcm; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; - snd_pcm_uframes_t write_position; + snd_pcm_uframes_t stream_position; snd_pcm_uframes_t last_position; snd_pcm_uframes_t buffer_size; cubeb_stream_params params; @@ -107,6 +158,12 @@ struct cubeb_stream { being logically active and playing. */ struct timeval last_activity; float volume; + + char * buffer; + snd_pcm_uframes_t bufframes; + snd_pcm_stream_t stream_type; + + struct cubeb_stream * other_stream; }; static int @@ -235,6 +292,14 @@ set_timeout(struct timeval * timeout, unsigned int ms) } static void +stream_buffer_decrement(cubeb_stream * stm, long count) +{ + char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); + memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count)); + stm->bufframes -= count; +} + +static void alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) { cubeb * ctx; @@ -249,97 +314,173 @@ alsa_set_stream_state(cubeb_stream * stm, enum stream_state state) } static enum stream_state -alsa_refill_stream(cubeb_stream * stm) +alsa_process_stream(cubeb_stream * stm) { + unsigned short revents; snd_pcm_sframes_t avail; - long got; - void * p; int draining; draining = 0; pthread_mutex_lock(&stm->mutex); - avail = snd_pcm_avail_update(stm->pcm); - if (avail < 0) { - snd_pcm_recover(stm->pcm, avail, 1); - avail = snd_pcm_avail_update(stm->pcm); - } + /* Call _poll_descriptors_revents() even if we don't use it + to let underlying plugins clear null events. Otherwise poll() + may wake up again and again, producing unnecessary CPU usage. */ + WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents); - /* Failed to recover from an xrun, this stream must be broken. */ - if (avail < 0) { + avail = WRAP(snd_pcm_avail_update)(stm->pcm); + + /* Got null event? Bail and wait for another wakeup. */ + if (avail == 0) { pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; + return RUNNING; } - /* This should never happen. */ + /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */ if ((unsigned int) avail > stm->buffer_size) { avail = stm->buffer_size; } - /* poll(2) claims this stream is active, so there should be some space - available to write. If avail is still zero here, the stream must be in - a funky state, bail and wait for another wakeup. */ - if (avail == 0) { + /* Capture: Read available frames */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && avail > 0) { + snd_pcm_sframes_t got; + + if (avail + stm->bufframes > stm->buffer_size) { + /* Buffer overflow. Skip and overwrite with new data. */ + stm->bufframes = 0; + // TODO: should it be marked as DRAINING? + } + + got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + stm->stream_position += got; + + gettimeofday(&stm->last_activity, NULL); + } + } + + /* Capture: Pass read frames to callback function */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && + (!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) { + snd_pcm_sframes_t wrote = stm->bufframes; + struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL; + + /* Correct write size to the other stream available space */ + if (stm->other_stream && wrote > (snd_pcm_sframes_t) (stm->other_stream->buffer_size - stm->other_stream->bufframes)) { + wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; + } + pthread_mutex_unlock(&stm->mutex); - return RUNNING; + wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote); + pthread_mutex_lock(&stm->mutex); + + if (wrote < 0) { + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + if (stm->other_stream) { + stm->other_stream->bufframes += wrote; + } + } } - p = calloc(1, snd_pcm_frames_to_bytes(stm->pcm, avail)); - assert(p); + /* Playback: Don't have enough data? Let's ask for more. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes && + (!stm->other_stream || stm->other_stream->bufframes > 0)) { + long got = avail - stm->bufframes; + void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; + char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); + + /* Correct read size to the other stream available frames */ + if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) { + got = stm->other_stream->bufframes; + } - pthread_mutex_unlock(&stm->mutex); - got = stm->data_callback(stm, stm->user_ptr, NULL, p, avail); - pthread_mutex_lock(&stm->mutex); - if (got < 0) { pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - free(p); - return ERROR; + got = stm->data_callback(stm, stm->user_ptr, other_buffer, buftail, got); + pthread_mutex_lock(&stm->mutex); + + if (got < 0) { + avail = got; // the error handler below will recover us + } else { + stm->bufframes += got; + + if (stm->other_stream) { + stream_buffer_decrement(stm->other_stream, got); + } + } } - if (got > 0) { + + /* Playback: Still don't have enough data? Add some silence. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes) { + long drain_frames = avail - stm->bufframes; + double drain_time = (double) drain_frames / stm->params.rate; + + char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); + memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames)); + stm->bufframes = avail; + + /* Mark as draining, unless we're waiting for capture */ + if (!stm->other_stream || stm->other_stream->bufframes > 0) { + set_timeout(&stm->drain_timeout, drain_time * 1000); + + draining = 1; + } + } + + /* Playback: Have enough data and no errors. Let's write it out. */ + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > 0) { snd_pcm_sframes_t wrote; if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { - float * b = (float *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + float * b = (float *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } else { - short * b = (short *) p; - for (uint32_t i = 0; i < got * stm->params.channels; i++) { + short * b = (short *) stm->buffer; + for (uint32_t i = 0; i < avail * stm->params.channels; i++) { b[i] *= stm->volume; } } - wrote = snd_pcm_writei(stm->pcm, p, got); + + wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail); if (wrote < 0) { - snd_pcm_recover(stm->pcm, wrote, 1); - wrote = snd_pcm_writei(stm->pcm, p, got); - } - if (wrote < 0 || wrote != got) { - /* Recovery failed, somehow. */ - pthread_mutex_unlock(&stm->mutex); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return ERROR; + avail = wrote; // the error handler below will recover us + } else { + stream_buffer_decrement(stm, wrote); + + stm->stream_position += wrote; + gettimeofday(&stm->last_activity, NULL); } - stm->write_position += wrote; - gettimeofday(&stm->last_activity, NULL); } - if (got != avail) { - long buffer_fill = stm->buffer_size - (avail - got); - double buffer_time = (double) buffer_fill / stm->params.rate; - /* Fill the remaining buffer with silence to guarantee one full period - has been written. */ - snd_pcm_writei(stm->pcm, (char *) p + got, avail - got); + /* Got some error? Let's try to recover the stream. */ + if (avail < 0) { + avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); - set_timeout(&stm->drain_timeout, buffer_time * 1000); + /* Capture pcm must be started after initial setup/recover */ + if (avail >= 0 && + stm->stream_type == SND_PCM_STREAM_CAPTURE && + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + avail = WRAP(snd_pcm_start)(stm->pcm); + } + } - draining = 1; + /* Failed to recover, this stream must be broken. */ + if (avail < 0) { + pthread_mutex_unlock(&stm->mutex); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return ERROR; } - free(p); pthread_mutex_unlock(&stm->mutex); return draining ? DRAINING : RUNNING; } @@ -395,7 +536,7 @@ alsa_run(cubeb * ctx) if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) { alsa_set_stream_state(stm, PROCESSING); pthread_mutex_unlock(&ctx->mutex); - state = alsa_refill_stream(stm); + state = alsa_process_stream(stm); pthread_mutex_lock(&ctx->mutex); alsa_set_stream_state(stm, state); } @@ -445,26 +586,26 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) slave_def = NULL; - r = snd_config_search(root_pcm, "slave", &slave_pcm); + r = WRAP(snd_config_search)(root_pcm, "slave", &slave_pcm); if (r < 0) { return NULL; } - r = snd_config_get_string(slave_pcm, &string); + r = WRAP(snd_config_get_string)(slave_pcm, &string); if (r >= 0) { - r = snd_config_search_definition(lconf, "pcm_slave", string, &slave_def); + r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def); if (r < 0) { return NULL; } } do { - r = snd_config_search(slave_def ? slave_def : slave_pcm, "pcm", &pcm); + r = WRAP(snd_config_search)(slave_def ? slave_def : slave_pcm, "pcm", &pcm); if (r < 0) { break; } - r = snd_config_get_string(slave_def ? slave_def : slave_pcm, &string); + r = WRAP(snd_config_get_string)(slave_def ? slave_def : slave_pcm, &string); if (r < 0) { break; } @@ -473,7 +614,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = snd_config_search(lconf, node_name, &pcm); + r = WRAP(snd_config_search)(lconf, node_name, &pcm); if (r < 0) { break; } @@ -482,7 +623,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm) } while (0); if (slave_def) { - snd_config_delete(slave_def); + WRAP(snd_config_delete)(slave_def); } return NULL; @@ -505,22 +646,22 @@ init_local_config_with_workaround(char const * pcm_name) lconf = NULL; - if (snd_config == NULL) { + if (*WRAP(snd_config) == NULL) { return NULL; } - r = snd_config_copy(&lconf, snd_config); + r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config)); if (r < 0) { return NULL; } do { - r = snd_config_search_definition(lconf, "pcm", pcm_name, &pcm_node); + r = WRAP(snd_config_search_definition)(lconf, "pcm", pcm_name, &pcm_node); if (r < 0) { break; } - r = snd_config_get_id(pcm_node, &string); + r = WRAP(snd_config_get_id)(pcm_node, &string); if (r < 0) { break; } @@ -529,7 +670,7 @@ init_local_config_with_workaround(char const * pcm_name) if (r < 0 || r > (int) sizeof(node_name)) { break; } - r = snd_config_search(lconf, node_name, &pcm_node); + r = WRAP(snd_config_search)(lconf, node_name, &pcm_node); if (r < 0) { break; } @@ -540,12 +681,12 @@ init_local_config_with_workaround(char const * pcm_name) } /* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ - r = snd_config_search(pcm_node, "type", &node); + r = WRAP(snd_config_search)(pcm_node, "type", &node); if (r < 0) { break; } - r = snd_config_get_string(node, &string); + r = WRAP(snd_config_get_string)(node, &string); if (r < 0) { break; } @@ -556,18 +697,18 @@ init_local_config_with_workaround(char const * pcm_name) /* Don't clobber an explicit existing handle_underrun value, set it only if it doesn't already exist. */ - r = snd_config_search(pcm_node, "handle_underrun", &node); + r = WRAP(snd_config_search)(pcm_node, "handle_underrun", &node); if (r != -ENOENT) { break; } /* Disable pcm_pulse's asynchronous underrun handling. */ - r = snd_config_imake_integer(&node, "handle_underrun", 0); + r = WRAP(snd_config_imake_integer)(&node, "handle_underrun", 0); if (r < 0) { break; } - r = snd_config_add(pcm_node, node); + r = WRAP(snd_config_add)(pcm_node, node); if (r < 0) { break; } @@ -575,21 +716,21 @@ init_local_config_with_workaround(char const * pcm_name) return lconf; } while (0); - snd_config_delete(lconf); + WRAP(snd_config_delete)(lconf); return NULL; } static int -alsa_locked_pcm_open(snd_pcm_t ** pcm, snd_pcm_stream_t stream, snd_config_t * local_config) +alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) { int r; pthread_mutex_lock(&cubeb_alsa_mutex); if (local_config) { - r = snd_pcm_open_lconf(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK, local_config); + r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); } else { - r = snd_pcm_open(pcm, CUBEB_ALSA_PCM_NAME, stream, SND_PCM_NONBLOCK); + r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -602,7 +743,7 @@ alsa_locked_pcm_close(snd_pcm_t * pcm) int r; pthread_mutex_lock(&cubeb_alsa_mutex); - r = snd_pcm_close(pcm); + r = WRAP(snd_pcm_close)(pcm); pthread_mutex_unlock(&cubeb_alsa_mutex); return r; @@ -658,6 +799,7 @@ silent_error_handler(char const * file, int line, char const * function, alsa_init(cubeb ** context, char const * context_name) { (void)context_name; + void * libasound = NULL; cubeb * ctx; int r; int i; @@ -668,9 +810,30 @@ alsa_init(cubeb ** context, char const * context_name) assert(context); *context = NULL; +#ifndef DISABLE_LIBASOUND_DLOPEN + libasound = dlopen("libasound.so.2", RTLD_LAZY); + if (!libasound) { + libasound = dlopen("libasound.so", RTLD_LAZY); + if (!libasound) { + return CUBEB_ERROR; + } + } + +#define LOAD(x) { \ + cubeb_##x = dlsym(libasound, #x); \ + if (!cubeb_##x) { \ + dlclose(libasound); \ + return CUBEB_ERROR; \ + } \ + } + + LIBASOUND_API_VISIT(LOAD); +#undef LOAD +#endif + pthread_mutex_lock(&cubeb_alsa_mutex); if (!cubeb_alsa_error_handler_set) { - snd_lib_error_set_handler(silent_error_handler); + WRAP(snd_lib_error_set_handler)(silent_error_handler); cubeb_alsa_error_handler_set = 1; } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -679,6 +842,7 @@ alsa_init(cubeb ** context, char const * context_name) assert(ctx); ctx->ops = &alsa_ops; + ctx->libasound = libasound; r = pthread_mutex_init(&ctx->mutex, NULL); assert(r == 0); @@ -712,7 +876,7 @@ alsa_init(cubeb ** context, char const * context_name) /* Open a dummy PCM to force the configuration space to be evaluated so that init_local_config_with_workaround can find and modify the default node. */ - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, NULL); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL); if (r >= 0) { alsa_locked_pcm_close(dummy); } @@ -722,12 +886,12 @@ alsa_init(cubeb ** context, char const * context_name) pthread_mutex_unlock(&cubeb_alsa_mutex); if (ctx->local_config) { ctx->is_pa = 1; - r = alsa_locked_pcm_open(&dummy, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config); /* If we got a local_config, we found a PA PCM. If opening a PCM with that config fails with EINVAL, the PA PCM is too old for this workaround. */ if (r == -EINVAL) { pthread_mutex_lock(&cubeb_alsa_mutex); - snd_config_delete(ctx->local_config); + WRAP(snd_config_delete)(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); ctx->local_config = NULL; } else if (r >= 0) { @@ -769,24 +933,28 @@ alsa_destroy(cubeb * ctx) if (ctx->local_config) { pthread_mutex_lock(&cubeb_alsa_mutex); - snd_config_delete(ctx->local_config); + WRAP(snd_config_delete)(ctx->local_config); pthread_mutex_unlock(&cubeb_alsa_mutex); } + if (ctx->libasound) { + dlclose(ctx->libasound); + } + free(ctx); } static void alsa_stream_destroy(cubeb_stream * stm); static int -alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, - cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, cubeb_state_callback state_callback, - void * user_ptr) +alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + snd_pcm_stream_t stream_type, + cubeb_devid deviceid, + cubeb_stream_params * stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, + void * user_ptr) { (void)stream_name; cubeb_stream * stm; @@ -794,23 +962,17 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, snd_pcm_format_t format; snd_pcm_uframes_t period_size; int latency_us = 0; - + char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME; assert(ctx && stream); - if (input_stream_params) { - /* Capture support not yet implemented. */ - return CUBEB_ERROR_NOT_SUPPORTED; - } + *stream = NULL; - if (input_device || output_device) { - /* Device selection not yet implemented. */ - return CUBEB_ERROR_DEVICE_UNAVAILABLE; + if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + return CUBEB_ERROR_NOT_SUPPORTED; } - *stream = NULL; - - switch (output_stream_params->format) { + switch (stream_params->format) { case CUBEB_SAMPLE_S16LE: format = SND_PCM_FORMAT_S16_LE; break; @@ -842,20 +1004,27 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; - stm->params = *output_stream_params; + stm->params = *stream_params; stm->state = INACTIVE; stm->volume = 1.0; + stm->buffer = NULL; + stm->bufframes = 0; + stm->stream_type = stream_type; + stm->other_stream = NULL; r = pthread_mutex_init(&stm->mutex, NULL); assert(r == 0); - r = alsa_locked_pcm_open(&stm->pcm, SND_PCM_STREAM_PLAYBACK, ctx->local_config); + r = pthread_cond_init(&stm->cond, NULL); + assert(r == 0); + + r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config); if (r < 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; } - r = snd_pcm_nonblock(stm->pcm, 1); + r = WRAP(snd_pcm_nonblock)(stm->pcm, 1); assert(r == 0); latency_us = latency_frames * 1e6 / stm->params.rate; @@ -868,7 +1037,7 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, latency_us = latency_us < min_latency ? min_latency: latency_us; } - r = snd_pcm_set_params(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, + r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, stm->params.channels, stm->params.rate, 1, latency_us); if (r < 0) { @@ -876,20 +1045,22 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_ERROR_INVALID_FORMAT; } - r = snd_pcm_get_params(stm->pcm, &stm->buffer_size, &period_size); + r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size); assert(r == 0); - stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); + /* Double internal buffer size to have enough space when waiting for the other side of duplex connection */ + stm->buffer_size *= 2; + stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); + assert(stm->buffer); + + stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm); assert(stm->nfds > 0); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); assert(stm->saved_fds); - r = snd_pcm_poll_descriptors(stm->pcm, stm->saved_fds, stm->nfds); + r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds); assert((nfds_t) r == stm->nfds); - r = pthread_cond_init(&stm->cond, NULL); - assert(r == 0); - if (alsa_register_stream(ctx, stm) != 0) { alsa_stream_destroy(stm); return CUBEB_ERROR; @@ -900,6 +1071,45 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, return CUBEB_OK; } +static int +alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, + cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, cubeb_state_callback state_callback, + void * user_ptr) +{ + int result = CUBEB_OK; + cubeb_stream * instm = NULL, * outstm = NULL; + + if (result == CUBEB_OK && input_stream_params) { + result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE, + input_device, input_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && output_stream_params) { + result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK, + output_device, output_stream_params, latency_frames, + data_callback, state_callback, user_ptr); + } + + if (result == CUBEB_OK && input_stream_params && output_stream_params) { + instm->other_stream = outstm; + outstm->other_stream = instm; + } + + if (result != CUBEB_OK && instm) { + alsa_stream_destroy(instm); + } + + *stream = outstm ? outstm : instm; + + return result; +} + static void alsa_stream_destroy(cubeb_stream * stm) { @@ -912,10 +1122,15 @@ alsa_stream_destroy(cubeb_stream * stm) ctx = stm->context; + if (stm->other_stream) { + stm->other_stream->other_stream = NULL; // to stop infinite recursion + alsa_stream_destroy(stm->other_stream); + } + pthread_mutex_lock(&stm->mutex); if (stm->pcm) { if (stm->state == DRAINING) { - snd_pcm_drain(stm->pcm); + WRAP(snd_pcm_drain)(stm->pcm); } alsa_locked_pcm_close(stm->pcm); stm->pcm = NULL; @@ -934,6 +1149,8 @@ alsa_stream_destroy(cubeb_stream * stm) ctx->active_streams -= 1; pthread_mutex_unlock(&ctx->mutex); + free(stm->buffer); + free(stm); } @@ -957,12 +1174,14 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_ERROR; } - r = snd_pcm_hw_params_any(stm->pcm, hw_params); + assert(stm); + + r = WRAP(snd_pcm_hw_params_any)(stm->pcm, hw_params); if (r < 0) { return CUBEB_ERROR; } - r = snd_pcm_hw_params_get_channels_max(hw_params, max_channels); + r = WRAP(snd_pcm_hw_params_get_channels_max)(hw_params, max_channels); if (r < 0) { return CUBEB_ERROR; } @@ -983,34 +1202,34 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { /* get a pcm, disabling resampling, so we get a rate the * hardware/dmix/pulse/etc. supports. */ - r = snd_pcm_open(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); + r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); if (r < 0) { return CUBEB_ERROR; } - r = snd_pcm_hw_params_any(pcm, hw_params); + r = WRAP(snd_pcm_hw_params_any)(pcm, hw_params); if (r < 0) { - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_ERROR; } - r = snd_pcm_hw_params_get_rate(hw_params, rate, &dir); + r = WRAP(snd_pcm_hw_params_get_rate)(hw_params, rate, &dir); if (r >= 0) { /* There is a default rate: use it. */ - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_OK; } /* Use a common rate, alsa may adjust it based on hw/etc. capabilities. */ *rate = 44100; - r = snd_pcm_hw_params_set_rate_near(pcm, hw_params, rate, NULL); + r = WRAP(snd_pcm_hw_params_set_rate_near)(pcm, hw_params, rate, NULL); if (r < 0) { - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_ERROR; } - snd_pcm_close(pcm); + WRAP(snd_pcm_close)(pcm); return CUBEB_OK; } @@ -1034,8 +1253,19 @@ alsa_stream_start(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_start(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&stm->mutex); - snd_pcm_pause(stm->pcm, 0); + /* Capture pcm must be started after initial setup/recover */ + if (stm->stream_type == SND_PCM_STREAM_CAPTURE && + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + WRAP(snd_pcm_start)(stm->pcm); + } + WRAP(snd_pcm_pause)(stm->pcm, 0); gettimeofday(&stm->last_activity, NULL); pthread_mutex_unlock(&stm->mutex); @@ -1059,6 +1289,12 @@ alsa_stream_stop(cubeb_stream * stm) assert(stm); ctx = stm->context; + if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && stm->other_stream) { + int r = alsa_stream_stop(stm->other_stream); + if (r != CUBEB_OK) + return r; + } + pthread_mutex_lock(&ctx->mutex); while (stm->state == PROCESSING) { r = pthread_cond_wait(&stm->cond, &ctx->mutex); @@ -1069,7 +1305,7 @@ alsa_stream_stop(cubeb_stream * stm) pthread_mutex_unlock(&ctx->mutex); pthread_mutex_lock(&stm->mutex); - snd_pcm_pause(stm->pcm, 1); + WRAP(snd_pcm_pause)(stm->pcm, 1); pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1085,8 +1321,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) pthread_mutex_lock(&stm->mutex); delay = -1; - if (snd_pcm_state(stm->pcm) != SND_PCM_STATE_RUNNING || - snd_pcm_delay(stm->pcm, &delay) != 0) { + if (WRAP(snd_pcm_state)(stm->pcm) != SND_PCM_STATE_RUNNING || + WRAP(snd_pcm_delay)(stm->pcm, &delay) != 0) { *position = stm->last_position; pthread_mutex_unlock(&stm->mutex); return CUBEB_OK; @@ -1095,8 +1331,8 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) assert(delay >= 0); *position = 0; - if (stm->write_position >= (snd_pcm_uframes_t) delay) { - *position = stm->write_position - delay; + if (stm->stream_position >= (snd_pcm_uframes_t) delay) { + *position = stm->stream_position - delay; } stm->last_position = *position; @@ -1111,7 +1347,7 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency) snd_pcm_sframes_t delay; /* This function returns the delay in frames until a frame written using snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */ - if (snd_pcm_delay(stm->pcm, &delay)) { + if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) { return CUBEB_ERROR; } @@ -1131,22 +1367,84 @@ alsa_stream_set_volume(cubeb_stream * stm, float volume) return CUBEB_OK; } +static int +alsa_enumerate_devices(cubeb * context, cubeb_device_type type, + cubeb_device_collection * collection) +{ + cubeb_device_info* device = NULL; + + if (!context) + return CUBEB_ERROR; + + uint32_t rate, max_channels; + int r; + + r = alsa_get_preferred_sample_rate(context, &rate); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + r = alsa_get_max_channel_count(context, &max_channels); + if (r != CUBEB_OK) { + return CUBEB_ERROR; + } + + char const * a_name = "default"; + device = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info)); + assert(device); + if (!device) + return CUBEB_ERROR; + + device->device_id = a_name; + device->devid = (cubeb_devid) device->device_id; + device->friendly_name = a_name; + device->group_id = a_name; + device->vendor_name = a_name; + device->type = type; + device->state = CUBEB_DEVICE_STATE_ENABLED; + device->preferred = CUBEB_DEVICE_PREF_ALL; + device->format = CUBEB_DEVICE_FMT_S16NE; + device->default_format = CUBEB_DEVICE_FMT_S16NE; + device->max_channels = max_channels; + device->min_rate = rate; + device->max_rate = rate; + device->default_rate = rate; + device->latency_lo = 0; + device->latency_hi = 0; + + collection->device = device; + collection->count = 1; + + return CUBEB_OK; +} + +static int +alsa_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection) +{ + assert(collection->count == 1); + (void) context; + free(collection->device); + return CUBEB_OK; +} + static struct cubeb_ops const alsa_ops = { .init = alsa_init, .get_backend_id = alsa_get_backend_id, .get_max_channel_count = alsa_get_max_channel_count, .get_min_latency = alsa_get_min_latency, .get_preferred_sample_rate = alsa_get_preferred_sample_rate, - .enumerate_devices = NULL, + .enumerate_devices = alsa_enumerate_devices, + .device_collection_destroy = alsa_device_collection_destroy, .destroy = alsa_destroy, .stream_init = alsa_stream_init, .stream_destroy = alsa_stream_destroy, .stream_start = alsa_stream_start, .stream_stop = alsa_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency, .stream_set_volume = alsa_stream_set_volume, - .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, |