diff options
author | Yu Ning <yu.ning@intel.com> | 2015-04-23 15:45:53 +0800 |
---|---|---|
committer | Yu Ning <yu.ning@intel.com> | 2015-04-28 17:59:54 +0800 |
commit | b6acf13d152b3326e8d985684500cf1d1ad0ca00 (patch) | |
tree | 660237d60cba5d6dd0454efdc8d0d1973752d41e | |
parent | 89035162e1125d75e1353e806a37f2c754e1273e (diff) | |
download | qemu-android-b6acf13d152b3326e8d985684500cf1d1ad0ca00.tar.gz |
audio: Import winaudio driver from the classic Android emulator
Import audio/winaudio.c from commit dcda949 of AOSP's external/qemu
project. Convert EOL of the file to UNIX format.
Change-Id: Ifb3de54648b6152210b4cfd96edbf747cae8b0e1
Signed-off-by: Yu Ning <yu.ning@intel.com>
-rw-r--r-- | audio/winaudio.c | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/audio/winaudio.c b/audio/winaudio.c new file mode 100644 index 0000000000..faaffb75fa --- /dev/null +++ b/audio/winaudio.c @@ -0,0 +1,665 @@ +/* + * 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) +}; |