diff --git build/moz.configure/old.configure build/moz.configure/old.configure index cd6c37cf4c7c..e98dc7973a6a 100644 --- build/moz.configure/old.configure +++ build/moz.configure/old.configure @@ -275,6 +275,7 @@ def old_configure_options(*options): '--with-nspr-prefix', '--with-nss-exec-prefix', '--with-nss-prefix', + '--with-oss', '--with-pthreads', '--with-qemu-exe', '--with-sixgill', diff --git old-configure.in old-configure.in index dff46614635e..dbbfdb76ab78 100644 --- old-configure.in +++ old-configure.in @@ -3128,6 +3128,67 @@ AC_DEFINE(MOZ_WEBM_ENCODER) AC_SUBST(MOZ_WEBM_ENCODER) dnl ================================== +dnl = Check OSS availability +dnl ================================== + +dnl If using Linux, Solaris or BSDs, ensure that OSS is available +case "$OS_TARGET" in +Linux|SunOS|DragonFly|FreeBSD|NetBSD|GNU/kFreeBSD) + MOZ_OSS=1 + ;; +esac + +MOZ_ARG_WITH_STRING(oss, +[ --with-oss[=PFX] Enable OpenSoundSystem support [installed at prefix PFX]], + OSSPREFIX=$withval) + +if test -n "$OSSPREFIX"; then + if test "$OSSPREFIX" != "no"; then + MOZ_OSS=1 + else + MOZ_OSS= + fi +fi + +_SAVE_CFLAGS=$CFLAGS +_SAVE_LIBS=$LIBS +if test -n "$MOZ_OSS"; then + dnl Prefer 4Front implementation + AC_MSG_CHECKING([MOZ_OSS_CFLAGS]) + if test "$OSSPREFIX" != "yes"; then + oss_conf=${OSSPREFIX%/usr}/etc/oss.conf + if test -f "$oss_conf"; then + . "$oss_conf" + else + OSSLIBDIR=$OSSPREFIX/lib/oss + fi + if test -d "$OSSLIBDIR"; then + MOZ_OSS_CFLAGS="$MOZ_OSS_CFLAGS -I$OSSLIBDIR/include" + fi + fi + AC_MSG_RESULT([$MOZ_OSS_CFLAGS]) + + CFLAGS="$CFLAGS $MOZ_OSS_CFLAGS" + MOZ_CHECK_HEADERS(sys/soundcard.h soundcard.h) + + if test "$ac_cv_header_sys_soundcard_h" != "yes" -a \ + "$ac_cv_header_soundcard_h" != "yes"; then + AC_MSG_ERROR([Need OSS for Ogg, Wave or WebM decoding on $OS_TARGET. Disable with --without-oss.]) + fi + + dnl Assume NetBSD implementation over SunAudio + AC_CHECK_LIB(ossaudio, _oss_ioctl, + [AC_DEFINE_UNQUOTED(CUBEB_OSS_DEFAULT_OUTPUT, "/dev/sound") + MOZ_OSS_LIBS="$MOZ_OSS_LIBS -lossaudio"]) +fi +CFLAGS=$_SAVE_CFLAGS +LIBS=$_SAVE_LIBS + +AC_SUBST(MOZ_OSS) +AC_SUBST_LIST(MOZ_OSS_CFLAGS) +AC_SUBST_LIST(MOZ_OSS_LIBS) + +dnl ================================== dnl = Check alsa availability on Linux dnl ================================== diff --git dom/media/CubebUtils.cpp dom/media/CubebUtils.cpp index e10e07e06ade..1fd561bea113 100644 --- dom/media/CubebUtils.cpp +++ dom/media/CubebUtils.cpp @@ -71,7 +71,8 @@ const char* AUDIOSTREAM_BACKEND_ID_STR[] = { "sndio", "opensl", "audiotrack", - "kai" + "kai", + "oss", }; /* Index for failures to create an audio stream the first time. */ const int CUBEB_BACKEND_INIT_FAILURE_FIRST = diff --git media/libcubeb/AUTHORS media/libcubeb/AUTHORS index 0fde65baad34..f8663c43b475 100644 --- media/libcubeb/AUTHORS +++ media/libcubeb/AUTHORS @@ -4,6 +4,7 @@ Michael Wu Paul Adenot David Richards Sebastien Alaiwan +Evgeniy Vodolazskiy KO Myung-Hun Haakon Sporsheim Alex Chronopoulos diff --git media/libcubeb/src/cubeb.c media/libcubeb/src/cubeb.c index 57bcb4c13652..68be024f4a68 100644 --- media/libcubeb/src/cubeb.c +++ media/libcubeb/src/cubeb.c @@ -54,6 +54,9 @@ int audiotrack_init(cubeb ** context, char const * context_name); #if defined(USE_KAI) int kai_init(cubeb ** context, char const * context_name); #endif +#if defined(USE_OSS) +int oss_init(cubeb ** context, char const * context_name); +#endif static int validate_stream_params(cubeb_stream_params * input_stream_params, @@ -155,6 +158,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #if defined(USE_KAI) init_oneshot = kai_init; #endif + } else if (!strcmp(backend_name, "oss")) { +#if defined(USE_OSS) + init_oneshot = oss_init; +#endif } else { /* Already set */ } @@ -196,6 +203,9 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam #if defined(USE_KAI) kai_init, #endif +#if defined(USE_OSS) + oss_init, +#endif }; int i; diff --git media/libcubeb/src/cubeb_alsa.c media/libcubeb/src/cubeb_alsa.c index 05ad27fef53b..3ea78e994f59 100644 --- media/libcubeb/src/cubeb_alsa.c +++ media/libcubeb/src/cubeb_alsa.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,52 @@ #define ALSA_PA_PLUGIN "ALSA <-> PulseAudio PCM I/O Plugin" +#ifdef DISABLE_LIBASOUND_DLOPEN +#define WRAP(x) x +#else +#define WRAP(x) cubeb_##x +#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x +MAKE_TYPEDEF(snd_config); +MAKE_TYPEDEF(snd_config_add); +MAKE_TYPEDEF(snd_config_copy); +MAKE_TYPEDEF(snd_config_delete); +MAKE_TYPEDEF(snd_config_get_id); +MAKE_TYPEDEF(snd_config_get_string); +MAKE_TYPEDEF(snd_config_imake_integer); +MAKE_TYPEDEF(snd_config_search); +MAKE_TYPEDEF(snd_config_search_definition); +MAKE_TYPEDEF(snd_lib_error_set_handler); +MAKE_TYPEDEF(snd_pcm_avail_update); +MAKE_TYPEDEF(snd_pcm_close); +MAKE_TYPEDEF(snd_pcm_delay); +MAKE_TYPEDEF(snd_pcm_drain); +MAKE_TYPEDEF(snd_pcm_frames_to_bytes); +MAKE_TYPEDEF(snd_pcm_get_params); +/* snd_pcm_hw_params_alloca is actually a macro */ +/* MAKE_TYPEDEF(snd_pcm_hw_params_alloca); */ +MAKE_TYPEDEF(snd_pcm_hw_params_sizeof); +#define snd_pcm_hw_params_sizeof cubeb_snd_pcm_hw_params_sizeof +MAKE_TYPEDEF(snd_pcm_hw_params_any); +MAKE_TYPEDEF(snd_pcm_hw_params_get_channels_max); +MAKE_TYPEDEF(snd_pcm_hw_params_get_rate); +MAKE_TYPEDEF(snd_pcm_hw_params_set_rate_near); +MAKE_TYPEDEF(snd_pcm_nonblock); +MAKE_TYPEDEF(snd_pcm_open); +MAKE_TYPEDEF(snd_pcm_open_lconf); +MAKE_TYPEDEF(snd_pcm_pause); +MAKE_TYPEDEF(snd_pcm_poll_descriptors); +MAKE_TYPEDEF(snd_pcm_poll_descriptors_count); +MAKE_TYPEDEF(snd_pcm_poll_descriptors_revents); +MAKE_TYPEDEF(snd_pcm_readi); +MAKE_TYPEDEF(snd_pcm_recover); +MAKE_TYPEDEF(snd_pcm_set_params); +MAKE_TYPEDEF(snd_pcm_start); +MAKE_TYPEDEF(snd_pcm_state); +MAKE_TYPEDEF(snd_pcm_writei); + +#undef MAKE_TYPEDEF +#endif + /* ALSA is not thread-safe. snd_pcm_t instances are individually protected by the owning cubeb_stream's mutex. snd_pcm_t creation and destruction is not thread-safe until ALSA 1.0.24 (see alsa-lib.git commit 91c9c8f1), @@ -65,6 +112,8 @@ struct cubeb { workaround is not required. */ snd_config_t * local_config; int is_pa; + + void * libasound; }; enum stream_state { @@ -243,8 +292,8 @@ set_timeout(struct timeval * timeout, unsigned int ms) static void stream_buffer_decrement(cubeb_stream * stm, long count) { - char * bufremains = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, count); - memmove(stm->buffer, bufremains, snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes - 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; } @@ -276,9 +325,9 @@ alsa_process_stream(cubeb_stream * stm) /* 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. */ - snd_pcm_poll_descriptors_revents(stm->pcm, stm->fds, stm->nfds, &revents); + WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents); - avail = snd_pcm_avail_update(stm->pcm); + avail = WRAP(snd_pcm_avail_update)(stm->pcm); /* Got null event? Bail and wait for another wakeup. */ if (avail == 0) { @@ -301,7 +350,7 @@ alsa_process_stream(cubeb_stream * stm) // TODO: should it be marked as DRAINING? } - got = snd_pcm_readi(stm->pcm, stm->buffer+stm->bufframes, avail); + got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail); if (got < 0) { avail = got; // the error handler below will recover us @@ -345,7 +394,7 @@ alsa_process_stream(cubeb_stream * stm) (!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 + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes); + 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) { @@ -372,8 +421,8 @@ alsa_process_stream(cubeb_stream * stm) long drain_frames = avail - stm->bufframes; double drain_time = (double) drain_frames / stm->params.rate; - char * buftail = stm->buffer + snd_pcm_frames_to_bytes(stm->pcm, stm->bufframes); - memset(buftail, 0, snd_pcm_frames_to_bytes(stm->pcm, drain_frames)); + 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 */ @@ -400,7 +449,7 @@ alsa_process_stream(cubeb_stream * stm) } } - wrote = snd_pcm_writei(stm->pcm, stm->buffer, avail); + wrote = WRAP(snd_pcm_writei)(stm->pcm, stm->buffer, avail); if (wrote < 0) { avail = wrote; // the error handler below will recover us } else { @@ -413,13 +462,13 @@ alsa_process_stream(cubeb_stream * stm) /* Got some error? Let's try to recover the stream. */ if (avail < 0) { - avail = snd_pcm_recover(stm->pcm, avail, 0); + avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); /* Capture pcm must be started after initial setup/recover */ if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE && - snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { - avail = snd_pcm_start(stm->pcm); + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + avail = WRAP(snd_pcm_start)(stm->pcm); } } @@ -535,26 +584,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; } @@ -563,7 +612,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; } @@ -572,7 +621,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; @@ -595,22 +644,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; } @@ -619,7 +668,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; } @@ -630,12 +679,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; } @@ -646,18 +695,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; } @@ -665,7 +714,7 @@ init_local_config_with_workaround(char const * pcm_name) return lconf; } while (0); - snd_config_delete(lconf); + WRAP(snd_config_delete)(lconf); return NULL; } @@ -677,9 +726,9 @@ alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t s pthread_mutex_lock(&cubeb_alsa_mutex); if (local_config) { - r = snd_pcm_open_lconf(pcm, 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, pcm_name, stream, SND_PCM_NONBLOCK); + r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); } pthread_mutex_unlock(&cubeb_alsa_mutex); @@ -692,7 +741,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; @@ -755,12 +804,65 @@ alsa_init(cubeb ** context, char const * context_name) pthread_attr_t attr; snd_pcm_t * dummy; + void * libasound = NULL; + +#ifndef DISABLE_LIBASOUND_DLOPEN + libasound = dlopen("libasound.so", RTLD_LAZY); + if (!libasound) { + return CUBEB_ERROR; + } + +#define LOAD(x) do { \ + cubeb_##x = dlsym(libasound, #x); \ + if (!cubeb_##x) { \ + dlclose(libasound); \ + return CUBEB_ERROR; \ + } \ + } while(0) + + LOAD(snd_config); + LOAD(snd_config_add); + LOAD(snd_config_copy); + LOAD(snd_config_delete); + LOAD(snd_config_get_id); + LOAD(snd_config_get_string); + LOAD(snd_config_imake_integer); + LOAD(snd_config_search); + LOAD(snd_config_search_definition); + LOAD(snd_lib_error_set_handler); + LOAD(snd_pcm_avail_update); + LOAD(snd_pcm_close); + LOAD(snd_pcm_delay); + LOAD(snd_pcm_drain); + LOAD(snd_pcm_frames_to_bytes); + LOAD(snd_pcm_get_params); + /* snd_pcm_hw_params_alloca is actually a macro */ + /* LOAD(snd_pcm_hw_params_alloca); */ + LOAD(snd_pcm_hw_params_sizeof); + LOAD(snd_pcm_hw_params_any); + LOAD(snd_pcm_hw_params_get_channels_max); + LOAD(snd_pcm_hw_params_get_rate); + LOAD(snd_pcm_hw_params_set_rate_near); + LOAD(snd_pcm_nonblock); + LOAD(snd_pcm_open); + LOAD(snd_pcm_open_lconf); + LOAD(snd_pcm_pause); + LOAD(snd_pcm_poll_descriptors); + LOAD(snd_pcm_poll_descriptors_count); + LOAD(snd_pcm_poll_descriptors_revents); + LOAD(snd_pcm_recover); + LOAD(snd_pcm_set_params); + LOAD(snd_pcm_state); + LOAD(snd_pcm_writei); + +#undef LOAD +#endif assert(context); *context = NULL; 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); @@ -768,6 +870,8 @@ alsa_init(cubeb ** context, char const * context_name) ctx = calloc(1, sizeof(*ctx)); assert(ctx); + ctx->libasound = libasound; + ctx->ops = &alsa_ops; r = pthread_mutex_init(&ctx->mutex, NULL); @@ -817,7 +921,7 @@ alsa_init(cubeb ** context, char const * context_name) 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) { @@ -857,9 +961,13 @@ alsa_destroy(cubeb * ctx) pthread_mutex_destroy(&ctx->mutex); free(ctx->fds); + if (ctx->libasound) { + dlclose(ctx->libasound); + } + 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); } @@ -939,7 +1047,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream 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; @@ -952,7 +1060,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream 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) { @@ -960,20 +1068,20 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream 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); /* 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, snd_pcm_frames_to_bytes(stm->pcm, stm->buffer_size)); + stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); assert(stm->buffer); - stm->nfds = snd_pcm_poll_descriptors_count(stm->pcm); + 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); if (alsa_register_stream(ctx, stm) != 0) { @@ -1048,7 +1156,7 @@ alsa_stream_destroy(cubeb_stream * stm) 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; @@ -1094,12 +1202,12 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) assert(stm); - r = snd_pcm_hw_params_any(stm->pcm, hw_params); + 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; } @@ -1120,34 +1228,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; } @@ -1180,10 +1288,10 @@ alsa_stream_start(cubeb_stream * stm) pthread_mutex_lock(&stm->mutex); /* Capture pcm must be started after initial setup/recover */ if (stm->stream_type == SND_PCM_STREAM_CAPTURE && - snd_pcm_state(stm->pcm) == SND_PCM_STATE_PREPARED) { - snd_pcm_start(stm->pcm); + WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { + WRAP(snd_pcm_start)(stm->pcm); } - snd_pcm_pause(stm->pcm, 0); + WRAP(snd_pcm_pause)(stm->pcm, 0); gettimeofday(&stm->last_activity, NULL); pthread_mutex_unlock(&stm->mutex); @@ -1223,7 +1331,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; @@ -1239,8 +1347,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; @@ -1265,7 +1373,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; } diff --git media/libcubeb/src/cubeb_oss.c media/libcubeb/src/cubeb_oss.c new file mode 100644 index 000000000000..7b0b4f37dbe4 --- /dev/null +++ media/libcubeb/src/cubeb_oss.c @@ -0,0 +1,445 @@ +/* + * Copyright © 2014 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#if defined(HAVE_SYS_SOUNDCARD_H) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" + +#ifndef CUBEB_OSS_DEFAULT_OUTPUT +#define CUBEB_OSS_DEFAULT_OUTPUT "/dev/dsp" +#endif + +#define OSS_BUFFER_SIZE 1024 + +struct cubeb { + struct cubeb_ops const * ops; +}; + +struct cubeb_stream { + cubeb * context; + + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + float volume; + float panning; + + pthread_mutex_t state_mutex; + pthread_cond_t state_cond; + + int running; + int stopped; + int floating; + + /* These two vars are needed to support old versions of OSS */ + unsigned int position_bytes; + unsigned int last_position_bytes; + + uint64_t written_frags; /* The number of fragments written to /dev/dsp */ + uint64_t missed_frags; /* fragments output with stopped stream */ + + cubeb_stream_params params; + int fd; + pthread_t th; +}; + +static struct cubeb_ops const oss_ops; + +int oss_init(cubeb ** context, char const * context_name) +{ + cubeb* ctx = (cubeb*)malloc(sizeof(cubeb)); + ctx->ops = &oss_ops; + *context = ctx; + return CUBEB_OK; +} + +static void oss_destroy(cubeb *ctx) +{ + free(ctx); +} + +static char const * oss_get_backend_id(cubeb * context) +{ + static char oss_name[] = "oss"; + return oss_name; +} + +static int oss_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + *max_channels = 2; /* Let's support only stereo for now */ + return CUBEB_OK; +} + +static int oss_get_min_latency(cubeb * context, cubeb_stream_params params, + uint32_t * latency_frames) +{ + (void)context; + /* 40ms is a big enough number to work ok */ + *latency_frames = 40 * params.rate / 1000; + return CUBEB_OK; +} + +static int oss_get_preferred_sample_rate(cubeb *context, uint32_t * rate) +{ + /* 48000 seems a prefered choice for most audio devices + * and a good choice for OSS */ + *rate = 48000; + return CUBEB_OK; +} + +static void run_state_callback(cubeb_stream *stream, cubeb_state state) +{ + if (stream->state_callback) { + stream->state_callback(stream, stream->user_ptr, state); + } +} + +static long run_data_callback(cubeb_stream *stream, void *buffer, long nframes) +{ + long got = 0; + pthread_mutex_lock(&stream->state_mutex); + if (stream->data_callback && stream->running && !stream->stopped) { + pthread_mutex_unlock(&stream->state_mutex); + got = stream->data_callback(stream, stream->user_ptr, NULL, buffer, nframes); + } else { + pthread_mutex_unlock(&stream->state_mutex); + } + return got; +} + +static void apply_volume_int(int16_t* buffer, unsigned int n, + float volume, float panning) +{ + float left = volume; + float right = volume; + unsigned int i; + int pan[2]; + if (panning<0) { + right *= (1+panning); + } else { + left *= (1-panning); + } + pan[0] = 128.0*left; + pan[1] = 128.0*right; + for(i=0; irunning) { + pthread_mutex_lock(&stream->state_mutex); + if (stream->stopped) { + pthread_mutex_unlock(&stream->state_mutex); + run_state_callback(stream, CUBEB_STATE_STOPPED); + pthread_mutex_lock(&stream->state_mutex); + while (stream->stopped) { + pthread_cond_wait(&stream->state_cond, &stream->state_mutex); + } + pthread_mutex_unlock(&stream->state_mutex); + run_state_callback(stream, CUBEB_STATE_STARTED); + continue; + } + pthread_mutex_unlock(&stream->state_mutex); + if (stream->floating) { + got = run_data_callback(stream, f_buffer, + OSS_BUFFER_SIZE/stream->params.channels); + apply_volume_float(f_buffer, got*stream->params.channels, + stream->volume, stream->panning); + for (i=0; i<((unsigned long)got)*stream->params.channels; i++) { + /* Clipping is prefered to overflow */ + if(f_buffer[i]>=1.0){ + f_buffer[i]=1.0; + } + if(f_buffer[i]<=-1.0){ + f_buffer[i]=-1.0; + } + /* One might think that multipling by 32767.0 is logical but results in clipping */ + buffer[i] = f_buffer[i]*32767.0; + } + } else { + got = run_data_callback(stream, buffer, + OSS_BUFFER_SIZE/stream->params.channels); + apply_volume_int(buffer, got*stream->params.channels, + stream->volume, stream->panning); + } + if (got<0) { + run_state_callback(stream, CUBEB_STATE_ERROR); + break; + } + if (!got) { + run_state_callback(stream, CUBEB_STATE_DRAINED); + } + if (got) { + size_t i = 0; + size_t s = got*stream->params.channels*sizeof(int16_t); + while (i < s) { + ssize_t n = write(stream->fd, ((char*)buffer) + i, s - i); + if (n<=0) { + run_state_callback(stream, CUBEB_STATE_ERROR); + break; + } + i+=n; + } + stream->written_frags+=got; + } + } + return NULL; +} + +static void oss_try_set_latency(cubeb_stream* stream, unsigned int latency) +{ + unsigned int latency_bytes, n_frag; + int frag; + /* fragment size of 1024 is a good choice with good chances to be accepted */ + unsigned int frag_log=10; /* 2^frag_log = fragment size */ + latency_bytes = + latency*stream->params.rate*stream->params.channels*sizeof(uint16_t)/1000; + n_frag = latency_bytes>>frag_log; + frag = (n_frag<<16) | frag_log; + /* Even if this fails we wish to continue, not checking for errors */ + ioctl(stream->fd, SNDCTL_DSP_SETFRAGMENT, &frag); +} + +static int oss_stream_init(cubeb * context, cubeb_stream ** stm, + 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, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, void * user_ptr) +{ + cubeb_stream* stream = (cubeb_stream*)malloc(sizeof(cubeb_stream)); + stream->context = context; + stream->data_callback = data_callback; + stream->state_callback = state_callback; + stream->user_ptr = user_ptr; + + assert(!input_stream_params && "not supported."); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + if ((stream->fd = open(CUBEB_OSS_DEFAULT_OUTPUT, O_WRONLY)) == -1) { + free(stream); + return CUBEB_ERROR; + } +#define SET(what, to) do { unsigned int i = to; \ + int j = ioctl(stream->fd, what, &i); \ + if (j == -1 || i != to) { \ + close(stream->fd); \ + free(stream); \ + return CUBEB_ERROR_INVALID_FORMAT; } } while (0) + + stream->params = *output_stream_params; + stream->volume = 1.0; + stream->panning = 0.0; + + oss_try_set_latency(stream, latency); + + stream->floating = 0; + SET(SNDCTL_DSP_CHANNELS, stream->params.channels); + SET(SNDCTL_DSP_SPEED, stream->params.rate); + switch (stream->params.format) { + case CUBEB_SAMPLE_S16LE: + SET(SNDCTL_DSP_SETFMT, AFMT_S16_LE); + break; + case CUBEB_SAMPLE_S16BE: + SET(SNDCTL_DSP_SETFMT, AFMT_S16_BE); + break; + case CUBEB_SAMPLE_FLOAT32LE: + SET(SNDCTL_DSP_SETFMT, AFMT_S16_NE); + stream->floating = 1; + break; + default: + close(stream->fd); + free(stream); + return CUBEB_ERROR; + } + + + pthread_mutex_init(&stream->state_mutex, NULL); + pthread_cond_init(&stream->state_cond, NULL); + + stream->running = 1; + stream->stopped = 1; + stream->position_bytes = 0; + stream->last_position_bytes = 0; + stream->written_frags = 0; + stream->missed_frags = 0; + + pthread_create(&stream->th, NULL, writer, (void*)stream); + + *stm = stream; + + return CUBEB_OK; +} + +static void oss_stream_destroy(cubeb_stream * stream) +{ + pthread_mutex_lock(&stream->state_mutex); + + stream->running = 0; + stream->stopped = 0; + pthread_cond_signal(&stream->state_cond); + + pthread_mutex_unlock(&stream->state_mutex); + + pthread_join(stream->th, NULL); + + pthread_mutex_destroy(&stream->state_mutex); + pthread_cond_destroy(&stream->state_cond); + close(stream->fd); + free(stream); +} + +static int oss_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +{ + if (ioctl(stream->fd, SNDCTL_DSP_GETODELAY, latency)==-1) { + return CUBEB_ERROR; + } + /* Convert latency from bytes to frames */ + *latency /= stream->params.channels*sizeof(int16_t); + return CUBEB_OK; +} + + +static int oss_stream_current_optr(cubeb_stream * stream, uint64_t * position) +{ + count_info ci; + /* Unfortunately, this ioctl is only available in OSS 4.x */ +#ifdef SNDCTL_DSP_CURRENT_OPTR + oss_count_t count; + if (ioctl(stream->fd, SNDCTL_DSP_CURRENT_OPTR, &count) != -1) { + *position = count.samples;// + count.fifo_samples; + return CUBEB_OK; + } +#endif + /* Fall back to this ioctl in case the previous one fails */ + if (ioctl(stream->fd, SNDCTL_DSP_GETOPTR, &ci) == -1) { + return CUBEB_ERROR; + } + /* ci.bytes is only 32 bit and will start to wrap after arithmetic overflow */ + stream->position_bytes += ci.bytes - stream->last_position_bytes; + stream->last_position_bytes = ci.bytes; + *position = stream->position_bytes/stream->params.channels/sizeof(int16_t); + return CUBEB_OK; +} + +static int oss_stream_get_position(cubeb_stream * stream, uint64_t * position) +{ + if ( oss_stream_current_optr(stream, position) == CUBEB_OK ){ + *position -= stream->missed_frags; + return CUBEB_OK; + } + /* If no correct method to get position works we resort to this */ + *position = stream->written_frags; + return CUBEB_OK; +} + + +static int oss_stream_start(cubeb_stream * stream) +{ + pthread_mutex_lock(&stream->state_mutex); + if (stream->stopped) { + uint64_t ptr; + oss_stream_current_optr(stream, &ptr); + stream->missed_frags = ptr - stream->written_frags; + stream->stopped = 0; + pthread_cond_signal(&stream->state_cond); + } + pthread_mutex_unlock(&stream->state_mutex); + return CUBEB_OK; +} + +static int oss_stream_stop(cubeb_stream * stream) +{ + pthread_mutex_lock(&stream->state_mutex); + stream->stopped = 1; + pthread_mutex_unlock(&stream->state_mutex); + return CUBEB_OK; +} + +int oss_stream_set_panning(cubeb_stream * stream, float panning) +{ + if (stream->params.channels == 2) { + stream->panning=panning; + } + return CUBEB_OK; +} + +int oss_stream_set_volume(cubeb_stream * stream, float volume) +{ + stream->volume=volume; + return CUBEB_OK; +} + +static struct cubeb_ops const oss_ops = { + .init = oss_init, + .get_backend_id = oss_get_backend_id, + .get_max_channel_count = oss_get_max_channel_count, + .get_min_latency = oss_get_min_latency, + .get_preferred_sample_rate = oss_get_preferred_sample_rate, + .get_preferred_channel_layout = NULL, + .destroy = oss_destroy, + .enumerate_devices = NULL, + .stream_init = oss_stream_init, + .stream_destroy = oss_stream_destroy, + .stream_start = oss_stream_start, + .stream_stop = oss_stream_stop, + .stream_get_position = oss_stream_get_position, + .stream_get_latency = oss_stream_get_latency, + .stream_set_volume = oss_stream_set_volume, + .stream_set_panning = oss_stream_set_panning, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL +}; diff --git media/libcubeb/src/moz.build media/libcubeb/src/moz.build index fc9c79198e30..57d039d5a0f2 100644 --- media/libcubeb/src/moz.build +++ media/libcubeb/src/moz.build @@ -20,6 +20,12 @@ if CONFIG['MOZ_ALSA']: ] DEFINES['USE_ALSA'] = True +if CONFIG['MOZ_OSS']: + SOURCES += [ + 'cubeb_oss.c', + ] + DEFINES['USE_OSS'] = True + if CONFIG['MOZ_PULSEAUDIO'] or CONFIG['MOZ_JACK']: SOURCES += [ 'cubeb_resampler.cpp', @@ -92,6 +98,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': '%' + '%s/system/media/wilhelm/include' % CONFIG['ANDROID_SOURCE'], ] +CFLAGS += CONFIG['MOZ_OSS_CFLAGS'] CFLAGS += CONFIG['MOZ_ALSA_CFLAGS'] CFLAGS += CONFIG['MOZ_PULSEAUDIO_CFLAGS'] diff --git media/libcubeb/gtest/moz.build media/libcubeb/gtest/moz.build index e99968648554..efbbbf779e11 100644 --- media/libcubeb/gtest/moz.build +++ media/libcubeb/gtest/moz.build @@ -71,7 +71,6 @@ elif CONFIG['OS_TARGET'] == 'OpenBSD': 'sndio', ] else: - OS_LIBS += CONFIG['MOZ_ALSA_LIBS'] OS_LIBS += CONFIG['MOZ_PULSEAUDIO_LIBS'] if CONFIG['GNU_CXX']: diff --git media/libcubeb/update.sh media/libcubeb/update.sh index b139b8f497fd..d1f8a223c6d8 100755 --- media/libcubeb/update.sh +++ media/libcubeb/update.sh @@ -20,6 +20,7 @@ cp $1/src/cubeb_log.h src cp $1/src/cubeb_mixer.cpp src cp $1/src/cubeb_mixer.h src cp $1/src/cubeb_opensl.c src +cp $1/src/cubeb_oss.c src cp $1/src/cubeb_osx_run_loop.h src cp $1/src/cubeb_panner.cpp src cp $1/src/cubeb_panner.h src diff --git toolkit/library/moz.build toolkit/library/moz.build index a61c689c83c3..7764df6f8a6b 100644 --- toolkit/library/moz.build +++ toolkit/library/moz.build @@ -242,8 +242,8 @@ if CONFIG['MOZ_SYSTEM_LIBVPX']: if not CONFIG['MOZ_TREE_PIXMAN']: OS_LIBS += CONFIG['MOZ_PIXMAN_LIBS'] -if CONFIG['MOZ_ALSA']: - OS_LIBS += CONFIG['MOZ_ALSA_LIBS'] +if CONFIG['MOZ_OSS']: + OS_LIBS += CONFIG['MOZ_OSS_LIBS'] if CONFIG['HAVE_CLOCK_MONOTONIC']: OS_LIBS += CONFIG['REALTIME_LIBS']