summaryrefslogtreecommitdiffstats
path: root/media/libcubeb/src/cubeb_alsa.c
diff options
context:
space:
mode:
Diffstat (limited to 'media/libcubeb/src/cubeb_alsa.c')
-rw-r--r--media/libcubeb/src/cubeb_alsa.c560
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,