/* public domain */ #include "qemu-common.h" #include "sysemu/sysemu.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; int adc_headers; int adc_samples; } conf = { .dac_headers = 4, .dac_samples = 1024, .adc_headers = 4, .adc_samples = 1024 }; typedef struct { HWVoiceOut hw; HWAVEOUT hwo; WAVEHDR *hdrs; HANDLE event; void *pcm_buf; int avail; int pending; int curhdr; int paused; CRITICAL_SECTION crit_sect; } WaveVoiceOut; typedef struct { HWVoiceIn hw; HWAVEIN hwi; WAVEHDR *hdrs; HANDLE event; void *pcm_buf; int curhdr; int paused; int rpos; int avail; CRITICAL_SECTION crit_sect; } WaveVoiceIn; 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 lock 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; case WAVERR_STILLPLAYING: str = "There are still buffers in the queue"; break; default: dolog ("Reason: Unknown (MMRESULT %#x)\n", mr); return; } dolog ("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); AUD_log (NULL, " failed\n"); 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"); } wave->hwo = NULL; } static void CALLBACK winwave_callback_out ( 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); if (wave->hw.poll_mode) { if (!SetEvent (wave->event)) { dolog ("DAC SetEvent failed %lx\n", GetLastError ()); } } } } break; case WOM_CLOSE: case WOM_OPEN: break; default: dolog ("unknown wave out 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_out, (DWORD_PTR) wave, CALLBACK_FUNCTION); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveOutOpen"); 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)", i); goto err4; } } return 0; err4: g_free (wave->pcm_buf); err3: g_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; int doreset; 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); doreset = hw->poll_mode && (wave->pending >= conf.dac_samples); if (doreset && !ResetEvent (wave->event)) { dolog ("DAC ResetEvent failed %lx\n", GetLastError ()); } 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)", wave->curhdr); break; } wave->pending -= conf.dac_samples; wave->curhdr = (wave->curhdr + 1) % conf.dac_headers; } return decr; } static void winwave_poll (void *opaque) { (void) opaque; audio_run ("winwave_poll"); } static void winwave_fini_out (HWVoiceOut *hw) { int i; MMRESULT mr; WaveVoiceOut *wave = (WaveVoiceOut *) hw; mr = waveOutReset (wave->hwo); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveOutReset"); } for (i = 0; i < conf.dac_headers; ++i) { mr = waveOutUnprepareHeader (wave->hwo, &wave->hdrs[i], sizeof (wave->hdrs[i])); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveOutUnprepareHeader(%d)", i); } } winwave_anal_close_out (wave); if (wave->event) { qemu_del_wait_object (wave->event, winwave_poll, wave); if (!CloseHandle (wave->event)) { dolog ("DAC CloseHandle failed %lx\n", GetLastError ()); } wave->event = NULL; } g_free (wave->pcm_buf); wave->pcm_buf = NULL; g_free (wave->hdrs); wave->hdrs = NULL; } static int winwave_ctl_out (HWVoiceOut *hw, int cmd, ...) { MMRESULT mr; WaveVoiceOut *wave = (WaveVoiceOut *) hw; switch (cmd) { case VOICE_ENABLE: { va_list ap; int poll_mode; va_start (ap, cmd); poll_mode = va_arg (ap, int); va_end (ap); if (poll_mode && !wave->event) { wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); if (!wave->event) { dolog ("DAC CreateEvent: %lx, poll mode will be disabled\n", GetLastError ()); } } if (wave->event) { int ret; ret = qemu_add_wait_object (wave->event, winwave_poll, wave); hw->poll_mode = (ret == 0); } else { hw->poll_mode = 0; } wave->paused = 0; } return 0; case VOICE_DISABLE: if (!wave->paused) { mr = waveOutReset (wave->hwo); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveOutReset"); } else { wave->paused = 1; } } if (wave->event) { qemu_del_wait_object (wave->event, winwave_poll, wave); } return 0; } return -1; } static void winwave_anal_close_in (WaveVoiceIn *wave) { MMRESULT mr; mr = waveInClose (wave->hwi); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInClose"); } wave->hwi = NULL; } static void CALLBACK winwave_callback_in ( HWAVEIN *hwi, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 ) { WaveVoiceIn *wave = (WaveVoiceIn *) dwInstance; switch (msg) { case WIM_DATA: { WAVEHDR *h = (WAVEHDR *) dwParam1; if (!h->dwUser) { h->dwUser = 1; EnterCriticalSection (&wave->crit_sect); { wave->avail += conf.adc_samples; } LeaveCriticalSection (&wave->crit_sect); if (wave->hw.poll_mode) { if (!SetEvent (wave->event)) { dolog ("ADC SetEvent failed %lx\n", GetLastError ()); } } } } break; case WIM_CLOSE: case WIM_OPEN: break; default: dolog ("unknown wave in callback msg %x\n", msg); } } static void winwave_add_buffers (WaveVoiceIn *wave, int samples) { int doreset; doreset = wave->hw.poll_mode && (samples >= conf.adc_samples); if (doreset && !ResetEvent (wave->event)) { dolog ("ADC ResetEvent failed %lx\n", GetLastError ()); } while (samples >= conf.adc_samples) { MMRESULT mr; WAVEHDR *h = &wave->hdrs[wave->curhdr]; h->dwUser = 0; mr = waveInAddBuffer (wave->hwi, h, sizeof (*h)); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInAddBuffer(%d)", wave->curhdr); } wave->curhdr = (wave->curhdr + 1) % conf.adc_headers; samples -= conf.adc_samples; } } static int winwave_init_in (HWVoiceIn *hw, struct audsettings *as) { int i; int err; MMRESULT mr; WAVEFORMATEX wfx; WaveVoiceIn *wave; wave = (WaveVoiceIn *) hw; InitializeCriticalSection (&wave->crit_sect); err = waveformat_from_audio_settings (&wfx, as); if (err) { goto err0; } mr = waveInOpen (&wave->hwi, WAVE_MAPPER, &wfx, (DWORD_PTR) winwave_callback_in, (DWORD_PTR) wave, CALLBACK_FUNCTION); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInOpen"); 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.adc_samples * conf.adc_headers; wave->avail = 0; wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.adc_samples, conf.adc_headers << hw->info.shift); if (!wave->pcm_buf) { goto err3; } for (i = 0; i < conf.adc_headers; ++i) { WAVEHDR *h = &wave->hdrs[i]; h->dwUser = 0; h->dwBufferLength = conf.adc_samples << hw->info.shift; h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); h->dwFlags = 0; mr = waveInPrepareHeader (wave->hwi, h, sizeof (*h)); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInPrepareHeader(%d)", i); goto err4; } } wave->paused = 1; winwave_add_buffers (wave, hw->samples); return 0; err4: g_free (wave->pcm_buf); err3: g_free (wave->hdrs); err2: winwave_anal_close_in (wave); err1: err0: return -1; } static void winwave_fini_in (HWVoiceIn *hw) { int i; MMRESULT mr; WaveVoiceIn *wave = (WaveVoiceIn *) hw; mr = waveInReset (wave->hwi); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInReset"); } for (i = 0; i < conf.adc_headers; ++i) { mr = waveInUnprepareHeader (wave->hwi, &wave->hdrs[i], sizeof (wave->hdrs[i])); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInUnprepareHeader(%d)", i); } } winwave_anal_close_in (wave); if (wave->event) { qemu_del_wait_object (wave->event, winwave_poll, wave); if (!CloseHandle (wave->event)) { dolog ("ADC CloseHandle failed %lx\n", GetLastError ()); } wave->event = NULL; } g_free (wave->pcm_buf); wave->pcm_buf = NULL; g_free (wave->hdrs); wave->hdrs = NULL; } static int winwave_run_in (HWVoiceIn *hw) { WaveVoiceIn *wave = (WaveVoiceIn *) hw; int live = audio_pcm_hw_get_live_in (hw); int dead = hw->samples - live; int decr, ret; if (!dead) { return 0; } EnterCriticalSection (&wave->crit_sect); { decr = audio_MIN (dead, wave->avail); wave->avail -= decr; } LeaveCriticalSection (&wave->crit_sect); ret = decr; while (decr) { int left = hw->samples - hw->wpos; int conv = audio_MIN (left, decr); hw->conv (hw->conv_buf + hw->wpos, advance (wave->pcm_buf, wave->rpos << hw->info.shift), conv); wave->rpos = (wave->rpos + conv) % hw->samples; hw->wpos = (hw->wpos + conv) % hw->samples; decr -= conv; } winwave_add_buffers (wave, ret); return ret; } static int winwave_read (SWVoiceIn *sw, void *buf, int size) { return audio_pcm_sw_read (sw, buf, size); } static int winwave_ctl_in (HWVoiceIn *hw, int cmd, ...) { MMRESULT mr; WaveVoiceIn *wave = (WaveVoiceIn *) hw; switch (cmd) { case VOICE_ENABLE: { va_list ap; int poll_mode; va_start (ap, cmd); poll_mode = va_arg (ap, int); va_end (ap); if (poll_mode && !wave->event) { wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); if (!wave->event) { dolog ("ADC CreateEvent: %lx, poll mode will be disabled\n", GetLastError ()); } } if (wave->event) { int ret; ret = qemu_add_wait_object (wave->event, winwave_poll, wave); hw->poll_mode = (ret == 0); } else { hw->poll_mode = 0; } if (wave->paused) { mr = waveInStart (wave->hwi); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInStart"); } wave->paused = 0; } } return 0; case VOICE_DISABLE: if (!wave->paused) { mr = waveInStop (wave->hwi); if (mr != MMSYSERR_NOERROR) { winwave_logerr (mr, "waveInStop"); } else { wave->paused = 1; } } if (wave->event) { qemu_del_wait_object (wave->event, winwave_poll, wave); } return 0; } return 0; } 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", }, { .name = "ADC_HEADERS", .tag = AUD_OPT_INT, .valp = &conf.adc_headers, .descr = "ADC number of headers", }, { .name = "ADC_SAMPLES", .tag = AUD_OPT_INT, .valp = &conf.adc_samples, .descr = "ADC 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, .init_in = winwave_init_in, .fini_in = winwave_fini_in, .run_in = winwave_run_in, .read = winwave_read, .ctl_in = winwave_ctl_in }; 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 = INT_MAX, .voice_size_out = sizeof (WaveVoiceOut), .voice_size_in = sizeof (WaveVoiceIn) };