From 55f4e4a5ec657a017e3bf75299ad71fd1c968dd3 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- audio/alsaaudio.c | 215 +++++++++++---- audio/audio.c | 349 +++++++++++++++++++------ audio/audio.h | 11 + audio/audio_int.h | 8 +- audio/audio_pt_int.c | 149 +++++++++++ audio/audio_pt_int.h | 22 ++ audio/audio_template.h | 15 +- audio/coreaudio.c | 696 ++++++++++++++++++++++++++++++++++--------------- audio/dsoundaudio.c | 2 +- audio/esdaudio.c | 692 ++++++++++++++++++++++++++++++++++++++++++++++++ audio/noaudio.c | 2 +- audio/ossaudio.c | 2 +- audio/sdlaudio.c | 241 +++++++++++++++-- audio/wavaudio.c | 265 +++++++++++++++++-- audio/wavcapture.c | 6 +- audio/winaudio.c | 668 +++++++++++++++++++++++++++++++++++++++++++++++ 16 files changed, 2958 insertions(+), 385 deletions(-) create mode 100644 audio/audio_pt_int.c create mode 100644 audio/audio_pt_int.h create mode 100644 audio/esdaudio.c create mode 100644 audio/winaudio.c (limited to 'audio') diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c index 71e5235..d18705b 100644 --- a/audio/alsaaudio.c +++ b/audio/alsaaudio.c @@ -1,6 +1,7 @@ /* * QEMU ALSA audio driver * + * Copyright (c) 2008 The Android Open Source Project * Copyright (c) 2005 Vassili Karpov (malc) * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,6 +27,77 @@ #define AUDIO_CAP "alsa" #include "audio_int.h" +#include +#include +#include "android_debug.h" + +#define DEBUG 1 + +#if DEBUG +# include +# define D(...) VERBOSE_PRINT(audio,__VA_ARGS__) +# define D_ACTIVE VERBOSE_CHECK(audio) +# define O(...) VERBOSE_PRINT(audioout,__VA_ARGS__) +# define I(...) VERBOSE_PRINT(audioin,__VA_ARGS__) +#else +# define D(...) ((void)0) +# define D_ACTIVE 0 +# define O(...) ((void)0) +# define I(...) ((void)0) +#endif + + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define DYN_SYMBOLS \ + DYN_FUNCTION(size_t,snd_pcm_sw_params_sizeof,(void)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_current,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)) \ + DYN_FUNCTION(int,snd_pcm_sw_params_set_start_threshold,(snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val)) \ + DYN_FUNCTION(int,snd_pcm_sw_params,(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)) \ + DYN_FUNCTION(int,snd_pcm_sw_params_current,(snd_pcm_t *pcm, snd_pcm_sw_params_t *params)) \ + DYN_FUNCTION(size_t,snd_pcm_hw_params_sizeof,(void)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_any,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_access,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t _access)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_format,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_rate_near,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_channels_near,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_buffer_time_near,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)) \ + DYN_FUNCTION(int,snd_pcm_hw_params,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_get_buffer_size,(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)) \ + DYN_FUNCTION(int,snd_pcm_prepare,(snd_pcm_t *pcm)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_get_period_size,(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_get_period_size_min,(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *frames, int *dir)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_period_size,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val, int dir)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_get_buffer_size_min,(const snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_buffer_size,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t val)) \ + DYN_FUNCTION(int,snd_pcm_hw_params_set_period_time_near,(snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir)) \ + DYN_FUNCTION(snd_pcm_sframes_t,snd_pcm_avail_update,(snd_pcm_t *pcm)) \ + DYN_FUNCTION(int,snd_pcm_drop,(snd_pcm_t *pcm)) \ + DYN_FUNCTION(snd_pcm_sframes_t,snd_pcm_writei,(snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size)) \ + DYN_FUNCTION(snd_pcm_sframes_t,snd_pcm_readi,(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size)) \ + DYN_FUNCTION(snd_pcm_state_t,snd_pcm_state,(snd_pcm_t *pcm)) \ + DYN_FUNCTION(const char*,snd_strerror,(int errnum)) \ + DYN_FUNCTION(int,snd_pcm_open,(snd_pcm_t **pcm, const char *name,snd_pcm_stream_t stream, int mode)) \ + DYN_FUNCTION(int,snd_pcm_close,(snd_pcm_t *pcm)) \ + + + +/* define pointers to library functions we're going to use */ +#define DYN_FUNCTION(ret,name,sig) \ + static ret (*func_ ## name)sig; + +DYN_SYMBOLS + +#undef DYN_FUNCTION + +#define func_snd_pcm_hw_params_alloca(ptr) \ + do { assert(ptr); *ptr = (snd_pcm_hw_params_t *) alloca(func_snd_pcm_hw_params_sizeof()); memset(*ptr, 0, func_snd_pcm_hw_params_sizeof()); } while (0) + +#define func_snd_pcm_sw_params_alloca(ptr) \ + do { assert(ptr); *ptr = (snd_pcm_sw_params_t *) alloca(func_snd_pcm_sw_params_sizeof()); memset(*ptr, 0, func_snd_pcm_sw_params_sizeof()); } while (0) + +static void* alsa_lib; typedef struct ALSAVoiceOut { HWVoiceOut hw; @@ -107,7 +179,7 @@ static void GCC_FMT_ATTR (2, 3) alsa_logerr (int err, const char *fmt, ...) AUD_vlog (AUDIO_CAP, fmt, ap); va_end (ap); - AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); + AUD_log (AUDIO_CAP, "Reason: %s\n", func_snd_strerror (err)); } static void GCC_FMT_ATTR (3, 4) alsa_logerr2 ( @@ -125,12 +197,12 @@ static void GCC_FMT_ATTR (3, 4) alsa_logerr2 ( AUD_vlog (AUDIO_CAP, fmt, ap); va_end (ap); - AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); + AUD_log (AUDIO_CAP, "Reason: %s\n", func_snd_strerror (err)); } static void alsa_anal_close (snd_pcm_t **handlep) { - int err = snd_pcm_close (*handlep); + int err = func_snd_pcm_close (*handlep); if (err) { alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); } @@ -228,16 +300,16 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) int err; snd_pcm_sw_params_t *sw_params; - snd_pcm_sw_params_alloca (&sw_params); + func_snd_pcm_sw_params_alloca (&sw_params); - err = snd_pcm_sw_params_current (handle, sw_params); + err = func_snd_pcm_sw_params_current (handle, sw_params); if (err < 0) { dolog ("Could not fully initialize DAC\n"); alsa_logerr (err, "Failed to get current software parameters\n"); return; } - err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); + err = func_snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); if (err < 0) { dolog ("Could not fully initialize DAC\n"); alsa_logerr (err, "Failed to set software threshold to %ld\n", @@ -245,7 +317,7 @@ static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) return; } - err = snd_pcm_sw_params (handle, sw_params); + err = func_snd_pcm_sw_params (handle, sw_params); if (err < 0) { dolog ("Could not fully initialize DAC\n"); alsa_logerr (err, "Failed to set software parameters\n"); @@ -269,9 +341,9 @@ static int alsa_open (int in, struct alsa_params_req *req, buffer_size = req->buffer_size; nchannels = req->nchannels; - snd_pcm_hw_params_alloca (&hw_params); + func_snd_pcm_hw_params_alloca (&hw_params); - err = snd_pcm_open ( + err = func_snd_pcm_open ( &handle, pcm_name, in ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, @@ -282,13 +354,13 @@ static int alsa_open (int in, struct alsa_params_req *req, return -1; } - err = snd_pcm_hw_params_any (handle, hw_params); + err = func_snd_pcm_hw_params_any (handle, hw_params); if (err < 0) { alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); goto err; } - err = snd_pcm_hw_params_set_access ( + err = func_snd_pcm_hw_params_set_access ( handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED @@ -298,22 +370,22 @@ static int alsa_open (int in, struct alsa_params_req *req, goto err; } - err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); + err = func_snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); if (err < 0) { alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); goto err; } - err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); + err = func_snd_pcm_hw_params_set_rate_near (handle, hw_params, (unsigned*)&freq, 0); if (err < 0) { alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); goto err; } - err = snd_pcm_hw_params_set_channels_near ( + err = func_snd_pcm_hw_params_set_channels_near ( handle, hw_params, - &nchannels + (unsigned*)&nchannels ); if (err < 0) { alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", @@ -338,7 +410,7 @@ static int alsa_open (int in, struct alsa_params_req *req, if (buffer_size) { if ((in && conf.size_in_usec_in) || (!in && conf.size_in_usec_out)) { if (period_size) { - err = snd_pcm_hw_params_set_period_time_near ( + err = func_snd_pcm_hw_params_set_period_time_near ( handle, hw_params, &period_size, @@ -352,7 +424,7 @@ static int alsa_open (int in, struct alsa_params_req *req, } } - err = snd_pcm_hw_params_set_buffer_time_near ( + err = func_snd_pcm_hw_params_set_buffer_time_near ( handle, hw_params, &buffer_size, @@ -374,7 +446,7 @@ static int alsa_open (int in, struct alsa_params_req *req, minval = period_size; dir = 0; - err = snd_pcm_hw_params_get_period_size_min ( + err = func_snd_pcm_hw_params_get_period_size_min ( hw_params, &minval, &dir @@ -400,7 +472,7 @@ static int alsa_open (int in, struct alsa_params_req *req, } } - err = snd_pcm_hw_params_set_period_size ( + err = func_snd_pcm_hw_params_set_period_size ( handle, hw_params, period_size, @@ -414,7 +486,7 @@ static int alsa_open (int in, struct alsa_params_req *req, } minval = buffer_size; - err = snd_pcm_hw_params_get_buffer_size_min ( + err = func_snd_pcm_hw_params_get_buffer_size_min ( hw_params, &minval ); @@ -438,7 +510,7 @@ static int alsa_open (int in, struct alsa_params_req *req, } } - err = snd_pcm_hw_params_set_buffer_size ( + err = func_snd_pcm_hw_params_set_buffer_size ( handle, hw_params, buffer_size @@ -454,19 +526,19 @@ static int alsa_open (int in, struct alsa_params_req *req, dolog ("warning: Buffer size is not set\n"); } - err = snd_pcm_hw_params (handle, hw_params); + err = func_snd_pcm_hw_params (handle, hw_params); if (err < 0) { alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); goto err; } - err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); + err = func_snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); if (err < 0) { alsa_logerr2 (err, typ, "Failed to get buffer size\n"); goto err; } - err = snd_pcm_prepare (handle); + err = func_snd_pcm_prepare (handle); if (err < 0) { alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle); goto err; @@ -511,7 +583,7 @@ static int alsa_open (int in, struct alsa_params_req *req, static int alsa_recover (snd_pcm_t *handle) { - int err = snd_pcm_prepare (handle); + int err = func_snd_pcm_prepare (handle); if (err < 0) { alsa_logerr (err, "Failed to prepare handle %p\n", handle); return -1; @@ -523,11 +595,11 @@ static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle) { snd_pcm_sframes_t avail; - avail = snd_pcm_avail_update (handle); + avail = func_snd_pcm_avail_update (handle); if (avail < 0) { if (avail == -EPIPE) { if (!alsa_recover (handle)) { - avail = snd_pcm_avail_update (handle); + avail = func_snd_pcm_avail_update (handle); } } @@ -575,7 +647,7 @@ static int alsa_run_out (HWVoiceOut *hw) hw->clip (dst, src, len); while (len) { - written = snd_pcm_writei (alsa->handle, dst, len); + written = func_snd_pcm_writei (alsa->handle, dst, len); if (written <= 0) { switch (written) { @@ -639,10 +711,14 @@ static int alsa_init_out (HWVoiceOut *hw, audsettings_t *as) struct alsa_params_obt obt; audfmt_e effective_fmt; int endianness; - int err; + int err, result = -1; snd_pcm_t *handle; audsettings_t obt_as; + /* shut alsa debug spew */ + if (!D_ACTIVE) + stdio_disable(); + req.fmt = aud_to_alsafmt (as->fmt); req.freq = as->freq; req.nchannels = as->nchannels; @@ -650,13 +726,13 @@ static int alsa_init_out (HWVoiceOut *hw, audsettings_t *as) req.buffer_size = conf.buffer_size_out; if (alsa_open (0, &req, &obt, &handle)) { - return -1; + goto Exit; } err = alsa_to_audfmt (obt.fmt, &effective_fmt, &endianness); if (err) { alsa_anal_close (&handle); - return -1; + goto Exit; } obt_as.freq = obt.freq; @@ -672,11 +748,17 @@ static int alsa_init_out (HWVoiceOut *hw, audsettings_t *as) dolog ("Could not allocate DAC buffer (%d samples, each %d bytes)\n", hw->samples, 1 << hw->info.shift); alsa_anal_close (&handle); - return -1; + goto Exit; } alsa->handle = handle; - return 0; + result = 0; /* success */ + +Exit: + if (!D_ACTIVE) + stdio_enable(); + + return result; } static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int pause) @@ -684,14 +766,14 @@ static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int pause) int err; if (pause) { - err = snd_pcm_drop (handle); + err = func_snd_pcm_drop (handle); if (err < 0) { alsa_logerr (err, "Could not stop %s\n", typ); return -1; } } else { - err = snd_pcm_prepare (handle); + err = func_snd_pcm_prepare (handle); if (err < 0) { alsa_logerr (err, "Could not prepare handle for %s\n", typ); return -1; @@ -724,11 +806,15 @@ static int alsa_init_in (HWVoiceIn *hw, audsettings_t *as) struct alsa_params_req req; struct alsa_params_obt obt; int endianness; - int err; + int err, result = -1; audfmt_e effective_fmt; snd_pcm_t *handle; audsettings_t obt_as; + /* shut alsa debug spew */ + if (!D_ACTIVE) + stdio_disable(); + req.fmt = aud_to_alsafmt (as->fmt); req.freq = as->freq; req.nchannels = as->nchannels; @@ -736,13 +822,13 @@ static int alsa_init_in (HWVoiceIn *hw, audsettings_t *as) req.buffer_size = conf.buffer_size_in; if (alsa_open (1, &req, &obt, &handle)) { - return -1; + goto Exit; } err = alsa_to_audfmt (obt.fmt, &effective_fmt, &endianness); if (err) { alsa_anal_close (&handle); - return -1; + goto Exit; } obt_as.freq = obt.freq; @@ -758,11 +844,17 @@ static int alsa_init_in (HWVoiceIn *hw, audsettings_t *as) dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", hw->samples, 1 << hw->info.shift); alsa_anal_close (&handle); - return -1; + goto Exit; } alsa->handle = handle; - return 0; + result = 0; /* success */ + +Exit: + if (!D_ACTIVE) + stdio_enable(); + + return result; } static void alsa_fini_in (HWVoiceIn *hw) @@ -805,7 +897,7 @@ static int alsa_run_in (HWVoiceIn *hw) return 0; } - if (!avail && (snd_pcm_state (alsa->handle) == SND_PCM_STATE_PREPARED)) { + if (!avail && (func_snd_pcm_state (alsa->handle) == SND_PCM_STATE_PREPARED)) { avail = hw->samples; } @@ -834,7 +926,7 @@ static int alsa_run_in (HWVoiceIn *hw) dst = hw->conv_buf + bufs[i].add; while (len) { - nread = snd_pcm_readi (alsa->handle, src, len); + nread = func_snd_pcm_readi (alsa->handle, src, len); if (nread <= 0) { switch (nread) { @@ -907,11 +999,46 @@ static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) static void *alsa_audio_init (void) { - return &conf; + void* result = NULL; + + alsa_lib = dlopen( "libasound.so", RTLD_NOW ); + if (alsa_lib == NULL) + alsa_lib = dlopen( "libasound.so.2", RTLD_NOW ); + + if (alsa_lib == NULL) { + ldebug("could not find libasound on this system\n"); + goto Exit; + } + +#undef DYN_FUNCTION +#define DYN_FUNCTION(ret,name,sig) \ + do { \ + (func_ ##name) = dlsym( alsa_lib, STRINGIFY(name) ); \ + if ((func_##name) == NULL) { \ + ldebug("could not find %s in libasound\n", STRINGIFY(name)); \ + goto Fail; \ + } \ + } while (0); + + DYN_SYMBOLS + + result = &conf; + goto Exit; + +Fail: + ldebug("%s: failed to open library\n", __FUNCTION__); + dlclose(alsa_lib); + +Exit: + return result; } static void alsa_audio_fini (void *opaque) { + if (alsa_lib != NULL) { + dlclose(alsa_lib); + alsa_lib = NULL; + } (void) opaque; } @@ -961,7 +1088,7 @@ static struct audio_pcm_ops alsa_pcm_ops = { struct audio_driver alsa_audio_driver = { INIT_FIELD (name = ) "alsa", - INIT_FIELD (descr = ) "ALSA http://www.alsa-project.org", + INIT_FIELD (descr = ) "ALSA audio (www.alsa-project.org)", INIT_FIELD (options = ) alsa_options, INIT_FIELD (init = ) alsa_audio_init, INIT_FIELD (fini = ) alsa_audio_fini, diff --git a/audio/audio.c b/audio/audio.c index 8e7af1a..4ba1f7d 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -1,6 +1,7 @@ /* * QEMU Audio subsystem * + * Copyright (c) 2007-2008 The Android Open Source Project * Copyright (c) 2003-2005 Vassili Karpov (malc) * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -25,6 +26,8 @@ #define AUDIO_CAP "audio" #include "audio_int.h" +#include "android_utils.h" +#include "android.h" /* #define DEBUG_PLIVE */ /* #define DEBUG_LIVE */ @@ -34,8 +37,8 @@ #define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" static struct audio_driver *drvtab[] = { -#ifdef CONFIG_OSS - &oss_audio_driver, +#ifdef CONFIG_ESD + &esd_audio_driver, #endif #ifdef CONFIG_ALSA &alsa_audio_driver, @@ -49,13 +52,91 @@ static struct audio_driver *drvtab[] = { #ifdef CONFIG_FMOD &fmod_audio_driver, #endif +#ifdef CONFIG_WINAUDIO + &win_audio_driver, +#endif #ifdef CONFIG_SDL &sdl_audio_driver, #endif +#ifdef CONFIG_OSS + &oss_audio_driver, +#endif &no_audio_driver, +#if 0 /* disabled WAV audio for now - until we find a user-friendly way to use it */ &wav_audio_driver +#endif }; + +int +audio_get_backend_count( int is_input ) +{ + int nn, count = 0; + + for (nn = 0; nn < sizeof(drvtab)/sizeof(drvtab[0]); nn++) + { + if (is_input) { + if ( drvtab[nn]->max_voices_in > 0 ) + count += 1; + } else { + if ( drvtab[nn]->max_voices_out > 0 ) + count += 1; + } + } + return count; +} + +const char* +audio_get_backend_name( int is_input, int index, const char* *pinfo ) +{ + int nn; + + index += 1; + for (nn = 0; nn < sizeof(drvtab)/sizeof(drvtab[0]); nn++) + { + if (is_input) { + if ( drvtab[nn]->max_voices_in > 0 ) { + if ( --index == 0 ) { + *pinfo = drvtab[nn]->descr; + return drvtab[nn]->name; + } + } + } else { + if ( drvtab[nn]->max_voices_out > 0 ) { + if ( --index == 0 ) { + *pinfo = drvtab[nn]->descr; + return drvtab[nn]->name; + } + } + } + } + *pinfo = NULL; + return NULL; +} + + +int +audio_check_backend_name( int is_input, const char* name ) +{ + int nn; + + for (nn = 0; nn < sizeof(drvtab)/sizeof(drvtab[0]); nn++) + { + if ( !strcmp(drvtab[nn]->name, name) ) { + if (is_input) { + if (drvtab[nn]->max_voices_in > 0) + return 1; + } else { + if (drvtab[nn]->max_voices_out > 0) + return 1; + } + break; + } + } + return 0; +} + + struct fixed_settings { int enabled; int nb_voices; @@ -100,7 +181,7 @@ static struct { 0 /* log_to_monitor */ }; -static AudioState glob_audio_state; +AudioState glob_audio_state; volume_t nominal_volume = { 0, @@ -303,6 +384,10 @@ static const char *audio_get_conf_str (const char *key, } } +/* defined in android_sdl.c */ +extern void dprintn(const char* fmt, ...); +extern void dprintnv(const char* fmt, va_list args); + void AUD_vlog (const char *cap, const char *fmt, va_list ap) { if (conf.log_to_monitor) { @@ -313,11 +398,14 @@ void AUD_vlog (const char *cap, const char *fmt, va_list ap) term_vprintf (fmt, ap); } else { + if (!VERBOSE_CHECK(audio)) + return; + if (cap) { - fprintf (stderr, "%s: ", cap); + dprintn("%s: ", cap); } - vfprintf (stderr, fmt, ap); + dprintnv(fmt, ap); } } @@ -605,11 +693,11 @@ void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) } if (info->sign) { - memset (buf, len << info->shift, 0x00); + memset (buf, 0x80, len << info->shift); } else { if (info->bits == 8) { - memset (buf, len << info->shift, 0x80); + memset (buf, 0x80, len << info->shift); } else { int i; @@ -1024,7 +1112,9 @@ int AUD_write (SWVoiceOut *sw, void *buf, int size) return 0; } - bytes = sw->hw->pcm_ops->write (sw, buf, size); + BEGIN_NOSIGALRM + bytes = sw->hw->pcm_ops->write (sw, buf, size); + END_NOSIGALRM return bytes; } @@ -1042,7 +1132,9 @@ int AUD_read (SWVoiceIn *sw, void *buf, int size) return 0; } - bytes = sw->hw->pcm_ops->read (sw, buf, size); + BEGIN_NOSIGALRM + bytes = sw->hw->pcm_ops->read (sw, buf, size); + END_NOSIGALRM return bytes; } @@ -1068,7 +1160,9 @@ void AUD_set_active_out (SWVoiceOut *sw, int on) hw->pending_disable = 0; if (!hw->enabled) { hw->enabled = 1; - hw->pcm_ops->ctl_out (hw, VOICE_ENABLE); + BEGIN_NOSIGALRM + hw->pcm_ops->ctl_out (hw, VOICE_ENABLE); + END_NOSIGALRM } } else { @@ -1109,7 +1203,9 @@ void AUD_set_active_in (SWVoiceIn *sw, int on) if (on) { if (!hw->enabled) { hw->enabled = 1; - hw->pcm_ops->ctl_in (hw, VOICE_ENABLE); + BEGIN_NOSIGALRM + hw->pcm_ops->ctl_in (hw, VOICE_ENABLE); + END_NOSIGALRM } sw->total_hw_samples_acquired = hw->total_samples_captured; } @@ -1124,7 +1220,9 @@ void AUD_set_active_in (SWVoiceIn *sw, int on) if (nb_active == 1) { hw->enabled = 0; - hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); + BEGIN_NOSIGALRM + hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); + END_NOSIGALRM } } } @@ -1244,7 +1342,9 @@ static void audio_run_out (AudioState *s) #endif hw->enabled = 0; hw->pending_disable = 0; - hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); + BEGIN_NOSIGALRM + hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); + END_NOSIGALRM for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { sc->sw.active = 0; audio_recalc_and_notify_capture (sc->cap); @@ -1265,7 +1365,9 @@ static void audio_run_out (AudioState *s) } prev_rpos = hw->rpos; - played = hw->pcm_ops->run_out (hw); + BEGIN_NOSIGALRM + played = hw->pcm_ops->run_out (hw); + END_NOSIGALRM if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) { dolog ("hw->rpos=%d hw->samples=%d played=%d\n", hw->rpos, hw->samples, played); @@ -1334,7 +1436,9 @@ static void audio_run_in (AudioState *s) SWVoiceIn *sw; int captured, min; - captured = hw->pcm_ops->run_in (hw); + BEGIN_NOSIGALRM + captured = hw->pcm_ops->run_in (hw); + END_NOSIGALRM min = audio_pcm_hw_find_min_in (hw); hw->total_samples_captured += captured - min; @@ -1404,8 +1508,36 @@ static void audio_run_capture (AudioState *s) static void audio_timer (void *opaque) { - AudioState *s = opaque; - + AudioState* s = opaque; +#if 0 +#define MAX_DIFFS 1000 + int64_t now = qemu_get_clock(vm_clock); + static int64_t last = 0; + static float diffs[MAX_DIFFS]; + static int num_diffs; + + if (last == 0) + last = now; + else { + diffs[num_diffs] = (float)((now-last)/1e6); /* last diff in ms */ + if (++num_diffs == MAX_DIFFS) { + double min_diff = 1e6, max_diff = -1e6; + double all_diff = 0.; + int nn; + + for (nn = 0; nn < num_diffs; nn++) { + if (diffs[nn] < min_diff) min_diff = diffs[nn]; + if (diffs[nn] > max_diff) max_diff = diffs[nn]; + all_diff += diffs[nn]; + } + all_diff *= 1.0/num_diffs; + printf("audio timer: min_diff=%6.2g max_diff=%6.2g avg_diff=%6.2g samples=%d\n", + min_diff, max_diff, all_diff, num_diffs); + num_diffs = 0; + } + } + last = now; +#endif audio_run_out (s); audio_run_in (s); audio_run_capture (s); @@ -1530,17 +1662,43 @@ void AUD_help (void) ); } -static int audio_driver_init (AudioState *s, struct audio_driver *drv) +static int audio_driver_init (AudioState *s, struct audio_driver *drv, int out) { + void* opaque; + if (drv->options) { audio_process_options (drv->name, drv->options); } - s->drv_opaque = drv->init (); - if (s->drv_opaque) { + /* is the driver already initialized ? */ + if (out) { + if (drv == s->drv_in) { + s->drv_out = drv; + s->drv_out_opaque = s->drv_in_opaque; + return 0; + } + } else { + if (drv == s->drv_out) { + s->drv_in = drv; + s->drv_in_opaque = s->drv_out_opaque; + return 0; + } + } + + BEGIN_NOSIGALRM + opaque = drv->init(); + END_NOSIGALRM + + if (opaque != NULL) { audio_init_nb_voices_out (s, drv); audio_init_nb_voices_in (s, drv); - s->drv = drv; + if (out) { + s->drv_out = drv; + s->drv_out_opaque = opaque; + } else { + s->drv_in = drv; + s->drv_in_opaque = opaque; + } return 0; } else { @@ -1556,21 +1714,30 @@ static void audio_vm_change_state_handler (void *opaque, int running) HWVoiceIn *hwi = NULL; int op = running ? VOICE_ENABLE : VOICE_DISABLE; - while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) { - hwo->pcm_ops->ctl_out (hwo, op); - } + BEGIN_NOSIGALRM + while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) { + hwo->pcm_ops->ctl_out (hwo, op); + } - while ((hwi = audio_pcm_hw_find_any_enabled_in (s, hwi))) { - hwi->pcm_ops->ctl_in (hwi, op); - } + while ((hwi = audio_pcm_hw_find_any_enabled_in (s, hwi))) { + hwi->pcm_ops->ctl_in (hwi, op); + } + END_NOSIGALRM } +// to make sure audio_atexit() is only called once +static int initialized = 0; + static void audio_atexit (void) { AudioState *s = &glob_audio_state; HWVoiceOut *hwo = NULL; HWVoiceIn *hwi = NULL; + if (!initialized) return; + initialized = 0; + + BEGIN_NOSIGALRM while ((hwo = audio_pcm_hw_find_any_enabled_out (s, hwo))) { SWVoiceCap *sc; @@ -1592,9 +1759,13 @@ static void audio_atexit (void) hwi->pcm_ops->fini_in (hwi); } - if (s->drv) { - s->drv->fini (s->drv_opaque); + if (s->drv_in) { + s->drv_in->fini (s->drv_in_opaque); } + if (s->drv_out) { + s->drv_out->fini (s->drv_out_opaque); + } + END_NOSIGALRM } static void audio_save (QEMUFile *f, void *opaque) @@ -1630,11 +1801,70 @@ void AUD_remove_card (QEMUSoundCard *card) qemu_free (card->name); } +static int +find_audio_driver( AudioState* s, int out ) +{ + int i, done = 0, def; + const char* envname; + const char* drvname; + struct audio_driver* drv = NULL; + const char* drvtype = out ? "output" : "input"; + + envname = out ? "QEMU_AUDIO_OUT_DRV" : "QEMU_AUDIO_IN_DRV"; + drvname = audio_get_conf_str(envname, NULL, &def); + if (drvname == NULL) { + drvname = audio_get_conf_str("QEMU_AUDIO_DRV", NULL, &def); + } + + if (drvname != NULL) { /* look for a specific driver */ + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { + if (!strcmp (drvname, drvtab[i]->name)) { + drv = drvtab[i]; + break; + } + } + } + + if (drv != NULL) { + done = !audio_driver_init (s, drv, out); + if (!done) { + dolog ("Could not initialize '%s' %s audio backend, trying default one.\n", + drvname, drvtype); + dolog ("Run with -qemu -audio-help to list available backends\n"); + drv = NULL; + } + } + + if (!drv) { + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { + if (drvtab[i]->can_be_default) { + drv = drvtab[i]; + done = !audio_driver_init (s, drv, out); + if (done) + break; + } + } + } + + if (!done) { + drv = &no_audio_driver; + done = !audio_driver_init (s, drv, out); + if (!done) { + /* this should never happen */ + dolog ("Could not initialize audio subsystem\n"); + return -1; + } + dolog ("warning: Could not find suitable audio %s backend\n", drvtype); + } + + if (VERBOSE_CHECK(init)) + dprint("using '%s' audio %s backend", drv->name, drvtype ); + return 0; +} + + AudioState *AUD_init (void) { - size_t i; - int done = 0; - const char *drvname; AudioState *s = &glob_audio_state; LIST_INIT (&s->hw_head_out); @@ -1665,47 +1895,9 @@ AudioState *AUD_init (void) s->nb_hw_voices_in = 0; } + if ( find_audio_driver (s, 0) == 0 && + find_audio_driver (s, 1) == 0 ) { - int def; - drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def); - } - - if (drvname) { - int found = 0; - - for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { - if (!strcmp (drvname, drvtab[i]->name)) { - done = !audio_driver_init (s, drvtab[i]); - found = 1; - break; - } - } - - if (!found) { - dolog ("Unknown audio driver `%s'\n", drvname); - dolog ("Run with -audio-help to list available drivers\n"); - } - } - - if (!done) { - for (i = 0; !done && i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { - if (drvtab[i]->can_be_default) { - done = !audio_driver_init (s, drvtab[i]); - } - } - } - - if (!done) { - done = !audio_driver_init (s, &no_audio_driver); - if (!done) { - dolog ("Could not initialize audio subsystem\n"); - } - else { - dolog ("warning: Using timer based audio emulation\n"); - } - } - - if (done) { VMChangeStateEntry *e; if (conf.period.hz <= 0) { @@ -1731,12 +1923,20 @@ AudioState *AUD_init (void) return NULL; } + initialized = 1; + LIST_INIT (&s->card_head); register_savevm ("audio", 0, 1, audio_save, audio_load, s); qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + conf.period.ticks); return s; } +// this was added to work around a deadlock in SDL when quitting +void AUD_cleanup() +{ + audio_atexit(); +} + CaptureVoiceOut *AUD_add_capture ( AudioState *s, audsettings_t *as, @@ -1845,21 +2045,16 @@ void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque) if (!cap->cb_head.lh_first) { SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; - while (sw) { - SWVoiceCap *sc = (SWVoiceCap *) sw; #ifdef DEBUG_CAPTURE dolog ("freeing %s\n", sw->name); #endif - sw1 = sw->entries.le_next; if (sw->rate) { st_rate_stop (sw->rate); sw->rate = NULL; } LIST_REMOVE (sw, entries); - LIST_REMOVE (sc, entries); - qemu_free (sc); sw = sw1; } LIST_REMOVE (cap, entries); diff --git a/audio/audio.h b/audio/audio.h index c097f39..cc7aa04 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -93,6 +93,8 @@ void AUD_log (const char *cap, const char *fmt, ...) #endif ; +extern AudioState glob_audio_state; + AudioState *AUD_init (void); void AUD_help (void); void AUD_register_card (AudioState *s, const char *name, QEMUSoundCard *card); @@ -166,4 +168,13 @@ uint32_t lsbindex (uint32_t u); #define audio_MAX(a, b) ((a)<(b)?(b):(a)) #endif +extern int +audio_get_backend_count( int is_input ); + +extern const char* +audio_get_backend_name( int is_input, int index, const char* *pinfo ); + +extern int +audio_check_backend_name( int is_input, const char* name ); + #endif /* audio.h */ diff --git a/audio/audio_int.h b/audio/audio_int.h index 1a15d4c..099bcbd 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -182,8 +182,10 @@ struct SWVoiceCap { }; struct AudioState { - struct audio_driver *drv; - void *drv_opaque; + struct audio_driver* drv_in; + void* drv_in_opaque; + struct audio_driver* drv_out; + void* drv_out_opaque; QEMUTimer *ts; LIST_HEAD (card_listhead, QEMUSoundCard) card_head; @@ -197,8 +199,10 @@ struct AudioState { extern struct audio_driver no_audio_driver; extern struct audio_driver oss_audio_driver; extern struct audio_driver sdl_audio_driver; +extern struct audio_driver win_audio_driver; extern struct audio_driver wav_audio_driver; extern struct audio_driver fmod_audio_driver; +extern struct audio_driver esd_audio_driver; extern struct audio_driver alsa_audio_driver; extern struct audio_driver coreaudio_audio_driver; extern struct audio_driver dsound_audio_driver; diff --git a/audio/audio_pt_int.c b/audio/audio_pt_int.c new file mode 100644 index 0000000..3f01762 --- /dev/null +++ b/audio/audio_pt_int.c @@ -0,0 +1,149 @@ +#include "vl.h" +#include "audio.h" + +#define AUDIO_CAP "audio-pt" + +#include "audio_int.h" +#include "audio_pt_int.h" + +static void logerr (struct audio_pt *pt, int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (pt->drv, fmt, ap); + va_end (ap); + + AUD_log (NULL, "\n"); + AUD_log (pt->drv, "Reason: %s\n", strerror (err)); +} + +int audio_pt_init (struct audio_pt *p, void *(*func) (void *), + void *opaque, const char *drv, const char *cap) +{ + int err, err2; + const char *efunc; + + p->drv = drv; + + err = pthread_mutex_init (&p->mutex, NULL); + if (err) { + efunc = "pthread_mutex_init"; + goto err0; + } + + err = pthread_cond_init (&p->cond, NULL); + if (err) { + efunc = "pthread_cond_init"; + goto err1; + } + + err = pthread_create (&p->thread, NULL, func, opaque); + if (err) { + efunc = "pthread_create"; + goto err2; + } + + return 0; + + err2: + err2 = pthread_cond_destroy (&p->cond); + if (err2) { + logerr (p, err2, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + } + + err1: + err2 = pthread_mutex_destroy (&p->mutex); + if (err2) { + logerr (p, err2, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + } + + err0: + logerr (p, err, "%s(%s): %s failed", cap, AUDIO_FUNC, efunc); + return -1; +} + +int audio_pt_fini (struct audio_pt *p, const char *cap) +{ + int err, ret = 0; + + err = pthread_cond_destroy (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + + err = pthread_mutex_destroy (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + return ret; +} + +int audio_pt_lock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_lock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_lock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_wait (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_cond_wait (&p->cond, &p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_wait failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock_and_signal (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + err = pthread_cond_signal (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_signal failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_join (struct audio_pt *p, void **arg, const char *cap) +{ + int err; + void *ret; + + err = pthread_join (p->thread, &ret); + if (err) { + logerr (p, err, "%s(%s): pthread_join failed", cap, AUDIO_FUNC); + return -1; + } + *arg = ret; + return 0; +} diff --git a/audio/audio_pt_int.h b/audio/audio_pt_int.h new file mode 100644 index 0000000..0dfff76 --- /dev/null +++ b/audio/audio_pt_int.h @@ -0,0 +1,22 @@ +#ifndef QEMU_AUDIO_PT_INT_H +#define QEMU_AUDIO_PT_INT_H + +#include + +struct audio_pt { + const char *drv; + pthread_t thread; + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +int audio_pt_init (struct audio_pt *, void *(*) (void *), void *, + const char *, const char *); +int audio_pt_fini (struct audio_pt *, const char *); +int audio_pt_lock (struct audio_pt *, const char *); +int audio_pt_unlock (struct audio_pt *, const char *); +int audio_pt_wait (struct audio_pt *, const char *); +int audio_pt_unlock_and_signal (struct audio_pt *, const char *); +int audio_pt_join (struct audio_pt *, void **, const char *); + +#endif /* audio_pt_int.h */ diff --git a/audio/audio_template.h b/audio/audio_template.h index 13e1c3e..4575a56 100644 --- a/audio/audio_template.h +++ b/audio/audio_template.h @@ -205,7 +205,9 @@ static void glue (audio_pcm_hw_gc_, TYPE) (AudioState *s, HW **hwp) LIST_REMOVE (hw, entries); glue (s->nb_hw_voices_, TYPE) += 1; glue (audio_pcm_hw_free_resources_ ,TYPE) (hw); + BEGIN_NOSIGALRM glue (hw->pcm_ops->fini_, TYPE) (hw); + END_NOSIGALRM qemu_free (hw); *hwp = NULL; } @@ -243,7 +245,8 @@ static HW *glue (audio_pcm_hw_find_specific_, TYPE) ( static HW *glue (audio_pcm_hw_add_new_, TYPE) (AudioState *s, audsettings_t *as) { HW *hw; - struct audio_driver *drv = s->drv; + struct audio_driver *drv = glue(s->drv_, TYPE); + int err; if (!glue (s->nb_hw_voices_, TYPE)) { return NULL; @@ -271,9 +274,11 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (AudioState *s, audsettings_t *as) #ifdef DAC LIST_INIT (&hw->cap_head); #endif - if (glue (hw->pcm_ops->init_, TYPE) (hw, as)) { + BEGIN_NOSIGALRM + err = glue (hw->pcm_ops->init_, TYPE) (hw, as); + END_NOSIGALRM + if (err) goto err0; - } if (audio_bug (AUDIO_FUNC, hw->samples <= 0)) { dolog ("hw->samples=%d\n", hw->samples); @@ -302,7 +307,9 @@ static HW *glue (audio_pcm_hw_add_new_, TYPE) (AudioState *s, audsettings_t *as) return hw; err1: + BEGIN_NOSIGALRM glue (hw->pcm_ops->fini_, TYPE) (hw); + END_NOSIGALRM err0: qemu_free (hw); return NULL; @@ -431,7 +438,7 @@ SW *glue (AUD_open_, TYPE) ( goto fail; } - if (audio_bug (AUDIO_FUNC, !s->drv)) { + if (audio_bug (AUDIO_FUNC, !glue (s->drv_, TYPE))) { dolog ("Can not open `%s' (no host audio driver)\n", name); goto fail; } diff --git a/audio/coreaudio.c b/audio/coreaudio.c index 8512f12..cf3eaf9 100644 --- a/audio/coreaudio.c +++ b/audio/coreaudio.c @@ -1,6 +1,7 @@ /* * QEMU OS X CoreAudio audio driver * + * Copyright (c) 2008 The Android Open Source Project * Copyright (c) 2005 Mike Kronenberg * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -31,27 +32,35 @@ #define AUDIO_CAP "coreaudio" #include "audio_int.h" +#define ENABLE_IN 1 + +#if 0 +# define D(...) fprintf(stderr, __VA_ARGS__) +#else +# define D(...) ((void)0) +#endif + struct { - int buffer_frames; - int nbuffers; + int out_buffer_frames; + int out_nbuffers; + int in_buffer_frames; + int in_nbuffers; int isAtexit; } conf = { - .buffer_frames = 512, - .nbuffers = 4, + .out_buffer_frames = 512, + .out_nbuffers = 4, + .in_buffer_frames = 512, + .in_nbuffers = 4, .isAtexit = 0 }; -typedef struct coreaudioVoiceOut { - HWVoiceOut hw; - pthread_mutex_t mutex; - int isAtexit; - AudioDeviceID outputDeviceID; - UInt32 audioDevicePropertyBufferFrameSize; - AudioStreamBasicDescription outputStreamBasicDescription; - int live; - int decr; - int rpos; -} coreaudioVoiceOut; +/***************************************************************************************/ +/***************************************************************************************/ +/*** ***/ +/*** U T I L I T Y R O U T I N E S ***/ +/*** ***/ +/***************************************************************************************/ +/***************************************************************************************/ static void coreaudio_logstatus (OSStatus status) { @@ -143,13 +152,40 @@ static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( coreaudio_logstatus (status); } -static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) +static void coreaudio_atexit (void) +{ + conf.isAtexit = 1; +} + +/***************************************************************************************/ +/***************************************************************************************/ +/*** ***/ +/*** S H A R E D I N / O U T V O I C E ***/ +/*** ***/ +/***************************************************************************************/ +/***************************************************************************************/ + +typedef struct coreAudioVoice { + pthread_mutex_t mutex; + AudioDeviceID deviceID; + Boolean isInput; + UInt32 bufferFrameSize; + AudioStreamBasicDescription streamBasicDescription; + AudioDeviceIOProc ioproc; + int live; + int decr; + int pos; +} coreaudioVoice; + + +static inline UInt32 +coreaudio_voice_isPlaying (coreaudioVoice* core) { OSStatus status; UInt32 result = 0; - UInt32 propertySize = sizeof(outputDeviceID); + UInt32 propertySize = sizeof(core->deviceID); status = AudioDeviceGetProperty( - outputDeviceID, 0, 0, + core->deviceID, 0, core->isInput, kAudioDevicePropertyDeviceIsRunning, &propertySize, &result); if (status != kAudioHardwareNoError) { coreaudio_logerr(status, @@ -158,12 +194,8 @@ static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) return result; } -static void coreaudio_atexit (void) -{ - conf.isAtexit = 1; -} - -static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) +static int +coreaudio_voice_lock (coreaudioVoice* core, const char *fn_name) { int err; @@ -176,7 +208,8 @@ static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) return 0; } -static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) +static int +coreaudio_voice_unlock (coreaudioVoice* core, const char *fn_name) { int err; @@ -189,114 +222,81 @@ static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) return 0; } -static int coreaudio_run_out (HWVoiceOut *hw) +static int +coreaudio_voice_ctl (coreaudioVoice* core, int cmd) { - int live, decr; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; - - if (coreaudio_lock (core, "coreaudio_run_out")) { - return 0; - } + OSStatus status; - live = audio_pcm_hw_get_live_out (hw); + switch (cmd) { + case VOICE_ENABLE: + /* start playback */ + D("%s: %s started\n", __FUNCTION__, core->isInput ? "input" : "output"); + if (!coreaudio_voice_isPlaying(core)) { + status = AudioDeviceStart(core->deviceID, core->ioproc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not resume playback\n"); + } + } + break; - if (core->decr > live) { - ldebug ("core->decr %d live %d core->live %d\n", - core->decr, - live, - core->live); + case VOICE_DISABLE: + /* stop playback */ + D("%s: %s stopped\n", __FUNCTION__, core->isInput ? "input" : "output"); + if (!conf.isAtexit) { + if (coreaudio_voice_isPlaying(core)) { + status = AudioDeviceStop(core->deviceID, core->ioproc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not pause playback\n"); + } + } + } + break; } - - decr = audio_MIN (core->decr, live); - core->decr -= decr; - - core->live = live - decr; - hw->rpos = core->rpos; - - coreaudio_unlock (core, "coreaudio_run_out"); - return decr; + return 0; } -/* callback to feed audiooutput buffer */ -static OSStatus audioDeviceIOProc( - AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* hwptr) +static void +coreaudio_voice_fini (coreaudioVoice* core) { - UInt32 frame, frameCount; - float *out = outOutputData->mBuffers[0].mData; - HWVoiceOut *hw = hwptr; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; - int rpos, live; - st_sample_t *src; -#ifndef FLOAT_MIXENG -#ifdef RECIPROCAL - const float scale = 1.f / UINT_MAX; -#else - const float scale = UINT_MAX; -#endif -#endif - - if (coreaudio_lock (core, "audioDeviceIOProc")) { - inInputTime = 0; - return 0; - } + OSStatus status; + int err; - frameCount = core->audioDevicePropertyBufferFrameSize; - live = core->live; + if (!conf.isAtexit) { + /* stop playback */ + coreaudio_voice_ctl(core, VOICE_DISABLE); - /* if there are not enough samples, set signal and return */ - if (live < frameCount) { - inInputTime = 0; - coreaudio_unlock (core, "audioDeviceIOProc(empty)"); - return 0; + /* remove callback */ + status = AudioDeviceRemoveIOProc(core->deviceID, core->ioproc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not remove IOProc\n"); + } } + core->deviceID = kAudioDeviceUnknown; - rpos = core->rpos; - src = hw->mix_buf + rpos; - - /* fill buffer */ - for (frame = 0; frame < frameCount; frame++) { -#ifdef FLOAT_MIXENG - *out++ = src[frame].l; /* left channel */ - *out++ = src[frame].r; /* right channel */ -#else -#ifdef RECIPROCAL - *out++ = src[frame].l * scale; /* left channel */ - *out++ = src[frame].r * scale; /* right channel */ -#else - *out++ = src[frame].l / scale; /* left channel */ - *out++ = src[frame].r / scale; /* right channel */ -#endif -#endif + /* destroy mutex */ + err = pthread_mutex_destroy(&core->mutex); + if (err) { + dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); } - - rpos = (rpos + frameCount) % hw->samples; - core->decr += frameCount; - core->rpos = rpos; - - coreaudio_unlock (core, "audioDeviceIOProc"); - return 0; } -static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) -{ - return audio_pcm_sw_write (sw, buf, len); -} -static int coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) +static int +coreaudio_voice_init (coreaudioVoice* core, + audsettings_t* as, + int frameSize, + AudioDeviceIOProc ioproc, + void* hw, + int input) { - OSStatus status; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; - UInt32 propertySize; - int err; - int bits = 8; - const char *typ = "playback"; + OSStatus status; + UInt32 propertySize; + int err; + int bits = 8; AudioValueRange frameRange; + const char* typ = input ? "input" : "playback"; + + core->isInput = input ? true : false; /* create mutex */ err = pthread_mutex_init(&core->mutex, NULL); @@ -309,20 +309,24 @@ static int coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) bits = 16; } - audio_pcm_init_info (&hw->info, as); - + // TODO: audio_pcm_init_info (&hw->info, as); /* open default output device */ - propertySize = sizeof(core->outputDeviceID); + /* note: we use DefaultSystemOutputDevice because DefaultOutputDevice seems to + * always link to the internal speakers, and not the ones selected through system properties + * go figure... + */ + propertySize = sizeof(core->deviceID); status = AudioHardwareGetProperty( - kAudioHardwarePropertyDefaultOutputDevice, + input ? kAudioHardwarePropertyDefaultInputDevice : + kAudioHardwarePropertyDefaultSystemOutputDevice, &propertySize, - &core->outputDeviceID); + &core->deviceID); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, - "Could not get default output Device\n"); + "Could not get default %s device\n", typ); return -1; } - if (core->outputDeviceID == kAudioDeviceUnknown) { + if (core->deviceID == kAudioDeviceUnknown) { dolog ("Could not initialize %s - Unknown Audiodevice\n", typ); return -1; } @@ -330,9 +334,9 @@ static int coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) /* get minimum and maximum buffer frame sizes */ propertySize = sizeof(frameRange); status = AudioDeviceGetProperty( - core->outputDeviceID, - 0, + core->deviceID, 0, + core->isInput, kAudioDevicePropertyBufferFrameSizeRange, &propertySize, &frameRange); @@ -342,100 +346,101 @@ static int coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) return -1; } - if (frameRange.mMinimum > conf.buffer_frames) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; - dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); + if (frameRange.mMinimum > frameSize) { + core->bufferFrameSize = (UInt32) frameRange.mMinimum; + dolog ("warning: Upsizing Output Buffer Frames to %f\n", frameRange.mMinimum); } - else if (frameRange.mMaximum < conf.buffer_frames) { - core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; - dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); + else if (frameRange.mMaximum < frameSize) { + core->bufferFrameSize = (UInt32) frameRange.mMaximum; + dolog ("warning: Downsizing Output Buffer Frames to %f\n", frameRange.mMaximum); } else { - core->audioDevicePropertyBufferFrameSize = conf.buffer_frames; + core->bufferFrameSize = frameSize; } /* set Buffer Frame Size */ - propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + propertySize = sizeof(core->bufferFrameSize); status = AudioDeviceSetProperty( - core->outputDeviceID, + core->deviceID, NULL, 0, - false, + core->isInput, kAudioDevicePropertyBufferFrameSize, propertySize, - &core->audioDevicePropertyBufferFrameSize); + &core->bufferFrameSize); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not set device buffer frame size %ld\n", - core->audioDevicePropertyBufferFrameSize); + core->bufferFrameSize); return -1; } /* get Buffer Frame Size */ - propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + propertySize = sizeof(core->bufferFrameSize); status = AudioDeviceGetProperty( - core->outputDeviceID, + core->deviceID, 0, - false, + core->isInput, kAudioDevicePropertyBufferFrameSize, &propertySize, - &core->audioDevicePropertyBufferFrameSize); + &core->bufferFrameSize); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get device buffer frame size\n"); return -1; } - hw->samples = conf.nbuffers * core->audioDevicePropertyBufferFrameSize; + // TODO: hw->samples = *pNBuffers * core->bufferFrameSize; /* get StreamFormat */ - propertySize = sizeof(core->outputStreamBasicDescription); + propertySize = sizeof(core->streamBasicDescription); status = AudioDeviceGetProperty( - core->outputDeviceID, + core->deviceID, 0, - false, + core->isInput, kAudioDevicePropertyStreamFormat, &propertySize, - &core->outputStreamBasicDescription); + &core->streamBasicDescription); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not get Device Stream properties\n"); - core->outputDeviceID = kAudioDeviceUnknown; + core->deviceID = kAudioDeviceUnknown; return -1; } /* set Samplerate */ - core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; - propertySize = sizeof(core->outputStreamBasicDescription); + core->streamBasicDescription.mSampleRate = (Float64) as->freq; + propertySize = sizeof(core->streamBasicDescription); status = AudioDeviceSetProperty( - core->outputDeviceID, - 0, + core->deviceID, 0, 0, + core->isInput, kAudioDevicePropertyStreamFormat, propertySize, - &core->outputStreamBasicDescription); + &core->streamBasicDescription); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", as->freq); - core->outputDeviceID = kAudioDeviceUnknown; + core->deviceID = kAudioDeviceUnknown; return -1; } /* set Callback */ - status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); + core->ioproc = ioproc; + status = AudioDeviceAddIOProc(core->deviceID, ioproc, hw); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); - core->outputDeviceID = kAudioDeviceUnknown; + core->deviceID = kAudioDeviceUnknown; return -1; } /* start Playback */ - if (!isPlaying(core->outputDeviceID)) { - status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (!input && !coreaudio_voice_isPlaying(core)) { + status = AudioDeviceStart(core->deviceID, core->ioproc); if (status != kAudioHardwareNoError) { coreaudio_logerr2 (status, typ, "Could not start playback\n"); - AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); - core->outputDeviceID = kAudioDeviceUnknown; + AudioDeviceRemoveIOProc(core->deviceID, core->ioproc); + core->deviceID = kAudioDeviceUnknown; return -1; } } @@ -443,84 +448,332 @@ static int coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) return 0; } -static void coreaudio_fini_out (HWVoiceOut *hw) + +/***************************************************************************************/ +/***************************************************************************************/ +/*** ***/ +/*** O U T P U T V O I C E ***/ +/*** ***/ +/***************************************************************************************/ +/***************************************************************************************/ + +typedef struct coreaudioVoiceOut { + HWVoiceOut hw; + coreaudioVoice core[1]; +} coreaudioVoiceOut; + +#define CORE_OUT(hw) ((coreaudioVoiceOut*)(hw))->core + + +static int +coreaudio_run_out (HWVoiceOut *hw) { - OSStatus status; - int err; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + int live, decr; + coreaudioVoice *core = CORE_OUT(hw); - if (!conf.isAtexit) { - /* stop playback */ - if (isPlaying(core->outputDeviceID)) { - status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); - if (status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not stop playback\n"); - } - } + if (coreaudio_voice_lock (core, "coreaudio_run_out")) { + return 0; + } - /* remove callback */ - status = AudioDeviceRemoveIOProc(core->outputDeviceID, - audioDeviceIOProc); - if (status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not remove IOProc\n"); - } + live = audio_pcm_hw_get_live_out (hw); + + if (core->decr > live) { + ldebug ("core->decr %d live %d core->live %d\n", + core->decr, + live, + core->live); } - core->outputDeviceID = kAudioDeviceUnknown; - /* destroy mutex */ - err = pthread_mutex_destroy(&core->mutex); - if (err) { - dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); + decr = audio_MIN (core->decr, live); + core->decr -= decr; + core->live = live - decr; + hw->rpos = core->pos; + + coreaudio_voice_unlock (core, "coreaudio_run_out"); + return decr; +} + + +/* callback to feed audiooutput buffer */ +static OSStatus +audioOutDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* hwptr) +{ + UInt32 frame, frameCount; + float *out = outOutputData->mBuffers[0].mData; + HWVoiceOut *hw = hwptr; + coreaudioVoice *core = CORE_OUT(hw); + int rpos, live; + st_sample_t *src; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL + const float scale = 1.f / UINT_MAX; +#else + const float scale = UINT_MAX; +#endif +#endif + + if (coreaudio_voice_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; } + + frameCount = core->bufferFrameSize; + live = core->live; + + /* if there are not enough samples, set signal and return */ + if (live < frameCount) { + inInputTime = 0; + coreaudio_voice_unlock (core, "audioDeviceIOProc(empty)"); + return 0; + } + + rpos = core->pos; + src = hw->mix_buf + rpos; + + /* fill buffer */ + for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG + *out++ = src[frame].l; /* left channel */ + *out++ = src[frame].r; /* right channel */ +#else +#ifdef RECIPROCAL + *out++ = src[frame].l * scale; /* left channel */ + *out++ = src[frame].r * scale; /* right channel */ +#else + *out++ = src[frame].l / scale; /* left channel */ + *out++ = src[frame].r / scale; /* right channel */ +#endif +#endif + } + + rpos = (rpos + frameCount) % hw->samples; + core->decr += frameCount; + core->pos = rpos; + + coreaudio_voice_unlock (core, "audioDeviceIOProc"); + return 0; } -static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +static int +coreaudio_write (SWVoiceOut *sw, void *buf, int len) { - OSStatus status; - coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + return audio_pcm_sw_write (sw, buf, len); +} - switch (cmd) { - case VOICE_ENABLE: - /* start playback */ - if (!isPlaying(core->outputDeviceID)) { - status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); - if (status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not resume playback\n"); - } - } - break; +static int +coreaudio_init_out (HWVoiceOut *hw, audsettings_t *as) +{ + coreaudioVoice* core = CORE_OUT(hw); + int err; - case VOICE_DISABLE: - /* stop playback */ - if (!conf.isAtexit) { - if (isPlaying(core->outputDeviceID)) { - status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); - if (status != kAudioHardwareNoError) { - coreaudio_logerr (status, "Could not pause playback\n"); - } - } - } - break; + audio_pcm_init_info (&hw->info, as); + + err = coreaudio_voice_init( core, as, conf.out_buffer_frames, audioOutDeviceIOProc, hw, 0 ); + if (err < 0) + return err; + + hw->samples = core->bufferFrameSize * conf.out_nbuffers; + return 0; +} + +static void +coreaudio_fini_out (HWVoiceOut *hw) +{ + + coreaudioVoice* core = CORE_OUT(hw); + + coreaudio_voice_fini(core); +} + +static int +coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + coreaudioVoice* core = CORE_OUT(hw); + + return coreaudio_voice_ctl(core, cmd); +} + +/***************************************************************************************/ +/***************************************************************************************/ +/*** ***/ +/*** I N P U T V O I C E ***/ +/*** ***/ +/***************************************************************************************/ +/***************************************************************************************/ + + + +typedef struct coreaudioVoiceIn { + HWVoiceIn hw; + coreaudioVoice core[1]; +} coreaudioVoiceIn; + +#define CORE_IN(hw) ((coreaudioVoiceIn*)(hw))->core + + +static int +coreaudio_run_in (HWVoiceIn *hw) +{ + int decr; + + coreaudioVoice *core = CORE_IN(hw); + + if (coreaudio_voice_lock (core, "coreaudio_run_in")) { + return 0; + } + + D("%s: core.decr=%d core.pos=%d\n", __FUNCTION__, core->decr, core->pos); + decr = core->decr; + core->decr -= decr; + hw->wpos = core->pos; + + coreaudio_voice_unlock (core, "coreaudio_run_in"); + return decr; +} + + +/* callback to feed audiooutput buffer */ +static OSStatus +audioInDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* hwptr) +{ + UInt32 frame, frameCount; + float *in = inInputData->mBuffers[0].mData; + HWVoiceIn *hw = hwptr; + coreaudioVoice *core = CORE_IN(hw); + int wpos, avail; + st_sample_t *dst; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL + const float scale = 1.f / UINT_MAX; +#else + const float scale = UINT_MAX; +#endif +#endif + + if (coreaudio_voice_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; + } + + frameCount = core->bufferFrameSize; + avail = hw->samples - hw->total_samples_captured - core->decr; + + D("%s: enter avail=%d core.decr=%d core.pos=%d hw.samples=%d hw.total_samples_captured=%d frameCount=%d\n", + __FUNCTION__, avail, core->decr, core->pos, hw->samples, hw->total_samples_captured, (int)frameCount); + + /* if there are not enough samples, set signal and return */ + if (avail < frameCount) { + inInputTime = 0; + coreaudio_voice_unlock (core, "audioDeviceIOProc(empty)"); + return 0; } + + wpos = core->pos; + dst = hw->conv_buf + wpos; + + /* fill buffer */ + for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG + dst[frame].l = *in++; /* left channel */ + dst[frame].r = *in++; /* right channel */ +#else +#ifdef RECIPROCAL + dst[frame].l = *in++ * scale; /* left channel */ + dst[frame].r = *in++ * scale; /* right channel */ +#else + dst[frame].l = *in++ / scale; /* left channel */ + dst[frame].r = *in++ / scale; /* right channel */ +#endif +#endif + } + + wpos = (wpos + frameCount) % hw->samples; + core->decr += frameCount; + core->pos = wpos; + + D("exit: core.decr=%d core.pos=%d\n", core->decr, core->pos); + coreaudio_voice_unlock (core, "audioDeviceIOProc"); + return 0; +} + +static int +coreaudio_read (SWVoiceIn *sw, void *buf, int len) +{ + int result = audio_pcm_sw_read(sw, buf, len); + D("%s: audio_pcm_sw_read(%d) returned %d\n", __FUNCTION__, len, result); + return result; +} + +static int +coreaudio_init_in (HWVoiceIn *hw, audsettings_t *as) +{ + coreaudioVoice* core = CORE_IN(hw); + int err; + + audio_pcm_init_info (&hw->info, as); + + err = coreaudio_voice_init( core, as, conf.in_buffer_frames, audioInDeviceIOProc, hw, 1 ); + if (err < 0) { + return err; + } + + hw->samples = core->bufferFrameSize * conf.in_nbuffers; return 0; } -static void *coreaudio_audio_init (void) +static void +coreaudio_fini_in (HWVoiceIn *hw) +{ + + coreaudioVoice* core = CORE_IN(hw); + + coreaudio_voice_fini(core); +} + +static int +coreaudio_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + coreaudioVoice* core = CORE_IN(hw); + + return coreaudio_voice_ctl(core, cmd); +} + +static void* +coreaudio_audio_init (void) { atexit(coreaudio_atexit); return &coreaudio_audio_init; } -static void coreaudio_audio_fini (void *opaque) +static void +coreaudio_audio_fini (void *opaque) { (void) opaque; } static struct audio_option coreaudio_options[] = { - {"BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_frames, - "Size of the buffer in frames", NULL, 0}, - {"BUFFER_COUNT", AUD_OPT_INT, &conf.nbuffers, - "Number of buffers", NULL, 0}, + {"OUT_BUFFER_SIZE", AUD_OPT_INT, &conf.out_buffer_frames, + "Size of the output buffer in frames", NULL, 0}, + {"OUT_BUFFER_COUNT", AUD_OPT_INT, &conf.out_nbuffers, + "Number of output buffers", NULL, 0}, + {"IN_BUFFER_SIZE", AUD_OPT_INT, &conf.in_buffer_frames, + "Size of the input buffer in frames", NULL, 0}, + {"IN_BUFFER_COUNT", AUD_OPT_INT, &conf.in_nbuffers, + "Number of input buffers", NULL, 0}, {NULL, 0, NULL, NULL, NULL, 0} }; @@ -531,24 +784,39 @@ static struct audio_pcm_ops coreaudio_pcm_ops = { coreaudio_write, coreaudio_ctl_out, +#if ENABLE_IN + coreaudio_init_in, + coreaudio_fini_in, + coreaudio_run_in, + coreaudio_read, + coreaudio_ctl_in +#else NULL, NULL, NULL, NULL, NULL +#endif }; struct audio_driver coreaudio_audio_driver = { INIT_FIELD (name = ) "coreaudio", INIT_FIELD (descr = ) - "CoreAudio http://developer.apple.com/audio/coreaudio.html", + "CoreAudio (developer.apple.com/audio/coreaudio.html)", INIT_FIELD (options = ) coreaudio_options, INIT_FIELD (init = ) coreaudio_audio_init, INIT_FIELD (fini = ) coreaudio_audio_fini, INIT_FIELD (pcm_ops = ) &coreaudio_pcm_ops, INIT_FIELD (can_be_default = ) 1, +#if ENABLE_IN + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (coreaudioVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (coreaudioVoiceIn), +#else INIT_FIELD (max_voices_out = ) 1, INIT_FIELD (max_voices_in = ) 0, INIT_FIELD (voice_size_out = ) sizeof (coreaudioVoiceOut), - INIT_FIELD (voice_size_in = ) 0 + INIT_FIELD (voice_size_in = ) 0, +#endif }; diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c index 90a0333..6104acb 100644 --- a/audio/dsoundaudio.c +++ b/audio/dsoundaudio.c @@ -1067,7 +1067,7 @@ static struct audio_pcm_ops dsound_pcm_ops = { struct audio_driver dsound_audio_driver = { INIT_FIELD (name = ) "dsound", INIT_FIELD (descr = ) - "DirectSound http://wikipedia.org/wiki/DirectSound", + "DirectSound audio (www.wikipedia.org/wiki/DirectSound)", INIT_FIELD (options = ) dsound_options, INIT_FIELD (init = ) dsound_audio_init, INIT_FIELD (fini = ) dsound_audio_fini, diff --git a/audio/esdaudio.c b/audio/esdaudio.c new file mode 100644 index 0000000..9aeb8a5 --- /dev/null +++ b/audio/esdaudio.c @@ -0,0 +1,692 @@ +/* + * QEMU ESD audio driver + * + * Copyright (c) 2008 The Android Open Source Project + * Copyright (c) 2006 Frederick Reeve (brushed up by malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include "vl.h" +#include "audio.h" +#include + +#define AUDIO_CAP "esd" +#include "audio_int.h" +#include "audio_pt_int.h" +#include + +#include "android_debug.h" + +#define DEBUG 1 + +#if DEBUG +# include +# define D(...) VERBOSE_PRINT(audio,__VA_ARGS__) +# define D_ACTIVE VERBOSE_CHECK(audio) +# define O(...) VERBOSE_PRINT(audioout,__VA_ARGS__) +# define I(...) VERBOSE_PRINT(audioin,__VA_ARGS__) +#else +# define D(...) ((void)0) +# define D_ACTIVE 0 +# define O(...) ((void)0) +# define I(...) ((void)0) +#endif + +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +typedef struct { + HWVoiceOut hw; + int done; + int live; + int decr; + int rpos; + void *pcm_buf; + int fd; + struct audio_pt pt; +} ESDVoiceOut; + +typedef struct { + HWVoiceIn hw; + int done; + int dead; + int incr; + int wpos; + void *pcm_buf; + int fd; + struct audio_pt pt; +} ESDVoiceIn; + +static struct { + int samples; + int divisor; + char *dac_host; + char *adc_host; +} conf = { + 1024, + 2, + NULL, + NULL +}; + +/* link dynamically to the libesd.so */ + +#define ESD_SYMBOLS \ + ESD_FUNCTION(int,esd_play_stream,(esd_format_t,int,const char*,const char*)) \ + ESD_FUNCTION(int,esd_record_stream,(esd_format_t,int,const char*,const char*)) \ + ESD_FUNCTION(int,esd_open_sound,( const char *host )) \ + ESD_FUNCTION(int,esd_close,(int)) \ + +/* define pointers to library functions we're going to use */ +#define ESD_FUNCTION(ret,name,sig) \ + static ret (*func_ ## name)sig; + +ESD_SYMBOLS + +#undef ESD_FUNCTION + +static void* esd_lib; + +static void GCC_FMT_ATTR (2, 3) qesd_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +/* playback */ +static void *qesd_thread_out (void *arg) +{ + ESDVoiceOut* esd = arg; + HWVoiceOut* hw = &esd->hw; + int threshold; + sigset_t set; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + /* ignore SIGALRM in this thread */ + sigemptyset(&set); + sigaddset(&set, SIGALRM); + + pthread_sigmask( SIG_BLOCK, &set, NULL); + + O("EsounD output thread starting, threshold is %d samples", threshold); + for (;;) { + int decr, to_mix, rpos; + + for (;;) { + if (esd->done) { + goto exit; + } + + if (esd->live > threshold) { + break; + } + + if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { + O("EsounD output thread aborting on wait error"); + goto exit; + } + } + + decr = to_mix = esd->live; + rpos = hw->rpos; + + if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { + O("EsounD output thread aborting on unlock error"); + return NULL; + } + + while (to_mix) { + ssize_t written; + int chunk = audio_MIN (to_mix, hw->samples - rpos); + st_sample_t *src = hw->mix_buf + rpos; + + hw->clip (esd->pcm_buf, src, chunk); + + again: + written = write (esd->fd, esd->pcm_buf, chunk << hw->info.shift); + if (written == -1) { + if (errno == EINTR || errno == EAGAIN) { + goto again; + } + qesd_logerr (errno, "write failed\n"); + O("EsounD output thread aborting on write error: %s", strerror(errno)); + return NULL; + } + + if (written != chunk << hw->info.shift) { + int wsamples = written >> hw->info.shift; + int wbytes = wsamples << hw->info.shift; + if (wbytes != written) { + dolog ("warning: Misaligned write %d (requested %d), " + "alignment %d\n", + wbytes, written, hw->info.align + 1); + } + to_mix -= wsamples; + rpos = (rpos + wsamples) % hw->samples; + break; + } + + rpos = (rpos + chunk) % hw->samples; + to_mix -= chunk; + } + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + O("EsounD output thread aborting on lock error\n"); + return NULL; + } + + esd->rpos = rpos; + esd->live -= decr; + esd->decr += decr; + } + O("EsounD output thread exiting"); + + exit: + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + return NULL; +} + +static int qesd_run_out (HWVoiceOut *hw) +{ + int live, decr; + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + O("%s: exiting on lock error", __FUNCTION__); + return 0; + } + + live = audio_pcm_hw_get_live_out (hw); + decr = audio_MIN (live, esd->decr); + esd->decr -= decr; + esd->live = live - decr; + hw->rpos = esd->rpos; + if (esd->live > 0) { + O("%s: signaling %d samples\n", esd->live); + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + } + else { + O("."); + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + } + return decr; +} + +static int qesd_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int qesd_init_out (HWVoiceOut *hw, audsettings_t *as) +{ + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + audsettings_t obt_as = *as; + int esdfmt = ESD_STREAM | ESD_PLAY; + int result = -1; + + /* shut down verbose debug spew */ + if (!D_ACTIVE) + stdio_disable(); + + O("initializing EsoundD audio output"); + esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + esdfmt |= ESD_BITS8; + obt_as.fmt = AUD_FMT_U8; + break; +#if 0 + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("Will use 16 instead of 32 bit samples\n"); +#endif + case AUD_FMT_S16: + case AUD_FMT_U16: + deffmt: + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", as->fmt); +#ifdef DEBUG_FMOD + abort (); +#endif + goto deffmt; + + } + obt_as.endianness = 0; + + audio_pcm_init_info (&hw->info, &obt_as); + + hw->samples = conf.samples; + esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!esd->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto exit; + } + + esd->fd = func_esd_play_stream (esdfmt, as->freq, conf.dac_host, NULL); + if (esd->fd < 0) { + if (conf.dac_host == NULL) { + esd->fd = func_esd_play_stream (esdfmt, as->freq, "localhost", NULL); + } + if (esd->fd < 0) { + qesd_logerr (errno, "esd_play_stream failed\n"); + goto fail2; + } + } + + if (audio_pt_init (&esd->pt, qesd_thread_out, esd, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + result = 0; /* success */ + goto exit; + + fail3: + if (close (esd->fd)) { + qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", + AUDIO_FUNC, esd->fd); + } + esd->fd = -1; + + fail2: + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; + + exit: + if (!D_ACTIVE) + stdio_enable(); + + return result; +} + +static void qesd_fini_out (HWVoiceOut *hw) +{ + void *ret; + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + + audio_pt_lock (&esd->pt, AUDIO_FUNC); + esd->done = 1; + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); + + if (esd->fd >= 0) { + if (close (esd->fd)) { + qesd_logerr (errno, "failed to close esd socket\n"); + } + esd->fd = -1; + } + + audio_pt_fini (&esd->pt, AUDIO_FUNC); + + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; +} + +static int qesd_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* capture */ +static void *qesd_thread_in (void *arg) +{ + ESDVoiceIn *esd = arg; + HWVoiceIn *hw = &esd->hw; + int threshold; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + for (;;) { + int incr, to_grab, wpos; + + for (;;) { + if (esd->done) { + goto exit; + } + + if (esd->dead > threshold) { + break; + } + + if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { + goto exit; + } + } + + incr = to_grab = esd->dead; + wpos = hw->wpos; + + if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_grab) { + ssize_t nread; + int chunk = audio_MIN (to_grab, hw->samples - wpos); + void *buf = advance (esd->pcm_buf, wpos); + + again: + nread = read (esd->fd, buf, chunk << hw->info.shift); + if (nread == -1) { + if (errno == EINTR || errno == EAGAIN) { + goto again; + } + qesd_logerr (errno, "read failed\n"); + return NULL; + } + + if (nread != chunk << hw->info.shift) { + int rsamples = nread >> hw->info.shift; + int rbytes = rsamples << hw->info.shift; + if (rbytes != nread) { + dolog ("warning: Misaligned write %d (requested %d), " + "alignment %d\n", + rbytes, nread, hw->info.align + 1); + } + to_grab -= rsamples; + wpos = (wpos + rsamples) % hw->samples; + break; + } + + hw->conv (hw->conv_buf + wpos, buf, nread >> hw->info.shift, + &nominal_volume); + wpos = (wpos + chunk) % hw->samples; + to_grab -= chunk; + } + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + esd->wpos = wpos; + esd->dead -= incr; + esd->incr += incr; + } + + exit: + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + return NULL; +} + +static int qesd_run_in (HWVoiceIn *hw) +{ + int live, incr, dead; + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return 0; + } + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + incr = audio_MIN (dead, esd->incr); + esd->incr -= incr; + esd->dead = dead - incr; + hw->wpos = esd->wpos; + if (esd->dead > 0) { + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + } + return incr; +} + +static int qesd_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int qesd_init_in (HWVoiceIn *hw, audsettings_t *as) +{ + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + audsettings_t obt_as = *as; + int esdfmt = ESD_STREAM | ESD_RECORD; + int result = -1; + + /* shut down verbose debug spew */ + if (!D_ACTIVE) + stdio_disable(); + + esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + esdfmt |= ESD_BITS8; + obt_as.fmt = AUD_FMT_U8; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; +#if 0 + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("Will use 16 instead of 32 bit samples\n"); + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; +#endif + } + obt_as.endianness = 0; + + audio_pcm_init_info (&hw->info, &obt_as); + + hw->samples = conf.samples; + esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!esd->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto exit; + } + + esd->fd = func_esd_record_stream (esdfmt, as->freq, conf.adc_host, NULL); + if (esd->fd < 0) { + if (conf.adc_host == NULL) { + esd->fd = func_esd_record_stream (esdfmt, as->freq, "localhost", NULL); + } + if (esd->fd < 0) { + qesd_logerr (errno, "esd_record_stream failed\n"); + goto fail2; + } + } + + if (audio_pt_init (&esd->pt, qesd_thread_in, esd, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + result = 0; /* success */ + goto exit; + + fail3: + if (close (esd->fd)) { + qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", + AUDIO_FUNC, esd->fd); + } + esd->fd = -1; + + fail2: + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; + + exit: + if (!D_ACTIVE) + stdio_enable(); + + return result; +} + +static void qesd_fini_in (HWVoiceIn *hw) +{ + void *ret; + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + + audio_pt_lock (&esd->pt, AUDIO_FUNC); + esd->done = 1; + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); + + if (esd->fd >= 0) { + if (close (esd->fd)) { + qesd_logerr (errno, "failed to close esd socket\n"); + } + esd->fd = -1; + } + + audio_pt_fini (&esd->pt, AUDIO_FUNC); + + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; +} + +static int qesd_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* common */ +static void *qesd_audio_init (void) +{ + void* result = NULL; + + D("%s: entering", __FUNCTION__); + + if (esd_lib == NULL) { + int fd; + + esd_lib = dlopen( "libesd.so", RTLD_NOW ); + if (esd_lib == NULL) + esd_lib = dlopen( "libesd.so.0", RTLD_NOW ); + + if (esd_lib == NULL) { + D("could not find libesd on this system"); + goto Exit; + } + + #undef ESD_FUNCTION + #define ESD_FUNCTION(ret,name,sig) \ + do { \ + (func_ ##name) = dlsym( esd_lib, STRINGIFY(name) ); \ + if ((func_##name) == NULL) { \ + D("could not find %s in libesd\n", STRINGIFY(name)); \ + goto Fail; \ + } \ + } while (0); + + ESD_SYMBOLS + + fd = func_esd_open_sound(conf.dac_host); + if (fd < 0) { + D("%s: could not open direct sound server connection, trying localhost", + __FUNCTION__); + fd = func_esd_open_sound("localhost"); + if (fd < 0) { + D("%s: could not open localhost sound server connection", __FUNCTION__); + goto Fail; + } + } + + D("%s: EsounD server connection succeeded", __FUNCTION__); + /* func_esd_close(fd); */ + } + result = &conf; + goto Exit; + +Fail: + D("%s: failed to open library", __FUNCTION__); + dlclose(esd_lib); + esd_lib = NULL; + +Exit: + return result; +} + +static void qesd_audio_fini (void *opaque) +{ + (void) opaque; + if (esd_lib != NULL) { + dlclose(esd_lib); + esd_lib = NULL; + } + ldebug ("esd_fini"); +} + +struct audio_option qesd_options[] = { + {"SAMPLES", AUD_OPT_INT, &conf.samples, + "buffer size in samples", NULL, 0}, + + {"DIVISOR", AUD_OPT_INT, &conf.divisor, + "threshold divisor", NULL, 0}, + + {"DAC_HOST", AUD_OPT_STR, &conf.dac_host, + "playback host", NULL, 0}, + + {"ADC_HOST", AUD_OPT_STR, &conf.adc_host, + "capture host", NULL, 0}, + + {NULL, 0, NULL, NULL, NULL, 0} +}; + +struct audio_pcm_ops qesd_pcm_ops = { + qesd_init_out, + qesd_fini_out, + qesd_run_out, + qesd_write, + qesd_ctl_out, + + qesd_init_in, + qesd_fini_in, + qesd_run_in, + qesd_read, + qesd_ctl_in, +}; + +struct audio_driver esd_audio_driver = { + INIT_FIELD (name = ) "esd", + INIT_FIELD (descr = ) + "EsounD audio (en.wikipedia.org/wiki/Esound)", + INIT_FIELD (options = ) qesd_options, + INIT_FIELD (init = ) qesd_audio_init, + INIT_FIELD (fini = ) qesd_audio_fini, + INIT_FIELD (pcm_ops = ) &qesd_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (ESDVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (ESDVoiceIn) +}; diff --git a/audio/noaudio.c b/audio/noaudio.c index a3423e5..3567e9c 100644 --- a/audio/noaudio.c +++ b/audio/noaudio.c @@ -159,7 +159,7 @@ static struct audio_pcm_ops no_pcm_ops = { struct audio_driver no_audio_driver = { INIT_FIELD (name = ) "none", - INIT_FIELD (descr = ) "Timer based audio emulation", + INIT_FIELD (descr = ) "disabled audio", INIT_FIELD (options = ) NULL, INIT_FIELD (init = ) no_audio_init, INIT_FIELD (fini = ) no_audio_fini, diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 125e4c8..4d36e45 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -755,7 +755,7 @@ static struct audio_pcm_ops oss_pcm_ops = { struct audio_driver oss_audio_driver = { INIT_FIELD (name = ) "oss", - INIT_FIELD (descr = ) "OSS http://www.opensound.com", + INIT_FIELD (descr = ) "OSS audio (www.opensound.com)", INIT_FIELD (options = ) oss_options, INIT_FIELD (init = ) oss_audio_init, INIT_FIELD (fini = ) oss_audio_fini, diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index f2a6896..83469ac 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -28,12 +28,17 @@ #define AUDIO_CAP "sdl" #include "audio_int.h" -typedef struct SDLVoiceOut { - HWVoiceOut hw; - int live; - int rpos; - int decr; -} SDLVoiceOut; +/* define DEBUG to 1 to dump audio debugging info at runtime to stderr */ +#define DEBUG 0 + +/* define NEW_AUDIO to 1 to activate the new audio thread callback */ +#define NEW_AUDIO 1 + +#if DEBUG +# define D(...) fprintf(stderr, __VA_ARGS__) +#else +# define D(...) ((void)0) +#endif static struct { int nb_samples; @@ -41,12 +46,41 @@ static struct { 1024 }; +#if DEBUG +int64_t start_time; +#endif + +#if NEW_AUDIO + +#define AUDIO_BUFFER_SIZE (8192) + +typedef HWVoiceOut SDLVoiceOut; + +struct SDLAudioState { + int exit; + SDL_mutex* mutex; + int initialized; + uint8_t data[ AUDIO_BUFFER_SIZE ]; + int pos, count; +} glob_sdl; +#else /* !NEW_AUDIO */ + +typedef struct SDLVoiceOut { + HWVoiceOut hw; + int live; + int rpos; + int decr; +} SDLVoiceOut; + struct SDLAudioState { int exit; SDL_mutex *mutex; SDL_sem *sem; int initialized; } glob_sdl; + +#endif /* !NEW_AUDIO */ + typedef struct SDLAudioState SDLAudioState; static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) @@ -78,6 +112,7 @@ static int sdl_unlock (SDLAudioState *s, const char *forfn) return 0; } +#if !NEW_AUDIO static int sdl_post (SDLAudioState *s, const char *forfn) { if (SDL_SemPost (s->sem)) { @@ -104,6 +139,7 @@ static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) return sdl_post (s, forfn); } +#endif static int aud_to_sdlfmt (audfmt_e fmt, int *shift) { @@ -190,13 +226,62 @@ static void sdl_close (SDLAudioState *s) if (s->initialized) { sdl_lock (s, "sdl_close"); s->exit = 1; +#if NEW_AUDIO + sdl_unlock (s, "sdl_close"); +#else sdl_unlock_and_post (s, "sdl_close"); +#endif SDL_PauseAudio (1); SDL_CloseAudio (); s->initialized = 0; } } +#if NEW_AUDIO + +static void sdl_callback (void *opaque, Uint8 *buf, int len) +{ +#if DEBUG + int64_t now; +#endif + SDLAudioState *s = &glob_sdl; + + if (s->exit) { + return; + } + + sdl_lock (s, "sdl_callback"); +#if DEBUG + if (s->count > 0) { + now = qemu_get_clock(vm_clock); + if (start_time == 0) + start_time = now; + now = now - start_time; + D( "R %6.3f: pos:%5d count:%5d len:%5d\n", now/1e9, s->pos, s->count, len ); + } +#endif + while (len > 0) { + int avail = audio_MIN( AUDIO_BUFFER_SIZE - s->pos, s->count ); + + if (avail == 0) + break; + + if (avail > len) + avail = len; + + memcpy( buf, s->data + s->pos, avail ); + buf += avail; + len -= avail; + + s->count -= avail; + s->pos += avail; + if (s->pos == AUDIO_BUFFER_SIZE) + s->pos = 0; + } + sdl_unlock (s, "sdl_callback"); +} + +#else /* !NEW_AUDIO */ static void sdl_callback (void *opaque, Uint8 *buf, int len) { SDLVoiceOut *sdl = opaque; @@ -255,12 +340,69 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len) } /* dolog ("done len=%d\n", len); */ } +#endif /* !NEW_AUDIO */ static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) { return audio_pcm_sw_write (sw, buf, len); } +#if NEW_AUDIO + +static int sdl_run_out (HWVoiceOut *hw) +{ + SDLAudioState *s = &glob_sdl; + int live, avail, end, total; + + if (sdl_lock (s, "sdl_run_out")) { + return 0; + } + avail = AUDIO_BUFFER_SIZE - s->count; + end = s->pos + s->count; + if (end >= AUDIO_BUFFER_SIZE) + end -= AUDIO_BUFFER_SIZE; + sdl_unlock (s, "sdl_run_out"); + + live = audio_pcm_hw_get_live_out (hw); + + total = 0; + while (live > 0) { + int bytes = audio_MIN(AUDIO_BUFFER_SIZE - end, avail); + int samples = bytes >> hw->info.shift; + int hwsamples = audio_MIN(hw->samples - hw->rpos, live); + uint8_t* dst = s->data + end; + st_sample_t* src = hw->mix_buf + hw->rpos; + + if (samples == 0) + break; + + if (samples > hwsamples) { + samples = hwsamples; + bytes = hwsamples << hw->info.shift; + } + + hw->clip (dst, src, samples); + hw->rpos += samples; + if (hw->rpos == hw->samples) + hw->rpos = 0; + + live -= samples; + avail -= bytes; + end += bytes; + if (end == AUDIO_BUFFER_SIZE) + end = 0; + + total += bytes; + } + + sdl_lock (s, "sdl_run_out"); + s->count += total; + sdl_unlock (s, "sdl_run_out"); + + return total >> hw->info.shift; +} + +#else /* !NEW_AUDIO */ static int sdl_run_out (HWVoiceOut *hw) { int decr, live; @@ -294,6 +436,7 @@ static int sdl_run_out (HWVoiceOut *hw) } return decr; } +#endif /* !NEW_AUDIO */ static void sdl_fini_out (HWVoiceOut *hw) { @@ -302,6 +445,54 @@ static void sdl_fini_out (HWVoiceOut *hw) sdl_close (&glob_sdl); } +#if DEBUG + +typedef struct { int value; const char* name; } MatchRec; +typedef const MatchRec* Match; + +static const char* +match_find( Match matches, int value, char* temp ) +{ + int nn; + for ( nn = 0; matches[nn].name != NULL; nn++ ) { + if ( matches[nn].value == value ) + return matches[nn].name; + } + sprintf( temp, "(%d?)", value ); + return temp; +} + +static const MatchRec sdl_audio_format_matches[] = { + { AUDIO_U8, "AUDIO_U8" }, + { AUDIO_S8, "AUDIO_S8" }, + { AUDIO_U16, "AUDIO_U16LE" }, + { AUDIO_S16, "AUDIO_S16LE" }, + { AUDIO_U16MSB, "AUDIO_U16BE" }, + { AUDIO_S16MSB, "AUDIO_S16BE" }, + { 0, NULL } +}; + +static void +print_sdl_audiospec( SDL_AudioSpec* spec, const char* prefix ) +{ + char temp[64]; + const char* fmt; + + if (!prefix) + prefix = ""; + + printf( "%s audiospec [freq:%d format:%s channels:%d samples:%d bytes:%d", + prefix, + spec->freq, + match_find( sdl_audio_format_matches, spec->format, temp ), + spec->channels, + spec->samples, + spec->size + ); + printf( "]\n" ); +} +#endif + static int sdl_init_out (HWVoiceOut *hw, audsettings_t *as) { SDLVoiceOut *sdl = (SDLVoiceOut *) hw; @@ -322,26 +513,38 @@ static int sdl_init_out (HWVoiceOut *hw, audsettings_t *as) req.callback = sdl_callback; req.userdata = sdl; +#if DEBUG + print_sdl_audiospec( &req, "wanted" ); +#endif + if (sdl_open (&req, &obt)) { return -1; } +#if DEBUG + print_sdl_audiospec( &req, "obtained" ); +#endif + err = sdl_to_audfmt (obt.format, &effective_fmt, &endianess); if (err) { sdl_close (s); return -1; } - obt_as.freq = obt.freq; - obt_as.nchannels = obt.channels; - obt_as.fmt = effective_fmt; + obt_as.freq = obt.freq; + obt_as.nchannels = obt.channels; + obt_as.fmt = effective_fmt; obt_as.endianness = endianess; audio_pcm_init_info (&hw->info, &obt_as); hw->samples = obt.samples; +#if DEBUG + start_time = qemu_get_clock(vm_clock); +#endif + s->initialized = 1; - s->exit = 0; + s->exit = 0; SDL_PauseAudio (0); return 0; } @@ -377,7 +580,7 @@ static void *sdl_audio_init (void) SDL_QuitSubSystem (SDL_INIT_AUDIO); return NULL; } - +#if !NEW_AUDIO s->sem = SDL_CreateSemaphore (0); if (!s->sem) { sdl_logerr ("Failed to create SDL semaphore\n"); @@ -385,7 +588,7 @@ static void *sdl_audio_init (void) SDL_QuitSubSystem (SDL_INIT_AUDIO); return NULL; } - +#endif return s; } @@ -393,8 +596,16 @@ static void sdl_audio_fini (void *opaque) { SDLAudioState *s = opaque; sdl_close (s); - SDL_DestroySemaphore (s->sem); - SDL_DestroyMutex (s->mutex); +#if !NEW_AUDIO + if (s->sem) { + SDL_DestroySemaphore (s->sem); + s->sem = NULL; + } +#endif + if (s->mutex) { + SDL_DestroyMutex (s->mutex); + s->mutex = NULL; + } SDL_QuitSubSystem (SDL_INIT_AUDIO); } @@ -420,7 +631,7 @@ static struct audio_pcm_ops sdl_pcm_ops = { struct audio_driver sdl_audio_driver = { INIT_FIELD (name = ) "sdl", - INIT_FIELD (descr = ) "SDL http://www.libsdl.org", + INIT_FIELD (descr = ) "SDL audio (www.libsdl.org)", INIT_FIELD (options = ) sdl_options, INIT_FIELD (init = ) sdl_audio_init, INIT_FIELD (fini = ) sdl_audio_fini, diff --git a/audio/wavaudio.c b/audio/wavaudio.c index c359fc4..7d4fed1 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -1,6 +1,7 @@ /* * QEMU WAV audio driver * + * Copyright (c) 2007 The Android Open Source Project * Copyright (c) 2004-2005 Vassili Karpov (malc) * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -26,6 +27,10 @@ #define AUDIO_CAP "wav" #include "audio_int.h" +#define WAV_AUDIO_IN 1 + +/** VOICE OUT (Saving to a .WAV file) + **/ typedef struct WAVVoiceOut { HWVoiceOut hw; QEMUFile *f; @@ -37,7 +42,7 @@ typedef struct WAVVoiceOut { static struct { audsettings_t settings; const char *wav_path; -} conf = { +} conf_out = { { 44100, 2, @@ -46,7 +51,7 @@ static struct { "qemu.wav" }; -static int wav_run_out (HWVoiceOut *hw) +static int wav_out_run (HWVoiceOut *hw) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; int rpos, live, decr, samples; @@ -91,7 +96,7 @@ static int wav_run_out (HWVoiceOut *hw) return decr; } -static int wav_write_out (SWVoiceOut *sw, void *buf, int len) +static int wav_out_write (SWVoiceOut *sw, void *buf, int len) { return audio_pcm_sw_write (sw, buf, len); } @@ -106,7 +111,7 @@ static void le_store (uint8_t *buf, uint32_t val, int len) } } -static int wav_init_out (HWVoiceOut *hw, audsettings_t *as) +static int wav_out_init (HWVoiceOut *hw, audsettings_t *as) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; int bits16 = 0, stereo = 0; @@ -116,7 +121,7 @@ static int wav_init_out (HWVoiceOut *hw, audsettings_t *as) 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 }; - audsettings_t wav_as = conf.settings; + audsettings_t wav_as = conf_out.settings; (void) as; @@ -151,10 +156,10 @@ static int wav_init_out (HWVoiceOut *hw, audsettings_t *as) le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4); le_store (hdr + 32, 1 << (bits16 + stereo), 2); - wav->f = fopen (conf.wav_path, "wb"); + wav->f = fopen (conf_out.wav_path, "wb"); if (!wav->f) { dolog ("Failed to open wave file `%s'\nReason: %s\n", - conf.wav_path, strerror (errno)); + conf_out.wav_path, strerror (errno)); qemu_free (wav->pcm_buf); wav->pcm_buf = NULL; return -1; @@ -164,7 +169,7 @@ static int wav_init_out (HWVoiceOut *hw, audsettings_t *as) return 0; } -static void wav_fini_out (HWVoiceOut *hw) +static void wav_out_fini (HWVoiceOut *hw) { WAVVoiceOut *wav = (WAVVoiceOut *) hw; uint8_t rlen[4]; @@ -192,16 +197,212 @@ static void wav_fini_out (HWVoiceOut *hw) wav->pcm_buf = NULL; } -static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) +static int wav_out_ctl (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + + +#if WAV_AUDIO_IN + +/** WAV IN (Reading from a .WAV file) + **/ + + static struct { + const char *wav_path; +} conf_in = { + "qemu.wav" +}; + +typedef struct WAVVoiceIn { + HWVoiceIn hw; + QEMUFile* f; + int64_t old_ticks; + void* pcm_buf; + int total_samples; + int total_size; +} WAVVoiceIn; + + +static int +le_read( const uint8_t* p, int size ) { + int shift = 0; + int result = 0; + for ( ; size > 0; size-- ) { + result = result | (p[0] << shift); + p += 1; + shift += 8; + } + return result; +} + +static int +wav_in_init (HWVoiceIn *hw, audsettings_t *as) +{ + WAVVoiceIn* wav = (WAVVoiceIn *) hw; + const char* path = conf_in.wav_path; + uint8_t hdr[44]; + audsettings_t wav_as = *as; + int nchannels, freq, format, bits; + + wav->f = fopen (path, "rb"); + if (wav->f == NULL) { + dolog("Failed to open wave file '%s'\nReason: %s\n", path, + strerror(errno)); + return -1; + } + + if (qemu_get_buffer (wav->f, hdr, sizeof(hdr)) != (int)sizeof(hdr)) { + dolog("File '%s' to be a .wav file\n", path); + goto Fail; + } + + /* check that this is a wave file */ + if ( hdr[0] != 'R' || hdr[1] != 'I' || hdr[2] != 'F' || hdr[3] != 'F' || + hdr[8] != 'W' || hdr[9] != 'A' || hdr[10]!= 'V' || hdr[11]!= 'E' || + hdr[12]!= 'f' || hdr[13]!= 'm' || hdr[14]!= 't' || hdr[15]!= ' ' || + hdr[40]!= 'd' || hdr[41]!= 'a' || hdr[42]!= 't' || hdr[43]!= 'a') { + dolog("File '%s' is not a valid .wav file\n", path); + goto Fail; + } + + nchannels = le_read( hdr+22, 2 ); + freq = le_read( hdr+24, 4 ); + format = le_read( hdr+32, 2 ); + bits = le_read( hdr+34, 2 ); + + wav->total_size = le_read( hdr+40, 4 ); + + /* perform some sainty checks */ + switch (nchannels) { + case 1: + case 2: break; + default: + dolog("unsupported number of channels (%d) in '%s'\n", + nchannels, path); + goto Fail; + } + + switch (format) { + case 1: + case 2: + case 4: break; + default: + dolog("unsupported bytes per sample (%d) in '%s'\n", + format, path); + goto Fail; + } + + if (format*8/nchannels != bits) { + dolog("invalid bits per sample (%d, expected %d) in '%s'\n", + bits, format*8/nchannels, path); + goto Fail; + } + + wav_as.nchannels = nchannels; + wav_as.fmt = (bits == 8) ? AUD_FMT_U8 : AUD_FMT_S16; + wav_as.freq = freq; + wav_as.endianness = 0; /* always little endian */ + + audio_pcm_init_info (&hw->info, &wav_as); + + hw->samples = 1024; + wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!wav->pcm_buf) { + goto Fail; + } + return 0; + +Fail: + fclose (wav->f); + wav->f = NULL; + return -1; +} + + +static void wav_in_fini (HWVoiceIn *hw) +{ + WAVVoiceIn *wav = (WAVVoiceIn *) hw; + + if (!wav->f) { + return; + } + + fclose (wav->f); + wav->f = NULL; + + qemu_free (wav->pcm_buf); + wav->pcm_buf = NULL; +} + +static int wav_in_run (HWVoiceIn *hw) +{ + WAVVoiceIn* wav = (WAVVoiceIn *) hw; + int wpos, live, decr, samples; + uint8_t* src; + st_sample_t* dst; + + int64_t now = qemu_get_clock (vm_clock); + int64_t ticks = now - wav->old_ticks; + int64_t bytes = (ticks * hw->info.bytes_per_second) / ticks_per_sec; + + if (bytes > INT_MAX) { + samples = INT_MAX >> hw->info.shift; + } + else { + samples = bytes >> hw->info.shift; + } + + live = audio_pcm_hw_get_live_in (hw); + if (!live) { + return 0; + } + + wav->old_ticks = now; + + decr = audio_MIN (live, samples); + samples = decr; + wpos = hw->wpos; + while (samples) { + int left_till_end_samples = hw->samples - wpos; + int convert_samples = audio_MIN (samples, left_till_end_samples); + + dst = hw->conv_buf + wpos; + src = advance (wav->pcm_buf, wpos << hw->info.shift); + + qemu_get_buffer (wav->f, src, convert_samples << hw->info.shift); + memcpy (dst, src, convert_samples << hw->info.shift); + + wpos = (wpos + convert_samples) % hw->samples; + samples -= convert_samples; + wav->total_samples += convert_samples; + } + + hw->wpos = wpos; + return decr; +} + +static int wav_in_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int wav_in_ctl (HWVoiceIn *hw, int cmd, ...) { (void) hw; (void) cmd; return 0; } +#endif /* WAV_AUDIO_IN */ + +/** COMMON CODE + **/ static void *wav_audio_init (void) { - return &conf; + return &conf_out; } static void wav_audio_fini (void *opaque) @@ -211,45 +412,65 @@ static void wav_audio_fini (void *opaque) } struct audio_option wav_options[] = { - {"FREQUENCY", AUD_OPT_INT, &conf.settings.freq, + {"FREQUENCY", AUD_OPT_INT, &conf_out.settings.freq, "Frequency", NULL, 0}, - {"FORMAT", AUD_OPT_FMT, &conf.settings.fmt, + {"FORMAT", AUD_OPT_FMT, &conf_out.settings.fmt, "Format", NULL, 0}, - {"DAC_FIXED_CHANNELS", AUD_OPT_INT, &conf.settings.nchannels, + {"DAC_FIXED_CHANNELS", AUD_OPT_INT, &conf_out.settings.nchannels, "Number of channels (1 - mono, 2 - stereo)", NULL, 0}, - {"PATH", AUD_OPT_STR, &conf.wav_path, - "Path to wave file", NULL, 0}, + {"PATH", AUD_OPT_STR, &conf_out.wav_path, + "Path to output .wav file", NULL, 0}, + +#if WAV_AUDIO_IN + {"IN_PATH", AUD_OPT_STR, &conf_in.wav_path, + "Path to input .wav file", NULL, 0}, +#endif {NULL, 0, NULL, NULL, NULL, 0} }; struct audio_pcm_ops wav_pcm_ops = { - wav_init_out, - wav_fini_out, - wav_run_out, - wav_write_out, - wav_ctl_out, - + wav_out_init, + wav_out_fini, + wav_out_run, + wav_out_write, + wav_out_ctl, + +#if WAV_AUDIO_IN + wav_in_init, + wav_in_fini, + wav_in_run, + wav_in_read, + wav_in_ctl +#else NULL, NULL, NULL, NULL, NULL +#endif }; struct audio_driver wav_audio_driver = { INIT_FIELD (name = ) "wav", INIT_FIELD (descr = ) - "WAV renderer http://wikipedia.org/wiki/WAV", + "WAV file read/write (www.wikipedia.org/wiki/WAV)", INIT_FIELD (options = ) wav_options, INIT_FIELD (init = ) wav_audio_init, INIT_FIELD (fini = ) wav_audio_fini, INIT_FIELD (pcm_ops = ) &wav_pcm_ops, INIT_FIELD (can_be_default = ) 0, +#if WAV_AUDIO_IN + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (WAVVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (WAVVoiceIn) +#else INIT_FIELD (max_voices_out = ) 1, INIT_FIELD (max_voices_in = ) 0, INIT_FIELD (voice_size_out = ) sizeof (WAVVoiceOut), INIT_FIELD (voice_size_in = ) 0 +#endif }; diff --git a/audio/wavcapture.c b/audio/wavcapture.c index 0f6f7bf..748b580 100644 --- a/audio/wavcapture.c +++ b/audio/wavcapture.c @@ -103,8 +103,7 @@ int wav_start_capture (CaptureState *s, const char *path, int freq, } if (nchannels != 1 && nchannels != 2) { - term_printf ("incorrect channel count %d, must be 1 or 2\n", - nchannels); + term_printf ("incorrect channel count %d, must be 1 or 2\n", bits); return -1; } @@ -122,8 +121,7 @@ int wav_start_capture (CaptureState *s, const char *path, int freq, wav = qemu_mallocz (sizeof (*wav)); if (!wav) { - term_printf ("Could not allocate memory for wav capture (%zu bytes)", - sizeof (*wav)); + AUD_log ("wav", "Could not allocate memory (%zu bytes)", sizeof (*wav)); return -1; } diff --git a/audio/winaudio.c b/audio/winaudio.c new file mode 100644 index 0000000..63cd351 --- /dev/null +++ b/audio/winaudio.c @@ -0,0 +1,668 @@ +/* + * QEMU "simple" Windows audio driver + * + * Copyright (c) 2007 The Android Open Source Project + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "vl.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include + +#define AUDIO_CAP "winaudio" +#include "audio_int.h" + +/* define DEBUG to 1 to dump audio debugging info at runtime to stderr */ +#define DEBUG 0 + +#if 1 +# define D_ACTIVE 1 +#else +# define D_ACTIVE DEBUG +#endif + +#if DEBUG +# define D(...) do{ if (D_ACTIVE) printf(__VA_ARGS__); } while(0) +#else +# define D(...) ((void)0) +#endif + +static struct { + int nb_samples; +} conf = { + 1024 +}; + +#if DEBUG +int64_t start_time; +int64_t last_time; +#endif + +#define NUM_OUT_BUFFERS 8 /* must be at least 2 */ + +/** COMMON UTILITIES + **/ + +#if DEBUG +static void +dump_mmerror( const char* func, MMRESULT error ) +{ + const char* reason = NULL; + + fprintf(stderr, "%s returned error: ", func); + switch (error) { + case MMSYSERR_ALLOCATED: reason="specified resource is already allocated"; break; + case MMSYSERR_BADDEVICEID: reason="bad device id"; break; + case MMSYSERR_NODRIVER: reason="no driver is present"; break; + case MMSYSERR_NOMEM: reason="unable to allocate or lock memory"; break; + case WAVERR_BADFORMAT: reason="unsupported waveform-audio format"; break; + case WAVERR_SYNC: reason="device is synchronous"; break; + default: + fprintf(stderr, "unknown(%d)\n", error); + } + if (reason) + fprintf(stderr, "%s\n", reason); +} +#else +# define dump_mmerror(func,error) ((void)0) +#endif + + +/** AUDIO OUT + **/ + +typedef struct WinAudioOut { + HWVoiceOut hw; + HWAVEOUT waveout; + int silence; + CRITICAL_SECTION lock; + unsigned char* buffer_bytes; + WAVEHDR buffers[ NUM_OUT_BUFFERS ]; + int write_index; /* starting first writable buffer */ + int write_count; /* available writable buffers count */ + int write_pos; /* position in current writable buffer */ + int write_size; /* size in bytes of each buffer */ +} WinAudioOut; + +/* The Win32 callback that is called when a buffer has finished playing */ +static void CALLBACK +winaudio_out_buffer_done (HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, + DWORD dwParam1, DWORD dwParam2) +{ + WinAudioOut* s = (WinAudioOut*) dwInstance; + + /* Only service "buffer done playing" messages */ + if ( uMsg != WOM_DONE ) + return; + + /* Signal that we are done playing a buffer */ + EnterCriticalSection( &s->lock ); + if (s->write_count < NUM_OUT_BUFFERS) + s->write_count += 1; + LeaveCriticalSection( &s->lock ); +} + +static int +winaudio_out_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static void +winaudio_out_fini (HWVoiceOut *hw) +{ + WinAudioOut* s = (WinAudioOut*) hw; + int i; + + if (s->waveout) { + waveOutReset(s->waveout); + s->waveout = 0; + } + + for ( i=0; ibuffers[i].dwUser != 0xFFFF ) { + waveOutUnprepareHeader( + s->waveout, &s->buffers[i], sizeof(s->buffers[i]) ); + s->buffers[i].dwUser = 0xFFFF; + } + } + + if (s->buffer_bytes != NULL) { + qemu_free(s->buffer_bytes); + s->buffer_bytes = NULL; + } + + if (s->waveout) { + waveOutClose(s->waveout); + s->waveout = NULL; + } +} + + +static int +winaudio_out_init (HWVoiceOut *hw, audsettings_t *as) +{ + WinAudioOut* s = (WinAudioOut*) hw; + MMRESULT result; + WAVEFORMATEX format; + int shift, i, samples_size; + + s->waveout = NULL; + InitializeCriticalSection( &s->lock ); + for (i = 0; i < NUM_OUT_BUFFERS; i++) { + s->buffers[i].dwUser = 0xFFFF; + } + s->buffer_bytes = NULL; + + /* compute desired wave output format */ + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = as->nchannels; + format.nSamplesPerSec = as->freq; + format.nAvgBytesPerSec = as->freq*as->nchannels; + + s->silence = 0; + + switch (as->fmt) { + case AUD_FMT_S8: shift = 0; break; + case AUD_FMT_U8: shift = 0; s->silence = 0x80; break; + case AUD_FMT_S16: shift = 1; break; + case AUD_FMT_U16: shift = 1; s->silence = 0x8000; break; + default: + fprintf(stderr, "qemu: winaudio: Bad output audio format: %d\n", + as->fmt); + return -1; + } + + format.nAvgBytesPerSec = (format.nSamplesPerSec & format.nChannels) << shift; + format.nBlockAlign = format.nChannels << shift; + format.wBitsPerSample = 8 << shift; + format.cbSize = 0; + + /* open the wave out device */ + result = waveOutOpen( &s->waveout, WAVE_MAPPER, &format, + (DWORD_PTR)winaudio_out_buffer_done, (DWORD_PTR) hw, + CALLBACK_FUNCTION); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror( "qemu: winaudio: waveOutOpen()", result); + return -1; + } + + samples_size = format.nBlockAlign * conf.nb_samples; + s->buffer_bytes = qemu_malloc( NUM_OUT_BUFFERS * samples_size ); + if (s->buffer_bytes == NULL) { + waveOutClose( s->waveout ); + s->waveout = NULL; + fprintf(stderr, "not enough memory for Windows audio buffers\n"); + return -1; + } + + for (i = 0; i < NUM_OUT_BUFFERS; i++) { + memset( &s->buffers[i], 0, sizeof(s->buffers[i]) ); + s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size); + s->buffers[i].dwBufferLength = samples_size; + s->buffers[i].dwFlags = WHDR_DONE; + + result = waveOutPrepareHeader( s->waveout, &s->buffers[i], + sizeof(s->buffers[i]) ); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror("waveOutPrepareHeader()", result); + return -1; + } + } + +#if DEBUG + /* Check the sound device we retrieved */ + { + WAVEOUTCAPS caps; + + result = waveOutGetDevCaps((UINT) s->waveout, &caps, sizeof(caps)); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror("waveOutGetDevCaps()", result); + } else + printf("Audio out device: %s\n", caps.szPname); + } +#endif + + audio_pcm_init_info (&hw->info, as); + hw->samples = conf.nb_samples*2; + + s->write_index = 0; + s->write_count = NUM_OUT_BUFFERS; + s->write_pos = 0; + s->write_size = samples_size; + return 0; +} + + +static int +winaudio_out_run (HWVoiceOut *hw) +{ + WinAudioOut* s = (WinAudioOut*) hw; + int played = 0; + int has_buffer; + int live = audio_pcm_hw_get_live_out (hw); + + if (!live) { + return 0; + } + + EnterCriticalSection( &s->lock ); + has_buffer = (s->write_count > 0); + LeaveCriticalSection( &s->lock ); + + if (has_buffer) { + while (live > 0) { + WAVEHDR* wav_buffer = s->buffers + s->write_index; + int wav_bytes = (s->write_size - s->write_pos); + int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live); + int hw_samples = audio_MIN(hw->samples - hw->rpos, live); + st_sample_t* src = hw->mix_buf + hw->rpos; + uint8_t* dst = (uint8_t*)wav_buffer->lpData + s->write_pos; + + if (wav_samples > hw_samples) { + wav_samples = hw_samples; + } + + wav_bytes = wav_samples << hw->info.shift; + + //D("run_out: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d rpos:%d hwsamples:%d\n", s->write_index, + // s->write_pos, s->write_size, wav_samples, wav_bytes, live, hw->rpos, hw->samples); + hw->clip (dst, src, wav_samples); + hw->rpos += wav_samples; + if (hw->rpos >= hw->samples) + hw->rpos -= hw->samples; + + live -= wav_samples; + played += wav_samples; + s->write_pos += wav_bytes; + if (s->write_pos == s->write_size) { +#if xxDEBUG + int64_t now = qemu_get_clock(vm_clock) - start_time; + int64_t diff = now - last_time; + + D("run_out: (%7.3f:%7d):waveOutWrite buffer:%d\n", + now/1e9, (now-last_time)/1e9, s->write_index); + last_time = now; +#endif + waveOutWrite( s->waveout, wav_buffer, sizeof(*wav_buffer) ); + s->write_pos = 0; + s->write_index += 1; + if (s->write_index == NUM_OUT_BUFFERS) + s->write_index = 0; + + EnterCriticalSection( &s->lock ); + if (--s->write_count == 0) { + live = 0; + } + LeaveCriticalSection( &s->lock ); + } + } + + } + return played; +} + +static int +winaudio_out_ctl (HWVoiceOut *hw, int cmd, ...) +{ + WinAudioOut* s = (WinAudioOut*) hw; + + switch (cmd) { + case VOICE_ENABLE: + waveOutRestart( s->waveout ); + break; + + case VOICE_DISABLE: + waveOutPause( s->waveout ); + break; + } + return 0; +} + +/** AUDIO IN + **/ + +#define NUM_IN_BUFFERS 2 + +typedef struct WinAudioIn { + HWVoiceIn hw; + HWAVEIN wavein; + CRITICAL_SECTION lock; + unsigned char* buffer_bytes; + WAVEHDR buffers[ NUM_IN_BUFFERS ]; + int read_index; + int read_count; + int read_pos; + int read_size; +} WinAudioIn; + +/* The Win32 callback that is called when a buffer has finished playing */ +static void CALLBACK +winaudio_in_buffer_done (HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, + DWORD dwParam1, DWORD dwParam2) +{ + WinAudioIn* s = (WinAudioIn*) dwInstance; + + /* Only service "buffer done playing" messages */ + if ( uMsg != WIM_DATA ) + return; + + /* Signal that we are done playing a buffer */ + EnterCriticalSection( &s->lock ); + if (s->read_count < NUM_IN_BUFFERS) + s->read_count += 1; + //D(".%c",s->read_count + '0'); fflush(stdout); + LeaveCriticalSection( &s->lock ); +} + +static void +winaudio_in_fini (HWVoiceIn *hw) +{ + WinAudioIn* s = (WinAudioIn*) hw; + int i; + + if (s->wavein) { + waveInReset(s->wavein); + s->wavein = 0; + } + + for ( i=0; ibuffers[i].dwUser != 0xFFFF ) { + waveInUnprepareHeader( + s->wavein, &s->buffers[i], sizeof(s->buffers[i]) ); + s->buffers[i].dwUser = 0xFFFF; + } + } + + if (s->buffer_bytes != NULL) { + qemu_free(s->buffer_bytes); + s->buffer_bytes = NULL; + } + + if (s->wavein) { + waveInClose(s->wavein); + s->wavein = NULL; + } +} + + +static int +winaudio_in_init (HWVoiceIn *hw, audsettings_t *as) +{ + WinAudioIn* s = (WinAudioIn*) hw; + MMRESULT result; + WAVEFORMATEX format; + int shift, i, samples_size; + + s->wavein = NULL; + InitializeCriticalSection( &s->lock ); + for (i = 0; i < NUM_OUT_BUFFERS; i++) { + s->buffers[i].dwUser = 0xFFFF; + } + s->buffer_bytes = NULL; + + /* compute desired wave input format */ + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = as->nchannels; + format.nSamplesPerSec = as->freq; + format.nAvgBytesPerSec = as->freq*as->nchannels; + + switch (as->fmt) { + case AUD_FMT_S8: shift = 0; break; + case AUD_FMT_U8: shift = 0; break; + case AUD_FMT_S16: shift = 1; break; + case AUD_FMT_U16: shift = 1; break; + default: + fprintf(stderr, "qemu: winaudio: Bad input audio format: %d\n", + as->fmt); + return -1; + } + + format.nAvgBytesPerSec = (format.nSamplesPerSec * format.nChannels) << shift; + format.nBlockAlign = format.nChannels << shift; + format.wBitsPerSample = 8 << shift; + format.cbSize = 0; + + /* open the wave in device */ + result = waveInOpen( &s->wavein, WAVE_MAPPER, &format, + (DWORD_PTR)winaudio_in_buffer_done, (DWORD_PTR) hw, + CALLBACK_FUNCTION); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror( "qemu: winaudio: waveInOpen()", result); + return -1; + } + + samples_size = format.nBlockAlign * conf.nb_samples; + s->buffer_bytes = qemu_malloc( NUM_IN_BUFFERS * samples_size ); + if (s->buffer_bytes == NULL) { + waveInClose( s->wavein ); + s->wavein = NULL; + fprintf(stderr, "not enough memory for Windows audio buffers\n"); + return -1; + } + + for (i = 0; i < NUM_IN_BUFFERS; i++) { + memset( &s->buffers[i], 0, sizeof(s->buffers[i]) ); + s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size); + s->buffers[i].dwBufferLength = samples_size; + s->buffers[i].dwFlags = WHDR_DONE; + + result = waveInPrepareHeader( s->wavein, &s->buffers[i], + sizeof(s->buffers[i]) ); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror("waveInPrepareHeader()", result); + return -1; + } + + result = waveInAddBuffer( s->wavein, &s->buffers[i], + sizeof(s->buffers[i]) ); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror("waveInAddBuffer()", result); + return -1; + } + } + +#if DEBUG + /* Check the sound device we retrieved */ + { + WAVEINCAPS caps; + + result = waveInGetDevCaps((UINT) s->wavein, &caps, sizeof(caps)); + if ( result != MMSYSERR_NOERROR ) { + dump_mmerror("waveInGetDevCaps()", result); + } else + printf("Audio in device: %s\n", caps.szPname); + } +#endif + + audio_pcm_init_info (&hw->info, as); + hw->samples = conf.nb_samples*2; + + s->read_index = 0; + s->read_count = 0; + s->read_pos = 0; + s->read_size = samples_size; + return 0; +} + + +/* report the number of captured samples to the audio subsystem */ +static int +winaudio_in_run (HWVoiceIn *hw) +{ + WinAudioIn* s = (WinAudioIn*) hw; + int captured = 0; + int has_buffer; + int live = hw->samples - hw->total_samples_captured; + + if (!live) { +#if 0 + static int counter; + if (++counter == 100) { + D("0"); fflush(stdout); + counter = 0; + } +#endif + return 0; + } + + EnterCriticalSection( &s->lock ); + has_buffer = (s->read_count > 0); + LeaveCriticalSection( &s->lock ); + + if (has_buffer > 0) { + while (live > 0) { + WAVEHDR* wav_buffer = s->buffers + s->read_index; + int wav_bytes = (s->read_size - s->read_pos); + int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live); + int hw_samples = audio_MIN(hw->samples - hw->wpos, live); + st_sample_t* dst = hw->conv_buf + hw->wpos; + uint8_t* src = (uint8_t*)wav_buffer->lpData + s->read_pos; + + if (wav_samples > hw_samples) { + wav_samples = hw_samples; + } + + wav_bytes = wav_samples << hw->info.shift; + + D("%s: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d wpos:%d hwsamples:%d\n", + __FUNCTION__, s->read_index, s->read_pos, s->read_size, wav_samples, wav_bytes, live, + hw->wpos, hw->samples); + + hw->conv(dst, src, wav_samples, &nominal_volume); + + hw->wpos += wav_samples; + if (hw->wpos >= hw->samples) + hw->wpos -= hw->samples; + + live -= wav_samples; + captured += wav_samples; + s->read_pos += wav_bytes; + if (s->read_pos == s->read_size) { + s->read_pos = 0; + s->read_index += 1; + if (s->read_index == NUM_IN_BUFFERS) + s->read_index = 0; + + waveInAddBuffer( s->wavein, wav_buffer, sizeof(*wav_buffer) ); + + EnterCriticalSection( &s->lock ); + if (--s->read_count == 0) { + live = 0; + } + LeaveCriticalSection( &s->lock ); + } + } + } + return captured; +} + + +static int +winaudio_in_read (SWVoiceIn *sw, void *buf, int len) +{ + int ret = audio_pcm_sw_read (sw, buf, len); + if (ret > 0) + D("%s: (%d) returned %d\n", __FUNCTION__, len, ret); + return ret; +} + + +static int +winaudio_in_ctl (HWVoiceIn *hw, int cmd, ...) +{ + WinAudioIn* s = (WinAudioIn*) hw; + + switch (cmd) { + case VOICE_ENABLE: + D("%s: enable audio in\n", __FUNCTION__); + waveInStart( s->wavein ); + break; + + case VOICE_DISABLE: + D("%s: disable audio in\n", __FUNCTION__); + waveInStop( s->wavein ); + break; + } + return 0; +} + +/** AUDIO STATE + **/ + +typedef struct WinAudioState { + int dummy; +} WinAudioState; + +static WinAudioState g_winaudio; + +static void* +winaudio_init(void) +{ + WinAudioState* s = &g_winaudio; + +#if DEBUG + start_time = qemu_get_clock(vm_clock); + last_time = 0; +#endif + + return s; +} + + +static void +winaudio_fini (void *opaque) +{ +} + +static struct audio_option winaudio_options[] = { + {"SAMPLES", AUD_OPT_INT, &conf.nb_samples, + "Size of Windows audio buffer in samples", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} +}; + +static struct audio_pcm_ops winaudio_pcm_ops = { + winaudio_out_init, + winaudio_out_fini, + winaudio_out_run, + winaudio_out_write, + winaudio_out_ctl, + + winaudio_in_init, + winaudio_in_fini, + winaudio_in_run, + winaudio_in_read, + winaudio_in_ctl +}; + +struct audio_driver win_audio_driver = { + INIT_FIELD (name = ) "winaudio", + INIT_FIELD (descr = ) "Windows wave audio", + INIT_FIELD (options = ) winaudio_options, + INIT_FIELD (init = ) winaudio_init, + INIT_FIELD (fini = ) winaudio_fini, + INIT_FIELD (pcm_ops = ) &winaudio_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (WinAudioOut), + INIT_FIELD (voice_size_in = ) sizeof (WinAudioIn) +}; -- cgit v1.1