diff options
Diffstat (limited to 'audio/winwaveaudio.c')
-rw-r--r-- | audio/winwaveaudio.c | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/audio/winwaveaudio.c b/audio/winwaveaudio.c new file mode 100644 index 000000000..139c7ef47 --- /dev/null +++ b/audio/winwaveaudio.c @@ -0,0 +1,312 @@ +/* public domain */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "winwave" +#include "audio_int.h" + +#include <windows.h> +#include <mmsystem.h> + +#include "audio_win_int.h" + +static struct { + int dac_headers; + int dac_samples; +} conf = { + .dac_headers = 4, + .dac_samples = 1024 +}; + +typedef struct { + HWVoiceOut hw; + HWAVEOUT hwo; + WAVEHDR *hdrs; + void *pcm_buf; + int avail; + int pending; + int curhdr; + CRITICAL_SECTION crit_sect; +} WaveVoiceOut; + +static void winwave_log_mmresult (MMRESULT mr) +{ + const char *str = "BUG"; + + switch (mr) { + case MMSYSERR_NOERROR: + str = "Success"; + break; + + case MMSYSERR_INVALHANDLE: + str = "Specified device handle is invalid"; + break; + + case MMSYSERR_BADDEVICEID: + str = "Specified device id is out of range"; + break; + + case MMSYSERR_NODRIVER: + str = "No device driver is present"; + break; + + case MMSYSERR_NOMEM: + str = "Unable to allocate or locl memory"; + break; + + case WAVERR_SYNC: + str = "Device is synchronous but waveOutOpen was called " + "without using the WINWAVE_ALLOWSYNC flag"; + break; + + case WAVERR_UNPREPARED: + str = "The data block pointed to by the pwh parameter " + "hasn't been prepared"; + break; + + default: + AUD_log (AUDIO_CAP, "Reason: Unknown (MMRESULT %#x)\n", mr); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) winwave_logerr ( + MMRESULT mr, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + winwave_log_mmresult (mr); +} + +static void winwave_anal_close_out (WaveVoiceOut *wave) +{ + MMRESULT mr; + + mr = waveOutClose (wave->hwo); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutClose\n"); + } + wave->hwo = NULL; +} + +static void CALLBACK winwave_callback ( + HWAVEOUT hwo, + UINT msg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2 + ) +{ + WaveVoiceOut *wave = (WaveVoiceOut *) dwInstance; + + switch (msg) { + case WOM_DONE: + { + WAVEHDR *h = (WAVEHDR *) dwParam1; + if (!h->dwUser) { + h->dwUser = 1; + EnterCriticalSection (&wave->crit_sect); + { + wave->avail += conf.dac_samples; + } + LeaveCriticalSection (&wave->crit_sect); + } + } + break; + + case WOM_CLOSE: + case WOM_OPEN: + break; + + default: + AUD_log (AUDIO_CAP, "unknown wave callback msg %x\n", msg); + } +} + +static int winwave_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + int i; + int err; + MMRESULT mr; + WAVEFORMATEX wfx; + WaveVoiceOut *wave; + + wave = (WaveVoiceOut *) hw; + + InitializeCriticalSection (&wave->crit_sect); + + err = waveformat_from_audio_settings (&wfx, as); + if (err) { + goto err0; + } + + mr = waveOutOpen (&wave->hwo, WAVE_MAPPER, &wfx, + (DWORD_PTR) winwave_callback, + (DWORD_PTR) wave, CALLBACK_FUNCTION); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutOpen\n"); + goto err1; + } + + wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers, + sizeof (*wave->hdrs)); + if (!wave->hdrs) { + goto err2; + } + + audio_pcm_init_info (&hw->info, as); + hw->samples = conf.dac_samples * conf.dac_headers; + wave->avail = hw->samples; + + wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.dac_samples, + conf.dac_headers << hw->info.shift); + if (!wave->pcm_buf) { + goto err3; + } + + for (i = 0; i < conf.dac_headers; ++i) { + WAVEHDR *h = &wave->hdrs[i]; + + h->dwUser = 0; + h->dwBufferLength = conf.dac_samples << hw->info.shift; + h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); + h->dwFlags = 0; + + mr = waveOutPrepareHeader (wave->hwo, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutPrepareHeader(%d)\n", wave->curhdr); + goto err4; + } + } + + return 0; + + err4: + qemu_free (wave->pcm_buf); + err3: + qemu_free (wave->hdrs); + err2: + winwave_anal_close_out (wave); + err1: + err0: + return -1; +} + +static int winwave_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int winwave_run_out (HWVoiceOut *hw, int live) +{ + WaveVoiceOut *wave = (WaveVoiceOut *) hw; + int decr; + + EnterCriticalSection (&wave->crit_sect); + { + decr = audio_MIN (live, wave->avail); + decr = audio_pcm_hw_clip_out (hw, wave->pcm_buf, decr, wave->pending); + wave->pending += decr; + wave->avail -= decr; + } + LeaveCriticalSection (&wave->crit_sect); + + while (wave->pending >= conf.dac_samples) { + MMRESULT mr; + WAVEHDR *h = &wave->hdrs[wave->curhdr]; + + h->dwUser = 0; + mr = waveOutWrite (wave->hwo, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutWrite(%d)\n", wave->curhdr); + break; + } + + wave->pending -= conf.dac_samples; + wave->curhdr = (wave->curhdr + 1) % conf.dac_headers; + } + return decr; +} + +static void winwave_fini_out (HWVoiceOut *hw) +{ + WaveVoiceOut *wave = (WaveVoiceOut *) hw; + + winwave_anal_close_out (wave); + + qemu_free (wave->pcm_buf); + wave->pcm_buf = NULL; + + qemu_free (wave->hdrs); + wave->hdrs = NULL; +} + +static int winwave_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + switch (cmd) { + case VOICE_ENABLE: + return 0; + + case VOICE_DISABLE: + return 0; + } + return -1; +} + +static void *winwave_audio_init (void) +{ + return &conf; +} + +static void winwave_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option winwave_options[] = { + { + .name = "DAC_HEADERS", + .tag = AUD_OPT_INT, + .valp = &conf.dac_headers, + .descr = "DAC number of headers", + }, + { + .name = "DAC_SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.dac_samples, + .descr = "DAC number of samples per header", + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops winwave_pcm_ops = { + .init_out = winwave_init_out, + .fini_out = winwave_fini_out, + .run_out = winwave_run_out, + .write = winwave_write, + .ctl_out = winwave_ctl_out +}; + +struct audio_driver winwave_audio_driver = { + .name = "winwave", + .descr = "Windows Waveform Audio http://msdn.microsoft.com", + .options = winwave_options, + .init = winwave_audio_init, + .fini = winwave_audio_fini, + .pcm_ops = &winwave_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = 0, + .voice_size_out = sizeof (WaveVoiceOut), + .voice_size_in = 0 +}; |