13ef70f64e
Winwave audio backend has problem with pausing and restart audio out. Unlike other backends, Winwave pausing API does not flush audio buffer. As a result, the previous audio data are played in front of user expected sound when user restart audio. So changes it to waveOutReset() Signed-off-by: Munkyu Im <munkyu.im@samsung.com> Signed-off-by: malc <av1474@comtv.ru>
718 lines
17 KiB
C
718 lines
17 KiB
C
/* public domain */
|
|
|
|
#include "qemu-common.h"
|
|
#include "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)
|
|
};
|