| /* |
| * QEMU "simple" Windows audio driver |
| * |
| * Copyright (c) 2007 The Android Open Source Project |
| * Copyright (c) 2015 Intel Corporation |
| * |
| * 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. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu-common.h" |
| #include "sysemu/sysemu.h" |
| #include "audio.h" |
| |
| #define AUDIO_CAP "winaudio" |
| #include "audio_int.h" |
| |
| #include <windows.h> |
| #include <mmsystem.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, void* drv_opaque) |
| { |
| 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) { |
| 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, void* drv_opaque) |
| { |
| 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); |
| |
| 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 winaudio_audio_driver = { |
| .name = "winaudio", |
| .descr = "Windows wave audio", |
| .options = winaudio_options, |
| .init = winaudio_init, |
| .fini = winaudio_fini, |
| .pcm_ops = &winaudio_pcm_ops, |
| .can_be_default = 1, |
| .max_voices_out = 1, |
| .max_voices_in = 1, |
| .voice_size_out = sizeof (WinAudioOut), |
| .voice_size_in = sizeof (WinAudioIn) |
| }; |