7 changed files with 449 additions and 97 deletions
@ -0,0 +1,108 @@ |
|||
/* public domain */ |
|||
|
|||
#include "qemu-common.h" |
|||
#include "audio.h" |
|||
|
|||
#define AUDIO_CAP "win-int" |
|||
#include <windows.h> |
|||
#include <mmsystem.h> |
|||
|
|||
#include "audio.h" |
|||
#include "audio_int.h" |
|||
#include "audio_win_int.h" |
|||
|
|||
int waveformat_from_audio_settings (WAVEFORMATEX *wfx, |
|||
struct audsettings *as) |
|||
{ |
|||
memset (wfx, 0, sizeof (*wfx)); |
|||
|
|||
wfx->wFormatTag = WAVE_FORMAT_PCM; |
|||
wfx->nChannels = as->nchannels; |
|||
wfx->nSamplesPerSec = as->freq; |
|||
wfx->nAvgBytesPerSec = as->freq << (as->nchannels == 2); |
|||
wfx->nBlockAlign = 1 << (as->nchannels == 2); |
|||
wfx->cbSize = 0; |
|||
|
|||
switch (as->fmt) { |
|||
case AUD_FMT_S8: |
|||
case AUD_FMT_U8: |
|||
wfx->wBitsPerSample = 8; |
|||
break; |
|||
|
|||
case AUD_FMT_S16: |
|||
case AUD_FMT_U16: |
|||
wfx->wBitsPerSample = 16; |
|||
wfx->nAvgBytesPerSec <<= 1; |
|||
wfx->nBlockAlign <<= 1; |
|||
break; |
|||
|
|||
case AUD_FMT_S32: |
|||
case AUD_FMT_U32: |
|||
wfx->wBitsPerSample = 32; |
|||
wfx->nAvgBytesPerSec <<= 2; |
|||
wfx->nBlockAlign <<= 2; |
|||
break; |
|||
|
|||
default: |
|||
dolog ("Internal logic error: Bad audio format %d\n", as->freq); |
|||
return -1; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
int waveformat_to_audio_settings (WAVEFORMATEX *wfx, |
|||
struct audsettings *as) |
|||
{ |
|||
if (wfx->wFormatTag != WAVE_FORMAT_PCM) { |
|||
dolog ("Invalid wave format, tag is not PCM, but %d\n", |
|||
wfx->wFormatTag); |
|||
return -1; |
|||
} |
|||
|
|||
if (!wfx->nSamplesPerSec) { |
|||
dolog ("Invalid wave format, frequency is zero\n"); |
|||
return -1; |
|||
} |
|||
as->freq = wfx->nSamplesPerSec; |
|||
|
|||
switch (wfx->nChannels) { |
|||
case 1: |
|||
as->nchannels = 1; |
|||
break; |
|||
|
|||
case 2: |
|||
as->nchannels = 2; |
|||
break; |
|||
|
|||
default: |
|||
dolog ( |
|||
"Invalid wave format, number of channels is not 1 or 2, but %d\n", |
|||
wfx->nChannels |
|||
); |
|||
return -1; |
|||
} |
|||
|
|||
switch (wfx->wBitsPerSample) { |
|||
case 8: |
|||
as->fmt = AUD_FMT_U8; |
|||
break; |
|||
|
|||
case 16: |
|||
as->fmt = AUD_FMT_S16; |
|||
break; |
|||
|
|||
case 32: |
|||
as->fmt = AUD_FMT_S32; |
|||
break; |
|||
|
|||
default: |
|||
dolog ("Invalid wave format, bits per sample is not " |
|||
"8, 16 or 32, but %d\n", |
|||
wfx->wBitsPerSample); |
|||
return -1; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
@ -0,0 +1,10 @@ |
|||
#ifndef AUDIO_WIN_INT_H |
|||
#define AUDIO_WIN_INT_H |
|||
|
|||
int waveformat_from_audio_settings (WAVEFORMATEX *wfx, |
|||
struct audsettings *as); |
|||
|
|||
int waveformat_to_audio_settings (WAVEFORMATEX *wfx, |
|||
struct audsettings *as); |
|||
|
|||
#endif /* AUDIO_WIN_INT_H */ |
|||
@ -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 |
|||
}; |
|||
Loading…
Reference in new issue