/*
* QEMU "simple" Windows audio driver
*
* Copyright (c) 2007 The Android Open Source Project
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
#define AUDIO_CAP "winaudio"
#include "audio_int.h"
/* define DEBUG to 1 to dump audio debugging info at runtime to stderr */
#define DEBUG 0
#if 1
# define D_ACTIVE 1
#else
# define D_ACTIVE DEBUG
#endif
#if DEBUG
# define D(...) do{ if (D_ACTIVE) printf(__VA_ARGS__); } while(0)
#else
# define D(...) ((void)0)
#endif
static struct {
int nb_samples;
} conf = {
1024
};
#if DEBUG
int64_t start_time;
int64_t last_time;
#endif
#define NUM_OUT_BUFFERS 8 /* must be at least 2 */
/** COMMON UTILITIES
**/
#if DEBUG
static void
dump_mmerror( const char* func, MMRESULT error )
{
const char* reason = NULL;
fprintf(stderr, "%s returned error: ", func);
switch (error) {
case MMSYSERR_ALLOCATED: reason="specified resource is already allocated"; break;
case MMSYSERR_BADDEVICEID: reason="bad device id"; break;
case MMSYSERR_NODRIVER: reason="no driver is present"; break;
case MMSYSERR_NOMEM: reason="unable to allocate or lock memory"; break;
case WAVERR_BADFORMAT: reason="unsupported waveform-audio format"; break;
case WAVERR_SYNC: reason="device is synchronous"; break;
default:
fprintf(stderr, "unknown(%d)\n", error);
}
if (reason)
fprintf(stderr, "%s\n", reason);
}
#else
# define dump_mmerror(func,error) ((void)0)
#endif
/** AUDIO OUT
**/
typedef struct WinAudioOut {
HWVoiceOut hw;
HWAVEOUT waveout;
int silence;
CRITICAL_SECTION lock;
unsigned char* buffer_bytes;
WAVEHDR buffers[ NUM_OUT_BUFFERS ];
int write_index; /* starting first writable buffer */
int write_count; /* available writable buffers count */
int write_pos; /* position in current writable buffer */
int write_size; /* size in bytes of each buffer */
} WinAudioOut;
/* The Win32 callback that is called when a buffer has finished playing */
static void CALLBACK
winaudio_out_buffer_done (HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD dwParam1, DWORD dwParam2)
{
WinAudioOut* s = (WinAudioOut*) dwInstance;
/* Only service "buffer done playing" messages */
if ( uMsg != WOM_DONE )
return;
/* Signal that we are done playing a buffer */
EnterCriticalSection( &s->lock );
if (s->write_count < NUM_OUT_BUFFERS)
s->write_count += 1;
LeaveCriticalSection( &s->lock );
}
static int
winaudio_out_write (SWVoiceOut *sw, void *buf, int len)
{
return audio_pcm_sw_write (sw, buf, len);
}
static void
winaudio_out_fini (HWVoiceOut *hw)
{
WinAudioOut* s = (WinAudioOut*) hw;
int i;
if (s->waveout) {
waveOutReset(s->waveout);
s->waveout = 0;
}
for ( i=0; i<NUM_OUT_BUFFERS; ++i ) {
if ( s->buffers[i].dwUser != 0xFFFF ) {
waveOutUnprepareHeader(
s->waveout, &s->buffers[i], sizeof(s->buffers[i]) );
s->buffers[i].dwUser = 0xFFFF;
}
}
if (s->buffer_bytes != NULL) {
g_free(s->buffer_bytes);
s->buffer_bytes = NULL;
}
if (s->waveout) {
waveOutClose(s->waveout);
s->waveout = NULL;
}
}
static int
winaudio_out_init (HWVoiceOut *hw, struct audsettings *as)
{
WinAudioOut* s = (WinAudioOut*) hw;
MMRESULT result;
WAVEFORMATEX format;
int shift, i, samples_size;
s->waveout = NULL;
InitializeCriticalSection( &s->lock );
for (i = 0; i < NUM_OUT_BUFFERS; i++) {
s->buffers[i].dwUser = 0xFFFF;
}
s->buffer_bytes = NULL;
/* compute desired wave output format */
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = as->nchannels;
format.nSamplesPerSec = as->freq;
format.nAvgBytesPerSec = as->freq*as->nchannels;
s->silence = 0;
switch (as->fmt) {
case AUD_FMT_S8: shift = 0; break;
case AUD_FMT_U8: shift = 0; s->silence = 0x80; break;
case AUD_FMT_S16: shift = 1; break;
case AUD_FMT_U16: shift = 1; s->silence = 0x8000; break;
default:
fprintf(stderr, "qemu: winaudio: Bad output audio format: %d\n",
as->fmt);
return -1;
}
format.nAvgBytesPerSec = (format.nSamplesPerSec & format.nChannels) << shift;
format.nBlockAlign = format.nChannels << shift;
format.wBitsPerSample = 8 << shift;
format.cbSize = 0;
/* open the wave out device */
result = waveOutOpen( &s->waveout, WAVE_MAPPER, &format,
(DWORD_PTR)winaudio_out_buffer_done, (DWORD_PTR) hw,
CALLBACK_FUNCTION);
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror( "qemu: winaudio: waveOutOpen()", result);
return -1;
}
samples_size = format.nBlockAlign * conf.nb_samples;
s->buffer_bytes = g_malloc( NUM_OUT_BUFFERS * samples_size );
if (s->buffer_bytes == NULL) {
waveOutClose( s->waveout );
s->waveout = NULL;
fprintf(stderr, "not enough memory for Windows audio buffers\n");
return -1;
}
for (i = 0; i < NUM_OUT_BUFFERS; i++) {
memset( &s->buffers[i], 0, sizeof(s->buffers[i]) );
s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size);
s->buffers[i].dwBufferLength = samples_size;
s->buffers[i].dwFlags = WHDR_DONE;
result = waveOutPrepareHeader( s->waveout, &s->buffers[i],
sizeof(s->buffers[i]) );
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror("waveOutPrepareHeader()", result);
return -1;
}
}
#if DEBUG
/* Check the sound device we retrieved */
{
WAVEOUTCAPS caps;
result = waveOutGetDevCaps((UINT) s->waveout, &caps, sizeof(caps));
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror("waveOutGetDevCaps()", result);
} else
printf("Audio out device: %s\n", caps.szPname);
}
#endif
audio_pcm_init_info (&hw->info, as);
hw->samples = conf.nb_samples*2;
s->write_index = 0;
s->write_count = NUM_OUT_BUFFERS;
s->write_pos = 0;
s->write_size = samples_size;
return 0;
}
static int
winaudio_out_run (HWVoiceOut *hw, int live)
{
WinAudioOut* s = (WinAudioOut*) hw;
int played = 0;
int has_buffer;
if (!live) {
return 0;
}
EnterCriticalSection( &s->lock );
has_buffer = (s->write_count > 0);
LeaveCriticalSection( &s->lock );
if (has_buffer) {
while (live > 0) {
WAVEHDR* wav_buffer = s->buffers + s->write_index;
int wav_bytes = (s->write_size - s->write_pos);
int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
int hw_samples = audio_MIN(hw->samples - hw->rpos, live);
struct st_sample* src = hw->mix_buf + hw->rpos;
uint8_t* dst = (uint8_t*)wav_buffer->lpData + s->write_pos;
if (wav_samples > hw_samples) {
wav_samples = hw_samples;
}
wav_bytes = wav_samples << hw->info.shift;
//D("run_out: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d rpos:%d hwsamples:%d\n", s->write_index,
// s->write_pos, s->write_size, wav_samples, wav_bytes, live, hw->rpos, hw->samples);
hw->clip (dst, src, wav_samples);
hw->rpos += wav_samples;
if (hw->rpos >= hw->samples)
hw->rpos -= hw->samples;
live -= wav_samples;
played += wav_samples;
s->write_pos += wav_bytes;
if (s->write_pos == s->write_size) {
#if xxDEBUG
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - start_time;
int64_t diff = now - last_time;
D("run_out: (%7.3f:%7d):waveOutWrite buffer:%d\n",
now/1e9, (now-last_time)/1e9, s->write_index);
last_time = now;
#endif
waveOutWrite( s->waveout, wav_buffer, sizeof(*wav_buffer) );
s->write_pos = 0;
s->write_index += 1;
if (s->write_index == NUM_OUT_BUFFERS)
s->write_index = 0;
EnterCriticalSection( &s->lock );
if (--s->write_count == 0) {
live = 0;
}
LeaveCriticalSection( &s->lock );
}
}
}
return played;
}
static int
winaudio_out_ctl (HWVoiceOut *hw, int cmd, ...)
{
WinAudioOut* s = (WinAudioOut*) hw;
switch (cmd) {
case VOICE_ENABLE:
waveOutRestart( s->waveout );
break;
case VOICE_DISABLE:
waveOutPause( s->waveout );
break;
}
return 0;
}
/** AUDIO IN
**/
#define NUM_IN_BUFFERS 2
typedef struct WinAudioIn {
HWVoiceIn hw;
HWAVEIN wavein;
CRITICAL_SECTION lock;
unsigned char* buffer_bytes;
WAVEHDR buffers[ NUM_IN_BUFFERS ];
int read_index;
int read_count;
int read_pos;
int read_size;
} WinAudioIn;
/* The Win32 callback that is called when a buffer has finished playing */
static void CALLBACK
winaudio_in_buffer_done (HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
DWORD dwParam1, DWORD dwParam2)
{
WinAudioIn* s = (WinAudioIn*) dwInstance;
/* Only service "buffer done playing" messages */
if ( uMsg != WIM_DATA )
return;
/* Signal that we are done playing a buffer */
EnterCriticalSection( &s->lock );
if (s->read_count < NUM_IN_BUFFERS)
s->read_count += 1;
//D(".%c",s->read_count + '0'); fflush(stdout);
LeaveCriticalSection( &s->lock );
}
static void
winaudio_in_fini (HWVoiceIn *hw)
{
WinAudioIn* s = (WinAudioIn*) hw;
int i;
if (s->wavein) {
waveInReset(s->wavein);
s->wavein = 0;
}
for ( i=0; i<NUM_IN_BUFFERS; ++i ) {
if ( s->buffers[i].dwUser != 0xFFFF ) {
waveInUnprepareHeader(
s->wavein, &s->buffers[i], sizeof(s->buffers[i]) );
s->buffers[i].dwUser = 0xFFFF;
}
}
if (s->buffer_bytes != NULL) {
g_free(s->buffer_bytes);
s->buffer_bytes = NULL;
}
if (s->wavein) {
waveInClose(s->wavein);
s->wavein = NULL;
}
}
static int
winaudio_in_init (HWVoiceIn *hw, struct audsettings *as)
{
WinAudioIn* s = (WinAudioIn*) hw;
MMRESULT result;
WAVEFORMATEX format;
int shift, i, samples_size;
s->wavein = NULL;
InitializeCriticalSection( &s->lock );
for (i = 0; i < NUM_IN_BUFFERS; i++) {
s->buffers[i].dwUser = 0xFFFF;
}
s->buffer_bytes = NULL;
/* compute desired wave input format */
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = as->nchannels;
format.nSamplesPerSec = as->freq;
format.nAvgBytesPerSec = as->freq*as->nchannels;
switch (as->fmt) {
case AUD_FMT_S8: shift = 0; break;
case AUD_FMT_U8: shift = 0; break;
case AUD_FMT_S16: shift = 1; break;
case AUD_FMT_U16: shift = 1; break;
default:
fprintf(stderr, "qemu: winaudio: Bad input audio format: %d\n",
as->fmt);
return -1;
}
format.nAvgBytesPerSec = (format.nSamplesPerSec * format.nChannels) << shift;
format.nBlockAlign = format.nChannels << shift;
format.wBitsPerSample = 8 << shift;
format.cbSize = 0;
/* open the wave in device */
result = waveInOpen( &s->wavein, WAVE_MAPPER, &format,
(DWORD_PTR)winaudio_in_buffer_done, (DWORD_PTR) hw,
CALLBACK_FUNCTION);
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror( "qemu: winaudio: waveInOpen()", result);
return -1;
}
samples_size = format.nBlockAlign * conf.nb_samples;
s->buffer_bytes = g_malloc( NUM_IN_BUFFERS * samples_size );
if (s->buffer_bytes == NULL) {
waveInClose( s->wavein );
s->wavein = NULL;
fprintf(stderr, "not enough memory for Windows audio buffers\n");
return -1;
}
for (i = 0; i < NUM_IN_BUFFERS; i++) {
memset( &s->buffers[i], 0, sizeof(s->buffers[i]) );
s->buffers[i].lpData = (LPSTR)(s->buffer_bytes + i*samples_size);
s->buffers[i].dwBufferLength = samples_size;
s->buffers[i].dwFlags = WHDR_DONE;
result = waveInPrepareHeader( s->wavein, &s->buffers[i],
sizeof(s->buffers[i]) );
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror("waveInPrepareHeader()", result);
return -1;
}
result = waveInAddBuffer( s->wavein, &s->buffers[i],
sizeof(s->buffers[i]) );
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror("waveInAddBuffer()", result);
return -1;
}
}
#if DEBUG
/* Check the sound device we retrieved */
{
WAVEINCAPS caps;
result = waveInGetDevCaps((UINT) s->wavein, &caps, sizeof(caps));
if ( result != MMSYSERR_NOERROR ) {
dump_mmerror("waveInGetDevCaps()", result);
} else
printf("Audio in device: %s\n", caps.szPname);
}
#endif
audio_pcm_init_info (&hw->info, as);
hw->samples = conf.nb_samples*2;
s->read_index = 0;
s->read_count = 0;
s->read_pos = 0;
s->read_size = samples_size;
return 0;
}
/* report the number of captured samples to the audio subsystem */
static int
winaudio_in_run (HWVoiceIn *hw)
{
WinAudioIn* s = (WinAudioIn*) hw;
int captured = 0;
int has_buffer;
int live = hw->samples - hw->total_samples_captured;
if (!live) {
#if 0
static int counter;
if (++counter == 100) {
D("0"); fflush(stdout);
counter = 0;
}
#endif
return 0;
}
EnterCriticalSection( &s->lock );
has_buffer = (s->read_count > 0);
LeaveCriticalSection( &s->lock );
if (has_buffer > 0) {
while (live > 0) {
WAVEHDR* wav_buffer = s->buffers + s->read_index;
int wav_bytes = (s->read_size - s->read_pos);
int wav_samples = audio_MIN(wav_bytes >> hw->info.shift, live);
int hw_samples = audio_MIN(hw->samples - hw->wpos, live);
struct st_sample* dst = hw->conv_buf + hw->wpos;
uint8_t* src = (uint8_t*)wav_buffer->lpData + s->read_pos;
if (wav_samples > hw_samples) {
wav_samples = hw_samples;
}
wav_bytes = wav_samples << hw->info.shift;
D("%s: buffer:%d pos:%d size:%d wsamples:%d wbytes:%d live:%d wpos:%d hwsamples:%d\n",
__FUNCTION__, s->read_index, s->read_pos, s->read_size, wav_samples, wav_bytes, live,
hw->wpos, hw->samples);
hw->conv(dst, src, wav_samples, &nominal_volume);
hw->wpos += wav_samples;
if (hw->wpos >= hw->samples)
hw->wpos -= hw->samples;
live -= wav_samples;
captured += wav_samples;
s->read_pos += wav_bytes;
if (s->read_pos == s->read_size) {
s->read_pos = 0;
s->read_index += 1;
if (s->read_index == NUM_IN_BUFFERS)
s->read_index = 0;
waveInAddBuffer( s->wavein, wav_buffer, sizeof(*wav_buffer) );
EnterCriticalSection( &s->lock );
if (--s->read_count == 0) {
live = 0;
}
LeaveCriticalSection( &s->lock );
}
}
}
return captured;
}
static int
winaudio_in_read (SWVoiceIn *sw, void *buf, int len)
{
int ret = audio_pcm_sw_read (sw, buf, len);
if (ret > 0)
D("%s: (%d) returned %d\n", __FUNCTION__, len, ret);
return ret;
}
static int
winaudio_in_ctl (HWVoiceIn *hw, int cmd, ...)
{
WinAudioIn* s = (WinAudioIn*) hw;
switch (cmd) {
case VOICE_ENABLE:
D("%s: enable audio in\n", __FUNCTION__);
waveInStart( s->wavein );
break;
case VOICE_DISABLE:
D("%s: disable audio in\n", __FUNCTION__);
waveInStop( s->wavein );
break;
}
return 0;
}
/** AUDIO STATE
**/
typedef struct WinAudioState {
int dummy;
} WinAudioState;
static WinAudioState g_winaudio;
static void*
winaudio_init(void)
{
WinAudioState* s = &g_winaudio;
#if DEBUG
start_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
last_time = 0;
#endif
return s;
}
static void
winaudio_fini (void *opaque)
{
}
static struct audio_option winaudio_options[] = {
{"SAMPLES", AUD_OPT_INT, &conf.nb_samples,
"Size of Windows audio buffer in samples", NULL, 0},
{NULL, 0, NULL, NULL, NULL, 0}
};
static struct audio_pcm_ops winaudio_pcm_ops = {
winaudio_out_init,
winaudio_out_fini,
winaudio_out_run,
winaudio_out_write,
winaudio_out_ctl,
winaudio_in_init,
winaudio_in_fini,
winaudio_in_run,
winaudio_in_read,
winaudio_in_ctl
};
struct audio_driver win_audio_driver = {
INIT_FIELD (name = ) "winaudio",
INIT_FIELD (descr = ) "Windows wave audio",
INIT_FIELD (options = ) winaudio_options,
INIT_FIELD (init = ) winaudio_init,
INIT_FIELD (fini = ) winaudio_fini,
INIT_FIELD (pcm_ops = ) &winaudio_pcm_ops,
INIT_FIELD (can_be_default = ) 1,
INIT_FIELD (max_voices_out = ) 1,
INIT_FIELD (max_voices_in = ) 1,
INIT_FIELD (voice_size_out = ) sizeof (WinAudioOut),
INIT_FIELD (voice_size_in = ) sizeof (WinAudioIn)
};