| /* 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) |
| }; |