diff --git a/Changelog b/Changelog index 3ba685bc21..ea7806544a 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,10 @@ +version 0.7.3: + + - Mac OS X cocoa improvements (Mike Kronenberg) + - DirectSound driver (malc) + - new audio options: '-soundhw' and 'audio-help' (malc) + - ES1370 PCI audio device (malc) + version 0.7.2: - x86_64 fixes (Win2000 and Linux 2.6 boot in 32 bit) diff --git a/Makefile.target b/Makefile.target index 1ca4e3a4c4..b1773f4ddb 100644 --- a/Makefile.target +++ b/Makefile.target @@ -262,7 +262,7 @@ endif VL_OBJS=vl.o osdep.o block.o readline.o monitor.o pci.o console.o VL_OBJS+=block-cow.o block-qcow.o aes.o block-vmdk.o block-cloop.o block-dmg.o block-bochs.o block-vpc.o block-vvfat.o -SOUND_HW = sb16.o +SOUND_HW = sb16.o es1370.o AUDIODRV = audio.o noaudio.o wavaudio.o ifdef CONFIG_SDL AUDIODRV += sdlaudio.o @@ -270,29 +270,38 @@ endif ifdef CONFIG_OSS AUDIODRV += ossaudio.o endif - -pc.o: DEFINES := -DUSE_SB16 $(DEFINES) - -ifdef CONFIG_ADLIB -SOUND_HW += fmopl.o adlib.o +ifdef CONFIG_COREAUDIO +AUDIODRV += coreaudio.o +endif +ifdef CONFIG_ALSA +AUDIODRV += alsaaudio.o +LIBS += -lasound +endif +ifdef CONFIG_DSOUND +AUDIODRV += dsoundaudio.o +LIBS += -lole32 -ldxguid endif - ifdef CONFIG_FMOD AUDIODRV += fmodaudio.o audio.o fmodaudio.o: DEFINES := -I$(CONFIG_FMOD_INC) $(DEFINES) LIBS += $(CONFIG_FMOD_LIB) endif +ifdef CONFIG_ADLIB +SOUND_HW += fmopl.o adlib.o +endif ifeq ($(TARGET_BASE_ARCH), i386) # Hardware support VL_OBJS+= ide.o ne2000.o pckbd.o vga.o $(SOUND_HW) dma.o $(AUDIODRV) VL_OBJS+= fdc.o mc146818rtc.o serial.o i8259.o i8254.o pc.o VL_OBJS+= cirrus_vga.o mixeng.o apic.o parallel.o +DEFINES += -DHAS_AUDIO endif ifeq ($(TARGET_BASE_ARCH), ppc) VL_OBJS+= ppc.o ide.o ne2000.o pckbd.o vga.o $(SOUND_HW) dma.o $(AUDIODRV) VL_OBJS+= mc146818rtc.o serial.o i8259.o i8254.o fdc.o m48t59.o VL_OBJS+= ppc_prep.o ppc_chrp.o cuda.o adb.o openpic.o heathrow_pic.o mixeng.o +DEFINES += -DHAS_AUDIO endif ifeq ($(TARGET_ARCH), mips) VL_OBJS+= mips_r4k.o dma.o vga.o serial.o ne2000.o i8254.o i8259.o @@ -317,7 +326,10 @@ VL_OBJS+=sdl.o endif ifdef CONFIG_COCOA VL_OBJS+=cocoa.o -COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa +COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa -framework IOKit +ifdef CONFIG_COREAUDIO +COCOA_LIBS+=-framework CoreAudio +endif endif ifdef CONFIG_SLIRP DEFINES+=-I$(SRC_PATH)/slirp @@ -349,6 +361,10 @@ ifeq ($(ARCH),ia64) VL_LDFLAGS+=-Wl,-G0 -Wl,-T,$(SRC_PATH)/ia64.ld endif +ifdef CONFIG_WIN32 +SDL_LIBS := $(filter-out -mwindows, $(SDL_LIBS)) -mconsole +endif + $(QEMU_SYSTEM): $(VL_OBJS) libqemu.a $(CC) $(VL_LDFLAGS) -o $@ $^ $(LIBS) $(SDL_LIBS) $(COCOA_LIBS) $(VL_LIBS) @@ -364,6 +380,9 @@ sdlaudio.o: sdlaudio.c depend: $(SRCS) $(CC) -MM $(CFLAGS) $(DEFINES) $^ 1>.depend +vldepend: $(VL_OBJS:.o=.c) + $(CC) -MM $(CFLAGS) $(DEFINES) $^ 1>.depend + # libqemu libqemu.a: $(LIBOBJS) @@ -415,8 +434,6 @@ op.o: op.c op_template.c op_mem.c op_helper.o: op_helper_mem.c endif -mixeng.o: mixeng.c mixeng.h mixeng_template.h - %.o: %.c $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< @@ -434,3 +451,9 @@ endif ifneq ($(wildcard .depend),) include .depend endif + +ifeq (0, 1) +audio.o sdlaudio.o dsoundaudio.o ossaudio.o wavaudio.o noaudio.o \ +fmodaudio.o alsaaudio.o mixeng.o: \ +CFLAGS := $(CFLAGS) -Wall -Werror -W -Wsign-compare +endif diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c new file mode 100644 index 0000000000..133690576e --- /dev/null +++ b/audio/alsaaudio.c @@ -0,0 +1,926 @@ +/* + * QEMU ALSA audio driver + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * 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 +#include "vl.h" + +#define AUDIO_CAP "alsa" +#include "audio_int.h" + +typedef struct ALSAVoiceOut { + HWVoiceOut hw; + void *pcm_buf; + snd_pcm_t *handle; + int can_pause; + int was_enabled; +} ALSAVoiceOut; + +typedef struct ALSAVoiceIn { + HWVoiceIn hw; + snd_pcm_t *handle; + void *pcm_buf; + int can_pause; +} ALSAVoiceIn; + +static struct { + int size_in_usec_in; + int size_in_usec_out; + const char *pcm_name_in; + const char *pcm_name_out; + unsigned int buffer_size_in; + unsigned int period_size_in; + unsigned int buffer_size_out; + unsigned int period_size_out; + unsigned int threshold; + + int buffer_size_in_overriden; + int period_size_in_overriden; + + int buffer_size_out_overriden; + int period_size_out_overriden; +} conf = { +#ifdef HIGH_LATENCY + .size_in_usec_in = 1, + .size_in_usec_out = 1, +#endif + .pcm_name_out = "hw:0,0", + .pcm_name_in = "hw:0,0", +#ifdef HIGH_LATENCY + .buffer_size_in = 400000, + .period_size_in = 400000 / 4, + .buffer_size_out = 400000, + .period_size_out = 400000 / 4, +#else +#define DEFAULT_BUFFER_SIZE 1024 +#define DEFAULT_PERIOD_SIZE 256 + .buffer_size_in = DEFAULT_BUFFER_SIZE, + .period_size_in = DEFAULT_PERIOD_SIZE, + .buffer_size_out = DEFAULT_BUFFER_SIZE, + .period_size_out = DEFAULT_PERIOD_SIZE, + .buffer_size_in_overriden = 0, + .buffer_size_out_overriden = 0, + .period_size_in_overriden = 0, + .period_size_out_overriden = 0, +#endif + .threshold = 0 +}; + +struct alsa_params_req { + int freq; + audfmt_e fmt; + int nchannels; + unsigned int buffer_size; + unsigned int period_size; +}; + +struct alsa_params_obt { + int freq; + audfmt_e fmt; + int nchannels; + int can_pause; + snd_pcm_uframes_t buffer_size; +}; + +static void GCC_FMT_ATTR (2, 3) alsa_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) alsa_logerr2 ( + int err, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void alsa_anal_close (snd_pcm_t **handlep) +{ + int err = snd_pcm_close (*handlep); + if (err) { + alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); + } + *handlep = NULL; +} + +static int alsa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int aud_to_alsafmt (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_S8: + return SND_PCM_FORMAT_S8; + + case AUD_FMT_U8: + return SND_PCM_FORMAT_U8; + + case AUD_FMT_S16: + return SND_PCM_FORMAT_S16_LE; + + case AUD_FMT_U16: + return SND_PCM_FORMAT_U16_LE; + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return SND_PCM_FORMAT_U8; + } +} + +static int alsa_to_audfmt (int alsafmt, audfmt_e *fmt, int *endianness) +{ + switch (alsafmt) { + case SND_PCM_FORMAT_S8: + *endianness = 0; + *fmt = AUD_FMT_S8; + break; + + case SND_PCM_FORMAT_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case SND_PCM_FORMAT_S16_LE: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_LE: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case SND_PCM_FORMAT_S16_BE: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_BE: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + + default: + dolog ("Unrecognized audio format %d\n", alsafmt); + return -1; + } + + return 0; +} + +#ifdef DEBUG_MISMATCHES +static void alsa_dump_info (struct alsa_params_req *req, + struct alsa_params_obt *obt) +{ + dolog ("parameter | requested value | obtained value\n"); + dolog ("format | %10d | %10d\n", req->fmt, obt->fmt); + dolog ("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); + dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); + dolog ("============================================\n"); + dolog ("requested: buffer size %d period size %d\n", + req->buffer_size, req->period_size); + dolog ("obtained: buffer size %ld\n", obt->buffer_size); +} +#endif + +static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) +{ + int err; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_sw_params_alloca (&sw_params); + + err = snd_pcm_sw_params_current (handle, sw_params); + if (err < 0) { + dolog ("Can not fully initialize DAC\n"); + alsa_logerr (err, "Failed to get current software parameters\n"); + return; + } + + err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); + if (err < 0) { + dolog ("Can not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software threshold to %ld\n", + threshold); + return; + } + + err = snd_pcm_sw_params (handle, sw_params); + if (err < 0) { + dolog ("Can not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software parameters\n"); + return; + } +} + +static int alsa_open (int in, struct alsa_params_req *req, + struct alsa_params_obt *obt, snd_pcm_t **handlep) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params; + int err, freq, nchannels; + const char *pcm_name = in ? conf.pcm_name_in : conf.pcm_name_out; + unsigned int period_size, buffer_size; + snd_pcm_uframes_t obt_buffer_size; + const char *typ = in ? "ADC" : "DAC"; + + freq = req->freq; + period_size = req->period_size; + buffer_size = req->buffer_size; + nchannels = req->nchannels; + + snd_pcm_hw_params_alloca (&hw_params); + + err = snd_pcm_open ( + &handle, + pcm_name, + in ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to open `%s':\n", pcm_name); + return -1; + } + + err = snd_pcm_hw_params_any (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_set_access ( + handle, + hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set access type\n"); + goto err; + } + + err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); + goto err; + } + + err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); + goto err; + } + + err = snd_pcm_hw_params_set_channels_near ( + handle, + hw_params, + &nchannels + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", + req->nchannels); + goto err; + } + + if (nchannels != 1 && nchannels != 2) { + alsa_logerr2 (err, typ, + "Can not handle obtained number of channels %d\n", + nchannels); + goto err; + } + + if (!((in && conf.size_in_usec_in) || (!in && conf.size_in_usec_out))) { + if (!buffer_size) { + buffer_size = DEFAULT_BUFFER_SIZE; + period_size= DEFAULT_PERIOD_SIZE; + } + } + + if (buffer_size) { + if ((in && conf.size_in_usec_in) || (!in && conf.size_in_usec_out)) { + if (period_size) { + err = snd_pcm_hw_params_set_period_time_near ( + handle, + hw_params, + &period_size, + 0); + if (err < 0) { + alsa_logerr2 (err, typ, + "Failed to set period time %d\n", + req->period_size); + goto err; + } + } + + err = snd_pcm_hw_params_set_buffer_time_near ( + handle, + hw_params, + &buffer_size, + 0); + + if (err < 0) { + alsa_logerr2 (err, typ, + "Failed to set buffer time %d\n", + req->buffer_size); + goto err; + } + } + else { + int dir; + snd_pcm_uframes_t minval; + + if (period_size) { + minval = period_size; + dir = 0; + + err = snd_pcm_hw_params_get_period_size_min ( + hw_params, + &minval, + &dir + ); + if (err < 0) { + alsa_logerr ( + err, + "Can not get minmal period size for %s\n", + typ + ); + } + else { + if (period_size < minval) { + if ((in && conf.period_size_in_overriden) + || (!in && conf.period_size_out_overriden)) { + dolog ("%s period size(%d) is less " + "than minmal period size(%ld)\n", + typ, + period_size, + minval); + } + period_size = minval; + } + } + + err = snd_pcm_hw_params_set_period_size ( + handle, + hw_params, + period_size, + 0 + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set period size %d\n", + req->period_size); + goto err; + } + } + + minval = buffer_size; + err = snd_pcm_hw_params_get_buffer_size_min ( + hw_params, + &minval + ); + if (err < 0) { + alsa_logerr (err, "Can not get minmal buffer size for %s\n", + typ); + } + else { + if (buffer_size < minval) { + if ((in && conf.buffer_size_in_overriden) + || (!in && conf.buffer_size_out_overriden)) { + dolog ( + "%s buffer size(%d) is less " + "than minimal buffer size(%ld)\n", + typ, + buffer_size, + minval + ); + } + buffer_size = minval; + } + } + + err = snd_pcm_hw_params_set_buffer_size ( + handle, + hw_params, + buffer_size + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set buffer size %d\n", + req->buffer_size); + goto err; + } + } + } + else { + dolog ("warning: buffer size is not set\n"); + } + + err = snd_pcm_hw_params (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to get buffer size\n"); + goto err; + } + + err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr2 (err, typ, "Can not prepare handle %p\n", handle); + goto err; + } + + obt->can_pause = snd_pcm_hw_params_can_pause (hw_params); + if (obt->can_pause < 0) { + alsa_logerr (err, "Can not get pause capability for %s\n", typ); + obt->can_pause = 0; + } + + if (!in && conf.threshold) { + snd_pcm_uframes_t threshold; + int bytes_per_sec; + + bytes_per_sec = freq + << (nchannels == 2) + << (req->fmt == AUD_FMT_S16 || req->fmt == AUD_FMT_U16); + + threshold = (conf.threshold * bytes_per_sec) / 1000; + alsa_set_threshold (handle, threshold); + } + + obt->fmt = req->fmt; + obt->nchannels = nchannels; + obt->freq = freq; + obt->buffer_size = snd_pcm_frames_to_bytes (handle, obt_buffer_size); + *handlep = handle; + + if (obt->fmt != req->fmt || + obt->nchannels != req->nchannels || + obt->freq != req->freq) { +#ifdef DEBUG_MISMATCHES + dolog ("Audio paramters mismatch for %s\n", typ); + alsa_dump_info (req, obt); +#endif + } + +#ifdef DEBUG + alsa_dump_info (req, obt); +#endif + return 0; + + err: + alsa_anal_close (&handle); + return -1; +} + +static int alsa_recover (snd_pcm_t *handle) +{ + int err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr (err, "Failed to prepare handle %p\n", handle); + return -1; + } + return 0; +} + +static int alsa_run_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + int rpos, live, decr; + int samples; + uint8_t *dst; + st_sample_t *src; + snd_pcm_sframes_t avail; + + live = audio_pcm_hw_get_live_out (hw); + if (!live) { + return 0; + } + + avail = snd_pcm_avail_update (alsa->handle); + if (avail < 0) { + if (avail == -EPIPE) { + if (!alsa_recover (alsa->handle)) { + avail = snd_pcm_avail_update (alsa->handle); + if (avail >= 0) { + goto ok; + } + } + } + + alsa_logerr (avail, "Can not get amount free space\n"); + return 0; + } + + ok: + decr = audio_MIN (live, avail); + samples = decr; + rpos = hw->rpos; + while (samples) { + int left_till_end_samples = hw->samples - rpos; + int convert_samples = audio_MIN (samples, left_till_end_samples); + snd_pcm_sframes_t written; + + src = hw->mix_buf + rpos; + dst = advance (alsa->pcm_buf, rpos << hw->info.shift); + + hw->clip (dst, src, convert_samples); + + again: + written = snd_pcm_writei (alsa->handle, dst, convert_samples); + + if (written < 0) { + switch (written) { + case -EPIPE: + if (!alsa_recover (alsa->handle)) { + goto again; + } + dolog ( + "Failed to write %d frames to %p, handle %p not prepared\n", + convert_samples, + dst, + alsa->handle + ); + goto exit; + + case -EAGAIN: + goto again; + + default: + alsa_logerr (written, "Failed to write %d frames to %p\n", + convert_samples, dst); + goto exit; + } + } + + mixeng_clear (src, written); + rpos = (rpos + written) % hw->samples; + samples -= written; + } + + exit: + hw->rpos = rpos; + return decr; +} + +static void alsa_fini_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + ldebug ("alsa_fini\n"); + alsa_anal_close (&alsa->handle); + + if (alsa->pcm_buf) { + qemu_free (alsa->pcm_buf); + alsa->pcm_buf = NULL; + } +} + +static int alsa_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + audfmt_e effective_fmt; + int endianness; + int err; + snd_pcm_t *handle; + + req.fmt = aud_to_alsafmt (fmt); + req.freq = freq; + req.nchannels = nchannels; + req.period_size = conf.period_size_out; + req.buffer_size = conf.buffer_size_out; + + if (alsa_open (0, &req, &obt, &handle)) { + return -1; + } + + err = alsa_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + alsa_anal_close (&handle); + return -1; + } + + audio_pcm_init_info ( + &hw->info, + obt.freq, + obt.nchannels, + effective_fmt, + audio_need_to_swap_endian (endianness) + ); + alsa->can_pause = obt.can_pause; + hw->bufsize = obt.buffer_size; + + alsa->pcm_buf = qemu_mallocz (hw->bufsize); + if (!alsa->pcm_buf) { + alsa_anal_close (&handle); + return -1; + } + + alsa->handle = handle; + alsa->was_enabled = 0; + return 0; +} + +static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + int err; + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + ldebug ("enabling voice\n"); + audio_pcm_info_clear_buf (&hw->info, alsa->pcm_buf, hw->samples); + if (alsa->can_pause) { + /* Why this was_enabled madness is needed at all?? */ + if (alsa->was_enabled) { + err = snd_pcm_pause (alsa->handle, 0); + if (err < 0) { + alsa_logerr (err, "Failed to resume playing\n"); + /* not fatal really */ + } + } + else { + alsa->was_enabled = 1; + } + } + break; + + case VOICE_DISABLE: + ldebug ("disabling voice\n"); + if (alsa->can_pause) { + err = snd_pcm_pause (alsa->handle, 1); + if (err < 0) { + alsa_logerr (err, "Failed to stop playing\n"); + /* not fatal really */ + } + } + break; + } + return 0; +} + +static int alsa_init_in (HWVoiceIn *hw, + int freq, int nchannels, audfmt_e fmt) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + int endianness; + int err; + audfmt_e effective_fmt; + snd_pcm_t *handle; + + req.fmt = aud_to_alsafmt (fmt); + req.freq = freq; + req.nchannels = nchannels; + req.period_size = conf.period_size_in; + req.buffer_size = conf.buffer_size_in; + + if (alsa_open (1, &req, &obt, &handle)) { + return -1; + } + + err = alsa_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + alsa_anal_close (&handle); + return -1; + } + + audio_pcm_init_info ( + &hw->info, + obt.freq, + obt.nchannels, + effective_fmt, + audio_need_to_swap_endian (endianness) + ); + alsa->can_pause = obt.can_pause; + hw->bufsize = obt.buffer_size; + alsa->pcm_buf = qemu_mallocz (hw->bufsize); + if (!alsa->pcm_buf) { + alsa_anal_close (&handle); + return -1; + } + + alsa->handle = handle; + return 0; +} + +static void alsa_fini_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + alsa_anal_close (&alsa->handle); + + if (alsa->pcm_buf) { + qemu_free (alsa->pcm_buf); + alsa->pcm_buf = NULL; + } +} + +static int alsa_run_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + int hwshift = hw->info.shift; + int i; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + struct { + int add; + int len; + } bufs[2] = { + { hw->wpos, 0 }, + { 0, 0 } + }; + + snd_pcm_uframes_t read_samples = 0; + + if (!dead) { + return 0; + } + + if (hw->wpos + dead > hw->samples) { + bufs[0].len = (hw->samples - hw->wpos); + bufs[1].len = (dead - (hw->samples - hw->wpos)); + } + else { + bufs[0].len = dead; + } + + + for (i = 0; i < 2; ++i) { + void *src; + st_sample_t *dst; + snd_pcm_sframes_t nread; + snd_pcm_uframes_t len; + + len = bufs[i].len; + + src = advance (alsa->pcm_buf, bufs[i].add << hwshift); + dst = hw->conv_buf + bufs[i].add; + + while (len) { + nread = snd_pcm_readi (alsa->handle, src, len); + + if (nread < 0) { + switch (nread) { + case -EPIPE: + if (!alsa_recover (alsa->handle)) { + continue; + } + dolog ( + "Failed to read %ld frames from %p, " + "handle %p not prepared\n", + len, + src, + alsa->handle + ); + goto exit; + + case -EAGAIN: + continue; + + default: + alsa_logerr ( + nread, + "Failed to read %ld frames from %p\n", + len, + src + ); + goto exit; + } + } + + hw->conv (dst, src, nread, &nominal_volume); + + src = advance (src, nread << hwshift); + dst += nread; + + read_samples += nread; + len -= nread; + } + } + + exit: + hw->wpos = (hw->wpos + read_samples) % hw->samples; + return read_samples; +} + +static int alsa_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static void *alsa_audio_init (void) +{ + return &conf; +} + +static void alsa_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option alsa_options[] = { + {"DAC_SIZE_IN_USEC", AUD_OPT_BOOL, &conf.size_in_usec_out, + "DAC period/buffer size in microseconds (otherwise in frames)", NULL, 0}, + {"DAC_PERIOD_SIZE", AUD_OPT_INT, &conf.period_size_out, + "DAC period size", &conf.period_size_out_overriden, 0}, + {"DAC_BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_size_out, + "DAC buffer size", &conf.buffer_size_out_overriden, 0}, + + {"ADC_SIZE_IN_USEC", AUD_OPT_BOOL, &conf.size_in_usec_in, + "ADC period/buffer size in microseconds (otherwise in frames)", NULL, 0}, + {"ADC_PERIOD_SIZE", AUD_OPT_INT, &conf.period_size_in, + "ADC period size", &conf.period_size_in_overriden, 0}, + {"ADC_BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_size_in, + "ADC buffer size", &conf.buffer_size_in_overriden, 0}, + + {"THRESHOLD", AUD_OPT_INT, &conf.threshold, + "(undocumented)", NULL, 0}, + + {"DAC_DEV", AUD_OPT_STR, &conf.pcm_name_out, + "DAC device name (for instance dmix)", NULL, 0}, + + {"ADC_DEV", AUD_OPT_STR, &conf.pcm_name_in, + "ADC device name", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} +}; + +static struct audio_pcm_ops alsa_pcm_ops = { + alsa_init_out, + alsa_fini_out, + alsa_run_out, + alsa_write, + alsa_ctl_out, + + alsa_init_in, + alsa_fini_in, + alsa_run_in, + alsa_read, + alsa_ctl_in +}; + +struct audio_driver alsa_audio_driver = { + INIT_FIELD (name = ) "alsa", + INIT_FIELD (descr = ) "ALSA http://www.alsa-project.org", + INIT_FIELD (options = ) alsa_options, + INIT_FIELD (init = ) alsa_audio_init, + INIT_FIELD (fini = ) alsa_audio_fini, + INIT_FIELD (pcm_ops = ) &alsa_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) INT_MAX, + INIT_FIELD (voice_size_out = ) sizeof (ALSAVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (ALSAVoiceIn) +}; diff --git a/audio/audio.c b/audio/audio.c index 0c0c8dd86f..1a3925d4e9 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -1,8 +1,8 @@ /* * QEMU Audio subsystem - * - * Copyright (c) 2003-2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * * 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 @@ -21,34 +21,78 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include #include "vl.h" -#define USE_WAV_AUDIO +#define AUDIO_CAP "audio" +#include "audio_int.h" -#include "audio/audio_int.h" +static void audio_pcm_hw_fini_in (HWVoiceIn *hw); +static void audio_pcm_hw_fini_out (HWVoiceOut *hw); -#define dolog(...) AUD_log ("audio", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) +static LIST_HEAD (hw_in_listhead, HWVoiceIn) hw_head_in; +static LIST_HEAD (hw_out_listhead, HWVoiceOut) hw_head_out; + +/* #define DEBUG_PLIVE */ +/* #define DEBUG_LIVE */ +/* #define DEBUG_OUT */ + +static struct audio_driver *drvtab[] = { +#ifdef CONFIG_OSS + &oss_audio_driver, #endif - -#define QC_AUDIO_DRV "QEMU_AUDIO_DRV" -#define QC_VOICES "QEMU_VOICES" -#define QC_FIXED_FORMAT "QEMU_FIXED_FORMAT" -#define QC_FIXED_FREQ "QEMU_FIXED_FREQ" - -static HWVoice *hw_voices; +#ifdef CONFIG_ALSA + &alsa_audio_driver, +#endif +#ifdef CONFIG_COREAUDIO + &coreaudio_audio_driver, +#endif +#ifdef CONFIG_DSOUND + &dsound_audio_driver, +#endif +#ifdef CONFIG_FMOD + &fmod_audio_driver, +#endif +#ifdef CONFIG_SDL + &sdl_audio_driver, +#endif + &no_audio_driver, + &wav_audio_driver +}; AudioState audio_state = { + /* Out */ 1, /* use fixed settings */ 44100, /* fixed frequency */ 2, /* fixed channels */ AUD_FMT_S16, /* fixed format */ 1, /* number of hw voices */ - -1 /* voice size */ + 1, /* greedy */ + + /* In */ + 1, /* use fixed settings */ + 44100, /* fixed frequency */ + 2, /* fixed channels */ + AUD_FMT_S16, /* fixed format */ + 1, /* number of hw voices */ + 1, /* greedy */ + + NULL, /* driver opaque */ + NULL, /* driver */ + + NULL, /* timer handle */ + { 0 }, /* period */ + 0 /* plive */ +}; + +volume_t nominal_volume = { + 0, +#ifdef FLOAT_MIXENG + 1.0, + 1.0 +#else + UINT_MAX, + UINT_MAX +#endif }; /* http://www.df.lth.se/~john_e/gems/gem002d.html */ @@ -68,70 +112,357 @@ inline uint32_t lsbindex (uint32_t u) return popcount ((u&-u)-1); } -int audio_get_conf_int (const char *key, int defval) +#ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED +#error No its not +#else +int audio_bug (const char *funcname, int cond) { - int val = defval; + if (cond) { + static int shown; + + AUD_log (NULL, "Error a bug that was just triggered in %s\n", funcname); + if (!shown) { + shown = 1; + AUD_log (NULL, "Save all your work and restart without audio\n"); + AUD_log (NULL, "Please send bug report to malc@pulsesoft.com\n"); + AUD_log (NULL, "I am sorry\n"); + } + AUD_log (NULL, "Context:\n"); + +#if defined AUDIO_BREAKPOINT_ON_BUG +# if defined HOST_I386 +# if defined __GNUC__ + __asm__ ("int3"); +# elif defined _MSC_VER + _asm _emit 0xcc; +# else + abort (); +# endif +# else + abort (); +# endif +#endif + } + + return cond; +} +#endif + +static char *audio_alloc_prefix (const char *s) +{ + const char qemu_prefix[] = "QEMU_"; + size_t len; + char *r; + + if (!s) { + return NULL; + } + + len = strlen (s); + r = qemu_malloc (len + sizeof (qemu_prefix)); + + if (r) { + size_t i; + char *u = r + sizeof (qemu_prefix) - 1; + + strcpy (r, qemu_prefix); + strcat (r, s); + + for (i = 0; i < len; ++i) { + u[i] = toupper (u[i]); + } + } + return r; +} + +const char *audio_audfmt_to_string (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_U8: + return "U8"; + + case AUD_FMT_U16: + return "U16"; + + case AUD_FMT_S8: + return "S8"; + + case AUD_FMT_S16: + return "S16"; + } + + dolog ("Bogus audfmt %d returning S16\n", fmt); + return "S16"; +} + +audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, int *defaultp) +{ + if (!strcasecmp (s, "u8")) { + *defaultp = 0; + return AUD_FMT_U8; + } + else if (!strcasecmp (s, "u16")) { + *defaultp = 0; + return AUD_FMT_U16; + } + else if (!strcasecmp (s, "s8")) { + *defaultp = 0; + return AUD_FMT_S8; + } + else if (!strcasecmp (s, "s16")) { + *defaultp = 0; + return AUD_FMT_S16; + } + else { + dolog ("Bogus audio format `%s' using %s\n", + s, audio_audfmt_to_string (defval)); + *defaultp = 1; + return defval; + } +} + +static audfmt_e audio_get_conf_fmt (const char *envname, + audfmt_e defval, + int *defaultp) +{ + const char *var = getenv (envname); + if (!var) { + *defaultp = 1; + return defval; + } + return audio_string_to_audfmt (var, defval, defaultp); +} + +static int audio_get_conf_int (const char *key, int defval, int *defaultp) +{ + int val; char *strval; strval = getenv (key); if (strval) { + *defaultp = 0; val = atoi (strval); + return val; + } + else { + *defaultp = 1; + return defval; } - - return val; } -const char *audio_get_conf_str (const char *key, const char *defval) +static const char *audio_get_conf_str (const char *key, + const char *defval, + int *defaultp) { const char *val = getenv (key); - if (!val) + if (!val) { + *defaultp = 1; return defval; - else + } + else { + *defaultp = 0; return val; + } } void AUD_log (const char *cap, const char *fmt, ...) { va_list ap; - fprintf (stderr, "%s: ", cap); + if (cap) { + fprintf (stderr, "%s: ", cap); + } va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); } -/* - * Soft Voice - */ -void pcm_sw_free_resources (SWVoice *sw) +void AUD_vlog (const char *cap, const char *fmt, va_list ap) { - if (sw->buf) qemu_free (sw->buf); - if (sw->rate) st_rate_stop (sw->rate); - sw->buf = NULL; - sw->rate = NULL; -} - -int pcm_sw_alloc_resources (SWVoice *sw) -{ - sw->buf = qemu_mallocz (sw->hw->samples * sizeof (st_sample_t)); - if (!sw->buf) - return -1; - - sw->rate = st_rate_start (sw->freq, sw->hw->freq); - if (!sw->rate) { - qemu_free (sw->buf); - sw->buf = NULL; - return -1; + if (cap) { + fprintf (stderr, "%s: ", cap); } - return 0; + vfprintf (stderr, fmt, ap); } -void pcm_sw_fini (SWVoice *sw) +static void audio_print_options (const char *prefix, + struct audio_option *opt) { - pcm_sw_free_resources (sw); + char *uprefix; + + if (!prefix) { + dolog ("No prefix specified\n"); + return; + } + + if (!opt) { + dolog ("No options\n"); + return; + } + + uprefix = audio_alloc_prefix (prefix); + + for (; opt->name; opt++) { + const char *state = "default"; + printf (" %s_%s: ", uprefix, opt->name); + + if (opt->overridenp && *opt->overridenp) { + state = "current"; + } + + switch (opt->tag) { + case AUD_OPT_BOOL: + { + int *intp = opt->valp; + printf ("boolean, %s = %d\n", state, *intp ? 1 : 0); + } + break; + + case AUD_OPT_INT: + { + int *intp = opt->valp; + printf ("integer, %s = %d\n", state, *intp); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + printf ( + "format, %s = %s, (one of: U8 S8 U16 S16)\n", + state, + audio_audfmt_to_string (*fmtp) + ); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + printf ("string, %s = %s\n", + state, + *strp ? *strp : "(not set)"); + } + break; + + default: + printf ("???\n"); + dolog ("Bad value tag for option %s_%s %d\n", + uprefix, opt->name, opt->tag); + break; + } + printf (" %s\n", opt->descr); + } + + qemu_free (uprefix); } -int pcm_sw_init (SWVoice *sw, HWVoice *hw, int freq, - int nchannels, audfmt_e fmt) +static void audio_process_options (const char *prefix, + struct audio_option *opt) +{ + char *optname; + const char qemu_prefix[] = "QEMU_"; + size_t preflen; + + if (audio_bug (AUDIO_FUNC, !prefix)) { + dolog ("prefix = NULL\n"); + return; + } + + if (audio_bug (AUDIO_FUNC, !opt)) { + dolog ("opt = NULL\n"); + return; + } + + preflen = strlen (prefix); + + for (; opt->name; opt++) { + size_t len, i; + int def; + + if (!opt->valp) { + dolog ("Option value pointer for `%s' is not set\n", + opt->name); + continue; + } + + len = strlen (opt->name); + optname = qemu_malloc (len + preflen + sizeof (qemu_prefix) + 1); + if (!optname) { + dolog ("Can not allocate memory for option name `%s'\n", + opt->name); + continue; + } + + strcpy (optname, qemu_prefix); + for (i = 0; i <= preflen; ++i) { + optname[i + sizeof (qemu_prefix) - 1] = toupper (prefix[i]); + } + strcat (optname, "_"); + strcat (optname, opt->name); + + def = 1; + switch (opt->tag) { + case AUD_OPT_BOOL: + case AUD_OPT_INT: + { + int *intp = opt->valp; + *intp = audio_get_conf_int (optname, *intp, &def); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + *fmtp = audio_get_conf_fmt (optname, *fmtp, &def); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + *strp = audio_get_conf_str (optname, *strp, &def); + } + break; + + default: + dolog ("Bad value tag for option `%s' - %d\n", + optname, opt->tag); + break; + } + + if (!opt->overridenp) { + opt->overridenp = &opt->overriden; + } + *opt->overridenp = !def; + qemu_free (optname); + } +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, int freq, + int nchannels, audfmt_e fmt) +{ + int bits = 8, sign = 0; + + switch (fmt) { + case AUD_FMT_S8: + sign = 1; + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + sign = 1; + case AUD_FMT_U16: + bits = 16; + break; + } + return info->freq == freq + && info->nchannels == nchannels + && info->sign == sign + && info->bits == bits; +} + +void audio_pcm_init_info (struct audio_pcm_info *info, int freq, + int nchannels, audfmt_e fmt, int swap_endian) { int bits = 8, sign = 0; @@ -148,626 +479,595 @@ int pcm_sw_init (SWVoice *sw, HWVoice *hw, int freq, break; } - sw->hw = hw; - sw->freq = freq; - sw->fmt = fmt; - sw->nchannels = nchannels; - sw->shift = (nchannels == 2) + (bits == 16); - sw->align = (1 << sw->shift) - 1; - sw->left = 0; - sw->pos = 0; - sw->wpos = 0; - sw->live = 0; - sw->ratio = (sw->hw->freq * ((int64_t) INT_MAX)) / sw->freq; - sw->bytes_per_second = sw->freq << sw->shift; - sw->conv = mixeng_conv[nchannels == 2][sign][bits == 16]; - - pcm_sw_free_resources (sw); - return pcm_sw_alloc_resources (sw); + info->freq = freq; + info->bits = bits; + info->sign = sign; + info->nchannels = nchannels; + info->shift = (nchannels == 2) + (bits == 16); + info->align = (1 << info->shift) - 1; + info->bytes_per_second = info->freq << info->shift; + info->swap_endian = swap_endian; } -/* Hard voice */ -void pcm_hw_free_resources (HWVoice *hw) +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) { - if (hw->mix_buf) - qemu_free (hw->mix_buf); - hw->mix_buf = NULL; + if (!len) { + return; + } + + if (info->sign) { + memset (buf, len << info->shift, 0x00); + } + else { + if (info->bits == 8) { + memset (buf, len << info->shift, 0x80); + } + else { + int i; + uint16_t *p = buf; + int shift = info->nchannels - 1; + short s = INT16_MAX; + + if (info->swap_endian) { + s = bswap16 (s); + } + + for (i = 0; i < len << shift; i++) { + p[i] = s; + } + } + } } -int pcm_hw_alloc_resources (HWVoice *hw) +/* + * Hard voice (capture) + */ +static void audio_pcm_hw_free_resources_in (HWVoiceIn *hw) { - hw->mix_buf = qemu_mallocz (hw->samples * sizeof (st_sample_t)); - if (!hw->mix_buf) + if (hw->conv_buf) { + qemu_free (hw->conv_buf); + } + hw->conv_buf = NULL; +} + +static int audio_pcm_hw_alloc_resources_in (HWVoiceIn *hw) +{ + hw->conv_buf = qemu_mallocz (hw->samples * sizeof (st_sample_t)); + if (!hw->conv_buf) { return -1; + } return 0; } -void pcm_hw_fini (HWVoice *hw) +static int audio_pcm_hw_init_in (HWVoiceIn *hw, int freq, int nchannels, audfmt_e fmt) { - if (hw->active) { - ldebug ("pcm_hw_fini: %d %d %d\n", hw->freq, hw->nchannels, hw->fmt); - pcm_hw_free_resources (hw); - hw->pcm_ops->fini (hw); - memset (hw, 0, audio_state.drv->voice_size); - } -} + audio_pcm_hw_fini_in (hw); -void pcm_hw_gc (HWVoice *hw) -{ - if (hw->nb_voices) - return; - - pcm_hw_fini (hw); -} - -int pcm_hw_get_live (HWVoice *hw) -{ - int i, alive = 0, live = hw->samples; - - for (i = 0; i < hw->nb_voices; i++) { - if (hw->pvoice[i]->live) { - live = audio_MIN (hw->pvoice[i]->live, live); - alive += 1; - } - } - - if (alive) - return live; - else + if (hw->pcm_ops->init_in (hw, freq, nchannels, fmt)) { + memset (hw, 0, audio_state.drv->voice_size_in); return -1; -} - -int pcm_hw_get_live2 (HWVoice *hw, int *nb_active) -{ - int i, alive = 0, live = hw->samples; - - *nb_active = 0; - for (i = 0; i < hw->nb_voices; i++) { - if (hw->pvoice[i]->live) { - if (hw->pvoice[i]->live < live) { - *nb_active = hw->pvoice[i]->active != 0; - live = hw->pvoice[i]->live; - } - alive += 1; - } } - - if (alive) - return live; - else + LIST_INIT (&hw->sw_head); + hw->active = 1; + hw->samples = hw->bufsize >> hw->info.shift; + hw->conv = + mixeng_conv + [nchannels == 2] + [hw->info.sign] + [hw->info.swap_endian] + [hw->info.bits == 16]; + if (audio_pcm_hw_alloc_resources_in (hw)) { + audio_pcm_hw_free_resources_in (hw); return -1; + } + return 0; } -void pcm_hw_dec_live (HWVoice *hw, int decr) +static uint64_t audio_pcm_hw_find_min_in (HWVoiceIn *hw) { - int i; + SWVoiceIn *sw; + int m = hw->total_samples_captured; - for (i = 0; i < hw->nb_voices; i++) { - if (hw->pvoice[i]->live) { - hw->pvoice[i]->live -= decr; + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + m = audio_MIN (m, sw->total_hw_samples_acquired); } } + return m; } -void pcm_hw_clear (HWVoice *hw, void *buf, int len) +int audio_pcm_hw_get_live_in (HWVoiceIn *hw) { - if (!len) - return; + int live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; +} - switch (hw->fmt) { - case AUD_FMT_S16: - case AUD_FMT_S8: - memset (buf, len << hw->shift, 0x00); - break; +/* + * Soft voice (capture) + */ +static void audio_pcm_sw_free_resources_in (SWVoiceIn *sw) +{ + if (sw->conv_buf) { + qemu_free (sw->conv_buf); + } - case AUD_FMT_U8: - memset (buf, len << hw->shift, 0x80); - break; + if (sw->rate) { + st_rate_stop (sw->rate); + } - case AUD_FMT_U16: - { - unsigned int i; - uint16_t *p = buf; - int shift = hw->nchannels - 1; + sw->conv_buf = NULL; + sw->rate = NULL; +} - for (i = 0; i < len << shift; i++) { - p[i] = INT16_MAX; - } - } - break; +static int audio_pcm_sw_alloc_resources_in (SWVoiceIn *sw) +{ + int samples = ((int64_t) sw->hw->samples << 32) / sw->ratio; + sw->conv_buf = qemu_mallocz (samples * sizeof (st_sample_t)); + if (!sw->conv_buf) { + return -1; + } + + sw->rate = st_rate_start (sw->hw->info.freq, sw->info.freq); + if (!sw->rate) { + qemu_free (sw->conv_buf); + sw->conv_buf = NULL; + return -1; + } + return 0; +} + +static int audio_pcm_sw_init_in (SWVoiceIn *sw, HWVoiceIn *hw, const char *name, + int freq, int nchannels, audfmt_e fmt) +{ + audio_pcm_init_info (&sw->info, freq, nchannels, fmt, + /* None of the cards emulated by QEMU are big-endian + hence following shortcut */ + audio_need_to_swap_endian (0)); + sw->hw = hw; + sw->ratio = ((int64_t) sw->info.freq << 32) / sw->hw->info.freq; + + sw->clip = + mixeng_clip + [nchannels == 2] + [sw->info.sign] + [sw->info.swap_endian] + [sw->info.bits == 16]; + + sw->name = qemu_strdup (name); + audio_pcm_sw_free_resources_in (sw); + return audio_pcm_sw_alloc_resources_in (sw); +} + +static int audio_pcm_sw_get_rpos_in (SWVoiceIn *sw) +{ + HWVoiceIn *hw = sw->hw; + int live = hw->total_samples_captured - sw->total_hw_samples_acquired; + int rpos; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + + rpos = hw->wpos - live; + if (rpos >= 0) { + return rpos; + } + else { + return hw->samples + rpos; } } -int pcm_hw_write (SWVoice *sw, void *buf, int size) +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int size) { - int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck; - int ret = 0, pos = 0; - if (!sw) - return size; + HWVoiceIn *hw = sw->hw; + int samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0; + st_sample_t *src, *dst = sw->conv_buf; - hwsamples = sw->hw->samples; - samples = size >> sw->shift; + rpos = audio_pcm_sw_get_rpos_in (sw) % hw->samples; - if (!sw->live) { - sw->wpos = sw->hw->rpos; + live = hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live_in=%d hw->samples=%d\n", live, hw->samples); + return 0; } - wpos = sw->wpos; - live = sw->live; - dead = hwsamples - live; - swlim = (dead * ((int64_t) INT_MAX)) / sw->ratio; + + samples = size >> sw->info.shift; + if (!live) { + return 0; + } + + swlim = (live * sw->ratio) >> 32; swlim = audio_MIN (swlim, samples); - ldebug ("size=%d live=%d dead=%d swlim=%d wpos=%d\n", - size, live, dead, swlim, wpos); - if (swlim) - sw->conv (sw->buf, buf, swlim); + while (swlim) { + src = hw->conv_buf + rpos; + isamp = hw->wpos - rpos; + /* XXX: <= ? */ + if (isamp <= 0) { + isamp = hw->samples - rpos; + } + + if (!isamp) { + break; + } + osamp = swlim; + + if (audio_bug (AUDIO_FUNC, osamp < 0)) { + dolog ("osamp=%d\n", osamp); + } + + st_rate_flow (sw->rate, src, dst, &isamp, &osamp); + swlim -= osamp; + rpos = (rpos + isamp) % hw->samples; + dst += osamp; + ret += osamp; + total += isamp; + } + + sw->clip (buf, sw->conv_buf, ret); + sw->total_hw_samples_acquired += total; + return ret << sw->info.shift; +} + +/* + * Hard voice (playback) + */ +static int audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) +{ + SWVoiceOut *sw; + int m = INT_MAX; + int nb_live = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active || !sw->empty) { + m = audio_MIN (m, sw->total_hw_samples_mixed); + nb_live += 1; + } + } + + *nb_livep = nb_live; + return m; +} + +static void audio_pcm_hw_free_resources_out (HWVoiceOut *hw) +{ + if (hw->mix_buf) { + qemu_free (hw->mix_buf); + } + + hw->mix_buf = NULL; +} + +static int audio_pcm_hw_alloc_resources_out (HWVoiceOut *hw) +{ + hw->mix_buf = qemu_mallocz (hw->samples * sizeof (st_sample_t)); + if (!hw->mix_buf) { + return -1; + } + + return 0; +} + +static int audio_pcm_hw_init_out (HWVoiceOut *hw, int freq, + int nchannels, audfmt_e fmt) +{ + audio_pcm_hw_fini_out (hw); + if (hw->pcm_ops->init_out (hw, freq, nchannels, fmt)) { + memset (hw, 0, audio_state.drv->voice_size_out); + return -1; + } + + LIST_INIT (&hw->sw_head); + hw->active = 1; + hw->samples = hw->bufsize >> hw->info.shift; + hw->clip = + mixeng_clip + [nchannels == 2] + [hw->info.sign] + [hw->info.swap_endian] + [hw->info.bits == 16]; + if (audio_pcm_hw_alloc_resources_out (hw)) { + audio_pcm_hw_fini_out (hw); + return -1; + } + return 0; +} + +int audio_pcm_hw_get_live_out2 (HWVoiceOut *hw, int *nb_live) +{ + int smin; + + smin = audio_pcm_hw_find_min_out (hw, nb_live); + + if (!*nb_live) { + return 0; + } + else { + int live = smin; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; + } +} + +int audio_pcm_hw_get_live_out (HWVoiceOut *hw) +{ + int nb_live; + int live; + + live = audio_pcm_hw_get_live_out2 (hw, &nb_live); + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; +} + +/* + * Soft voice (playback) + */ +static void audio_pcm_sw_free_resources_out (SWVoiceOut *sw) +{ + if (sw->buf) { + qemu_free (sw->buf); + } + + if (sw->rate) { + st_rate_stop (sw->rate); + } + + sw->buf = NULL; + sw->rate = NULL; +} + +static int audio_pcm_sw_alloc_resources_out (SWVoiceOut *sw) +{ + sw->buf = qemu_mallocz (sw->hw->samples * sizeof (st_sample_t)); + if (!sw->buf) { + return -1; + } + + sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq); + if (!sw->rate) { + qemu_free (sw->buf); + sw->buf = NULL; + return -1; + } + return 0; +} + +static int audio_pcm_sw_init_out (SWVoiceOut *sw, HWVoiceOut *hw, + const char *name, int freq, + int nchannels, audfmt_e fmt) +{ + audio_pcm_init_info (&sw->info, freq, nchannels, fmt, + /* None of the cards emulated by QEMU are big-endian + hence following shortcut */ + audio_need_to_swap_endian (0)); + sw->hw = hw; + sw->empty = 1; + sw->active = 0; + sw->ratio = ((int64_t) sw->hw->info.freq << 32) / sw->info.freq; + sw->total_hw_samples_mixed = 0; + + sw->conv = + mixeng_conv + [nchannels == 2] + [sw->info.sign] + [sw->info.swap_endian] + [sw->info.bits == 16]; + sw->name = qemu_strdup (name); + + audio_pcm_sw_free_resources_out (sw); + return audio_pcm_sw_alloc_resources_out (sw); +} + +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size) +{ + int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck; + int ret = 0, pos = 0, total = 0; + + if (!sw) { + return size; + } + + hwsamples = sw->hw->samples; + + live = sw->total_hw_samples_mixed; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hwsamples)){ + dolog ("live=%d hw->samples=%d\n", live, hwsamples); + return 0; + } + + if (live == hwsamples) { + return 0; + } + + wpos = (sw->hw->rpos + live) % hwsamples; + samples = size >> sw->info.shift; + + dead = hwsamples - live; + swlim = ((int64_t) dead << 32) / sw->ratio; + swlim = audio_MIN (swlim, samples); + if (swlim) { + sw->conv (sw->buf, buf, swlim, &sw->vol); + } while (swlim) { dead = hwsamples - live; left = hwsamples - wpos; blck = audio_MIN (dead, left); if (!blck) { - /* dolog ("swlim=%d\n", swlim); */ break; } isamp = swlim; osamp = blck; - st_rate_flow (sw->rate, sw->buf + pos, sw->hw->mix_buf + wpos, &isamp, &osamp); + st_rate_flow_mix ( + sw->rate, + sw->buf + pos, + sw->hw->mix_buf + wpos, + &isamp, + &osamp + ); ret += isamp; swlim -= isamp; pos += isamp; live += osamp; wpos = (wpos + osamp) % hwsamples; + total += osamp; } - sw->wpos = wpos; - sw->live = live; - return ret << sw->shift; + sw->total_hw_samples_mixed += total; + sw->empty = sw->total_hw_samples_mixed == 0; + +#ifdef DEBUG_OUT + dolog ( + "%s: write size %d ret %d total sw %d, hw %d\n", + sw->name, + size >> sw->info.shift, + ret, + sw->total_hw_samples_mixed, + sw->hw->total_samples_played + ); +#endif + + return ret << sw->info.shift; } -int pcm_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +#ifdef DEBUG_AUDIO +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info) { - int sign = 0, bits = 8; - - pcm_hw_fini (hw); - ldebug ("pcm_hw_init: %d %d %d\n", freq, nchannels, fmt); - if (hw->pcm_ops->init (hw, freq, nchannels, fmt)) { - memset (hw, 0, audio_state.drv->voice_size); - return -1; - } - - switch (hw->fmt) { - case AUD_FMT_S8: - sign = 1; - case AUD_FMT_U8: - break; - - case AUD_FMT_S16: - sign = 1; - case AUD_FMT_U16: - bits = 16; - break; - } - - hw->nb_voices = 0; - hw->active = 1; - hw->shift = (hw->nchannels == 2) + (bits == 16); - hw->bytes_per_second = hw->freq << hw->shift; - hw->align = (1 << hw->shift) - 1; - hw->samples = hw->bufsize >> hw->shift; - hw->clip = mixeng_clip[hw->nchannels == 2][sign][bits == 16]; - if (pcm_hw_alloc_resources (hw)) { - pcm_hw_fini (hw); - return -1; - } - return 0; + dolog ("%s: bits %d, sign %d, freq %d, nchan %d\n", + cap, info->bits, info->sign, info->freq, info->nchannels); } +#endif -static int dist (void *hw) -{ - if (hw) { - return (((uint8_t *) hw - (uint8_t *) hw_voices) - / audio_state.drv->voice_size) + 1; - } - else { - return 0; - } -} +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" -#define ADVANCE(hw) \ - ((hw) ? advance (hw, audio_state.drv->voice_size) : hw_voices) - -HWVoice *pcm_hw_find_any (HWVoice *hw) -{ - int i = dist (hw); - for (; i < audio_state.nb_hw_voices; i++) { - hw = ADVANCE (hw); - return hw; - } - return NULL; -} - -HWVoice *pcm_hw_find_any_active (HWVoice *hw) -{ - int i = dist (hw); - for (; i < audio_state.nb_hw_voices; i++) { - hw = ADVANCE (hw); - if (hw->active) - return hw; - } - return NULL; -} - -HWVoice *pcm_hw_find_any_active_enabled (HWVoice *hw) -{ - int i = dist (hw); - for (; i < audio_state.nb_hw_voices; i++) { - hw = ADVANCE (hw); - if (hw->active && hw->enabled) - return hw; - } - return NULL; -} - -HWVoice *pcm_hw_find_any_passive (HWVoice *hw) -{ - int i = dist (hw); - for (; i < audio_state.nb_hw_voices; i++) { - hw = ADVANCE (hw); - if (!hw->active) - return hw; - } - return NULL; -} - -HWVoice *pcm_hw_find_specific (HWVoice *hw, int freq, - int nchannels, audfmt_e fmt) -{ - while ((hw = pcm_hw_find_any_active (hw))) { - if (hw->freq == freq && - hw->nchannels == nchannels && - hw->fmt == fmt) - return hw; - } - return NULL; -} - -HWVoice *pcm_hw_add (int freq, int nchannels, audfmt_e fmt) -{ - HWVoice *hw; - - if (audio_state.fixed_format) { - freq = audio_state.fixed_freq; - nchannels = audio_state.fixed_channels; - fmt = audio_state.fixed_fmt; - } - - hw = pcm_hw_find_specific (NULL, freq, nchannels, fmt); - - if (hw) - return hw; - - hw = pcm_hw_find_any_passive (NULL); - if (hw) { - hw->pcm_ops = audio_state.drv->pcm_ops; - if (!hw->pcm_ops) - return NULL; - - if (pcm_hw_init (hw, freq, nchannels, fmt)) { - pcm_hw_gc (hw); - return NULL; - } - else - return hw; - } - - return pcm_hw_find_any (NULL); -} - -int pcm_hw_add_sw (HWVoice *hw, SWVoice *sw) -{ - SWVoice **pvoice = qemu_mallocz ((hw->nb_voices + 1) * sizeof (sw)); - if (!pvoice) - return -1; - - memcpy (pvoice, hw->pvoice, hw->nb_voices * sizeof (sw)); - qemu_free (hw->pvoice); - hw->pvoice = pvoice; - hw->pvoice[hw->nb_voices++] = sw; - return 0; -} - -int pcm_hw_del_sw (HWVoice *hw, SWVoice *sw) -{ - int i, j; - if (hw->nb_voices > 1) { - SWVoice **pvoice = qemu_mallocz ((hw->nb_voices - 1) * sizeof (sw)); - - if (!pvoice) { - dolog ("Can not maintain consistent state (not enough memory)\n"); - return -1; - } - - for (i = 0, j = 0; i < hw->nb_voices; i++) { - if (j >= hw->nb_voices - 1) { - dolog ("Can not maintain consistent state " - "(invariant violated)\n"); - return -1; - } - if (hw->pvoice[i] != sw) - pvoice[j++] = hw->pvoice[i]; - } - qemu_free (hw->pvoice); - hw->pvoice = pvoice; - hw->nb_voices -= 1; - } - else { - qemu_free (hw->pvoice); - hw->pvoice = NULL; - hw->nb_voices = 0; - } - return 0; -} - -SWVoice *pcm_create_voice_pair (int freq, int nchannels, audfmt_e fmt) -{ - SWVoice *sw; - HWVoice *hw; - - sw = qemu_mallocz (sizeof (*sw)); - if (!sw) - goto err1; - - hw = pcm_hw_add (freq, nchannels, fmt); - if (!hw) - goto err2; - - if (pcm_hw_add_sw (hw, sw)) - goto err3; - - if (pcm_sw_init (sw, hw, freq, nchannels, fmt)) - goto err4; - - return sw; - -err4: - pcm_hw_del_sw (hw, sw); -err3: - pcm_hw_gc (hw); -err2: - qemu_free (sw); -err1: - return NULL; -} - -SWVoice *AUD_open (SWVoice *sw, const char *name, - int freq, int nchannels, audfmt_e fmt) -{ - if (!audio_state.drv) { - return NULL; - } - - if (sw && freq == sw->freq && sw->nchannels == nchannels && sw->fmt == fmt) { - return sw; - } - - if (sw) { - ldebug ("Different format %s %d %d %d\n", - name, - sw->freq == freq, - sw->nchannels == nchannels, - sw->fmt == fmt); - } - - if (nchannels != 1 && nchannels != 2) { - dolog ("Bogus channel count %d for voice %s\n", nchannels, name); - return NULL; - } - - if (!audio_state.fixed_format && sw) { - pcm_sw_fini (sw); - pcm_hw_del_sw (sw->hw, sw); - pcm_hw_gc (sw->hw); - if (sw->name) { - qemu_free (sw->name); - sw->name = NULL; - } - qemu_free (sw); - sw = NULL; - } - - if (sw) { - HWVoice *hw = sw->hw; - if (!hw) { - dolog ("Internal logic error voice %s has no hardware store\n", - name); - return sw; - } - - if (pcm_sw_init (sw, hw, freq, nchannels, fmt)) { - pcm_sw_fini (sw); - pcm_hw_del_sw (hw, sw); - pcm_hw_gc (hw); - if (sw->name) { - qemu_free (sw->name); - sw->name = NULL; - } - qemu_free (sw); - return NULL; - } - } - else { - sw = pcm_create_voice_pair (freq, nchannels, fmt); - if (!sw) { - dolog ("Failed to create voice %s\n", name); - return NULL; - } - } - - if (sw->name) { - qemu_free (sw->name); - sw->name = NULL; - } - sw->name = qemu_strdup (name); - return sw; -} - -void AUD_close (SWVoice *sw) -{ - if (!sw) - return; - - pcm_sw_fini (sw); - pcm_hw_del_sw (sw->hw, sw); - pcm_hw_gc (sw->hw); - if (sw->name) { - qemu_free (sw->name); - sw->name = NULL; - } - qemu_free (sw); -} - -int AUD_write (SWVoice *sw, void *buf, int size) +int AUD_write (SWVoiceOut *sw, void *buf, int size) { int bytes; - if (!sw->hw->enabled) + if (!sw) { + /* XXX: Consider options */ + return size; + } + + if (!sw->hw->enabled) { dolog ("Writing to disabled voice %s\n", sw->name); + return 0; + } + bytes = sw->hw->pcm_ops->write (sw, buf, size); return bytes; } -void AUD_run (void) +int AUD_read (SWVoiceIn *sw, void *buf, int size) { - HWVoice *hw = NULL; + int bytes; - while ((hw = pcm_hw_find_any_active_enabled (hw))) { - int i; - if (hw->pending_disable && pcm_hw_get_live (hw) <= 0) { - hw->enabled = 0; - hw->pcm_ops->ctl (hw, VOICE_DISABLE); - for (i = 0; i < hw->nb_voices; i++) { - hw->pvoice[i]->live = 0; - /* hw->pvoice[i]->old_ticks = 0; */ - } - continue; - } - - hw->pcm_ops->run (hw); - assert (hw->rpos < hw->samples); - for (i = 0; i < hw->nb_voices; i++) { - SWVoice *sw = hw->pvoice[i]; - if (!sw->active && !sw->live && sw->old_ticks) { - int64_t delta = qemu_get_clock (vm_clock) - sw->old_ticks; - if (delta > audio_state.ticks_threshold) { - ldebug ("resetting old_ticks for %s\n", sw->name); - sw->old_ticks = 0; - } - } - } + if (!sw) { + /* XXX: Consider options */ + return size; } + + if (!sw->hw->enabled) { + dolog ("Reading from disabled voice %s\n", sw->name); + return 0; + } + + bytes = sw->hw->pcm_ops->read (sw, buf, size); + return bytes; } -int AUD_get_free (SWVoice *sw) -{ - int free; - - if (!sw) - return 4096; - - free = ((sw->hw->samples - sw->live) << sw->hw->shift) * sw->ratio - / INT_MAX; - - free &= ~sw->hw->align; - if (!free) return 0; - - return free; -} - -int AUD_get_buffer_size (SWVoice *sw) +int AUD_get_buffer_size_out (SWVoiceOut *sw) { return sw->hw->bufsize; } -void AUD_adjust (SWVoice *sw, int bytes) +void AUD_set_active_out (SWVoiceOut *sw, int on) { - if (!sw) + HWVoiceOut *hw; + + if (!sw) { return; - sw->old_ticks += (ticks_per_sec * (int64_t) bytes) / sw->bytes_per_second; -} - -void AUD_reset (SWVoice *sw) -{ - sw->active = 0; - sw->old_ticks = 0; -} - -int AUD_calc_elapsed (SWVoice *sw) -{ - int64_t now, delta, bytes; - int dead, swlim; - - if (!sw) - return 0; - - now = qemu_get_clock (vm_clock); - delta = now - sw->old_ticks; - bytes = (delta * sw->bytes_per_second) / ticks_per_sec; - if (delta < 0) { - dolog ("whoops delta(<0)=%lld\n", delta); - return 0; } - dead = sw->hw->samples - sw->live; - swlim = ((dead * (int64_t) INT_MAX) / sw->ratio); - - if (bytes > swlim) { - return swlim; - } - else { - return bytes; - } -} - -void AUD_enable (SWVoice *sw, int on) -{ - int i; - HWVoice *hw; - - if (!sw) - return; - hw = sw->hw; - if (on) { - if (!sw->live) - sw->wpos = sw->hw->rpos; - if (!sw->old_ticks) { - sw->old_ticks = qemu_get_clock (vm_clock); - } - } - if (sw->active != on) { + SWVoiceOut *temp_sw; + if (on) { + int total; + hw->pending_disable = 0; if (!hw->enabled) { hw->enabled = 1; - for (i = 0; i < hw->nb_voices; i++) { - ldebug ("resetting voice\n"); - sw = hw->pvoice[i]; - sw->old_ticks = qemu_get_clock (vm_clock); - } - hw->pcm_ops->ctl (hw, VOICE_ENABLE); + hw->pcm_ops->ctl_out (hw, VOICE_ENABLE); + } + + if (sw->empty) { + total = 0; } } else { - if (hw->enabled && !hw->pending_disable) { + if (hw->enabled) { int nb_active = 0; - for (i = 0; i < hw->nb_voices; i++) { - nb_active += hw->pvoice[i]->active != 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + hw->pending_disable = nb_active == 1; + } + } + sw->active = on; + } +} + +void AUD_set_active_in (SWVoiceIn *sw, int on) +{ + HWVoiceIn *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + SWVoiceIn *temp_sw; + + if (on) { + if (!hw->enabled) { + hw->enabled = 1; + hw->pcm_ops->ctl_in (hw, VOICE_ENABLE); + } + sw->total_hw_samples_acquired = hw->total_samples_captured; + } + else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; } if (nb_active == 1) { - hw->pending_disable = 1; + hw->enabled = 0; + hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); } } } @@ -775,118 +1075,547 @@ void AUD_enable (SWVoice *sw, int on) } } -static struct audio_output_driver *drvtab[] = { -#ifdef CONFIG_OSS - &oss_output_driver, -#endif -#ifdef CONFIG_FMOD - &fmod_output_driver, -#endif -#ifdef CONFIG_SDL - &sdl_output_driver, -#endif - &no_output_driver, -#ifdef USE_WAV_AUDIO - &wav_output_driver, -#endif -}; - -static int voice_init (struct audio_output_driver *drv) +static int audio_get_avail (SWVoiceIn *sw) { - audio_state.opaque = drv->init (); - if (audio_state.opaque) { - if (audio_state.nb_hw_voices > drv->max_voices) { - dolog ("`%s' does not support %d multiple hardware channels\n" - "Resetting to %d\n", - drv->name, audio_state.nb_hw_voices, drv->max_voices); - audio_state.nb_hw_voices = drv->max_voices; + int live; + + if (!sw) { + return 0; + } + + live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + return 0; + } + + ldebug ( + "%s: get_avail live %d ret %lld\n", + sw->name, + live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift + ); + + return (((int64_t) live << 32) / sw->ratio) << sw->info.shift; +} + +static int audio_get_free (SWVoiceOut *sw) +{ + int live, dead; + + if (!sw) { + return 0; + } + + live = sw->total_hw_samples_mixed; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + } + + dead = sw->hw->samples - live; + +#ifdef DEBUG_OUT + dolog ("%s: get_free live %d dead %d ret %lld\n", + sw->name, + live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift); +#endif + + return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift; +} + +static void audio_run_out (void) +{ + HWVoiceOut *hw = NULL; + SWVoiceOut *sw; + + while ((hw = audio_pcm_hw_find_any_active_enabled_out (hw))) { + int played; + int live, free, nb_live; + + live = audio_pcm_hw_get_live_out2 (hw, &nb_live); + if (!nb_live) { + live = 0; } - hw_voices = qemu_mallocz (audio_state.nb_hw_voices * drv->voice_size); - if (hw_voices) { - audio_state.drv = drv; - return 1; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); } - else { - dolog ("Not enough memory for %d `%s' voices (each %d bytes)\n", - audio_state.nb_hw_voices, drv->name, drv->voice_size); - drv->fini (audio_state.opaque); - return 0; + + if (hw->pending_disable && !nb_live) { +#ifdef DEBUG_OUT + dolog ("Disabling voice\n"); +#endif + hw->enabled = 0; + hw->pending_disable = 0; + hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); + continue; + } + + if (!live) { + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } + } + continue; + } + + played = hw->pcm_ops->run_out (hw); + if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) { + dolog ("hw->rpos=%d hw->samples=%d played=%d\n", + hw->rpos, hw->samples, played); + hw->rpos = 0; + } + +#ifdef DEBUG_OUT + dolog ("played = %d total %d\n", played, hw->total_samples_played); +#endif + + if (played) { + hw->ts_helper += played; + } + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + again: + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug (AUDIO_FUNC, played > sw->total_hw_samples_mixed)) { + dolog ("played=%d sw->total_hw_samples_mixed=%d\n", + played, sw->total_hw_samples_mixed); + played = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= played; + + if (!sw->total_hw_samples_mixed) { + sw->empty = 1; + + if (!sw->active && !sw->callback.fn) { + SWVoiceOut *temp = sw->entries.le_next; + +#ifdef DEBUG_PLIVE + dolog ("Finishing with old voice\n"); +#endif + AUD_close_out (sw); + sw = temp; + if (sw) { + goto again; + } + else { + break; + } + } + } + + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } } } - else { - dolog ("Could not init `%s' audio\n", drv->name); +} + +static void audio_run_in (void) +{ + HWVoiceIn *hw = NULL; + + while ((hw = audio_pcm_hw_find_any_active_enabled_in (hw))) { + SWVoiceIn *sw; + int captured, min; + + captured = hw->pcm_ops->run_in (hw); + + min = audio_pcm_hw_find_min_in (hw); + hw->total_samples_captured += captured - min; + hw->ts_helper += captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + sw->total_hw_samples_acquired -= min; + + if (sw->active) { + int avail; + + avail = audio_get_avail (sw); + if (avail > 0) { + sw->callback.fn (sw->callback.opaque, avail); + } + } + } + } +} + +static struct audio_option audio_options[] = { + /* DAC */ + {"DAC_FIXED_SETTINGS", AUD_OPT_BOOL, &audio_state.fixed_settings_out, + "Use fixed settings for host DAC", NULL, 0}, + + {"DAC_FIXED_FREQ", AUD_OPT_INT, &audio_state.fixed_freq_out, + "Frequency for fixed host DAC", NULL, 0}, + + {"DAC_FIXED_FMT", AUD_OPT_FMT, &audio_state.fixed_fmt_out, + "Format for fixed host DAC", NULL, 0}, + + {"DAC_FIXED_CHANNELS", AUD_OPT_INT, &audio_state.fixed_channels_out, + "Number of channels for fixed DAC (1 - mono, 2 - stereo)", NULL, 0}, + + {"DAC_VOICES", AUD_OPT_INT, &audio_state.nb_hw_voices_out, + "Number of voices for DAC", NULL, 0}, + + /* ADC */ + {"ADC_FIXED_SETTINGS", AUD_OPT_BOOL, &audio_state.fixed_settings_out, + "Use fixed settings for host ADC", NULL, 0}, + + {"ADC_FIXED_FREQ", AUD_OPT_INT, &audio_state.fixed_freq_out, + "Frequency for fixed ADC", NULL, 0}, + + {"ADC_FIXED_FMT", AUD_OPT_FMT, &audio_state.fixed_fmt_out, + "Format for fixed ADC", NULL, 0}, + + {"ADC_FIXED_CHANNELS", AUD_OPT_INT, &audio_state.fixed_channels_in, + "Number of channels for fixed ADC (1 - mono, 2 - stereo)", NULL, 0}, + + {"ADC_VOICES", AUD_OPT_INT, &audio_state.nb_hw_voices_out, + "Number of voices for ADC", NULL, 0}, + + /* Misc */ + {"TIMER_PERIOD", AUD_OPT_INT, &audio_state.period.usec, + "Timer period in microseconds (0 - try lowest possible)", NULL, 0}, + + {"PLIVE", AUD_OPT_BOOL, &audio_state.plive, + "(undocumented)", NULL, 0}, + + {NULL, 0, NULL, NULL, NULL, 0} +}; + +void AUD_help (void) +{ + size_t i; + + audio_process_options ("AUDIO", audio_options); + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { + struct audio_driver *d = drvtab[i]; + if (d->options) { + audio_process_options (d->name, d->options); + } + } + + printf ("Audio options:\n"); + audio_print_options ("AUDIO", audio_options); + printf ("\n"); + + printf ("Available drivers:\n"); + + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { + struct audio_driver *d = drvtab[i]; + + printf ("Name: %s\n", d->name); + printf ("Description: %s\n", d->descr); + + switch (d->max_voices_out) { + case 0: + printf ("Does not support DAC\n"); + break; + case 1: + printf ("One DAC voice\n"); + break; + case INT_MAX: + printf ("Theoretically supports many DAC voices\n"); + break; + default: + printf ("Theoretically supports upto %d DAC voices\n", + d->max_voices_out); + break; + } + + switch (d->max_voices_in) { + case 0: + printf ("Does not support ADC\n"); + break; + case 1: + printf ("One ADC voice\n"); + break; + case INT_MAX: + printf ("Theoretically supports many ADC voices\n"); + break; + default: + printf ("Theoretically supports upto %d ADC voices\n", + d->max_voices_in); + break; + } + + if (d->options) { + printf ("Options:\n"); + audio_print_options (d->name, d->options); + } + else { + printf ("No options\n"); + } + printf ("\n"); + } + + printf ( + "Options are settable through environment variables.\n" + "Example:\n" +#ifdef _WIN32 + " set QEMU_AUDIO_DRV=wav\n" + " set QEMU_WAV_PATH=c:/tune.wav\n" +#else + " export QEMU_AUDIO_DRV=wav\n" + " export QEMU_WAV_PATH=$HOME/tune.wav\n" + "(for csh replace export with setenv in the above)\n" +#endif + " qemu ...\n\n" + ); +} + +void audio_timer (void *opaque) +{ + AudioState *s = opaque; + + audio_run_out (); + audio_run_in (); + + qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + s->period.ticks); +} + +static int audio_driver_init (struct audio_driver *drv) +{ + if (drv->options) { + audio_process_options (drv->name, drv->options); + } + audio_state.opaque = drv->init (); + + if (audio_state.opaque) { + int i; + HWVoiceOut *hwo; + HWVoiceIn *hwi; + + if (audio_state.nb_hw_voices_out > drv->max_voices_out) { + if (!drv->max_voices_out) { + dolog ("`%s' does not support DAC\n", drv->name); + } + else { + dolog ( + "`%s' does not support %d multiple DAC voicess\n" + "Resetting to %d\n", + drv->name, + audio_state.nb_hw_voices_out, + drv->max_voices_out + ); + } + audio_state.nb_hw_voices_out = drv->max_voices_out; + } + + LIST_INIT (&hw_head_out); + hwo = qemu_mallocz (audio_state.nb_hw_voices_out * drv->voice_size_out); + if (!hwo) { + dolog ( + "Not enough memory for %d `%s' DAC voices (each %d bytes)\n", + audio_state.nb_hw_voices_out, + drv->name, + drv->voice_size_out + ); + drv->fini (audio_state.opaque); + return -1; + } + + for (i = 0; i < audio_state.nb_hw_voices_out; ++i) { + LIST_INSERT_HEAD (&hw_head_out, hwo, entries); + hwo = advance (hwo, drv->voice_size_out); + } + + if (!drv->voice_size_in && drv->max_voices_in) { + ldebug ("warning: No ADC voice size defined for `%s'\n", + drv->name); + drv->max_voices_in = 0; + } + + if (!drv->voice_size_out && drv->max_voices_out) { + ldebug ("warning: No DAC voice size defined for `%s'\n", + drv->name); + } + + if (drv->voice_size_in && !drv->max_voices_in) { + ldebug ("warning: ADC voice size is %d for ADC less driver `%s'\n", + drv->voice_size_out, drv->name); + } + + if (drv->voice_size_out && !drv->max_voices_out) { + ldebug ("warning: DAC voice size is %d for DAC less driver `%s'\n", + drv->voice_size_in, drv->name); + } + + if (audio_state.nb_hw_voices_in > drv->max_voices_in) { + if (!drv->max_voices_in) { + ldebug ("`%s' does not support ADC\n", drv->name); + } + else { + dolog ( + "`%s' does not support %d multiple ADC voices\n" + "Resetting to %d\n", + drv->name, + audio_state.nb_hw_voices_in, + drv->max_voices_in + ); + } + audio_state.nb_hw_voices_in = drv->max_voices_in; + } + + LIST_INIT (&hw_head_in); + hwi = qemu_mallocz (audio_state.nb_hw_voices_in * drv->voice_size_in); + if (!hwi) { + dolog ( + "Not enough memory for %d `%s' ADC voices (each %d bytes)\n", + audio_state.nb_hw_voices_in, + drv->name, + drv->voice_size_in + ); + qemu_free (hwo); + drv->fini (audio_state.opaque); + return -1; + } + + for (i = 0; i < audio_state.nb_hw_voices_in; ++i) { + LIST_INSERT_HEAD (&hw_head_in, hwi, entries); + hwi = advance (hwi, drv->voice_size_in); + } + + audio_state.drv = drv; return 0; } + else { + dolog ("Could not init `%s' audio driver\n", drv->name); + return -1; + } } static void audio_vm_stop_handler (void *opaque, int reason) { - HWVoice *hw = NULL; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + int op = reason ? VOICE_ENABLE : VOICE_DISABLE; - while ((hw = pcm_hw_find_any (hw))) { - if (!hw->pcm_ops) + (void) opaque; + while ((hwo = audio_pcm_hw_find_any_out (hwo))) { + if (!hwo->pcm_ops) { continue; + } - hw->pcm_ops->ctl (hw, reason ? VOICE_ENABLE : VOICE_DISABLE); + if (hwo->enabled != reason) { + hwo->pcm_ops->ctl_out (hwo, op); + } + } + + while ((hwi = audio_pcm_hw_find_any_in (hwi))) { + if (!hwi->pcm_ops) { + continue; + } + + if (hwi->enabled != reason) { + hwi->pcm_ops->ctl_in (hwi, op); + } } } static void audio_atexit (void) { - HWVoice *hw = NULL; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; - while ((hw = pcm_hw_find_any (hw))) { - if (!hw->pcm_ops) + while ((hwo = audio_pcm_hw_find_any_out (hwo))) { + if (!hwo->pcm_ops) { continue; + } - hw->pcm_ops->ctl (hw, VOICE_DISABLE); - hw->pcm_ops->fini (hw); + if (hwo->enabled) { + hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE); + } + hwo->pcm_ops->fini_out (hwo); + } + + while ((hwi = audio_pcm_hw_find_any_in (hwi))) { + if (!hwi->pcm_ops) { + continue; + } + + if (hwi->enabled) { + hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE); + } + hwi->pcm_ops->fini_in (hwi); } audio_state.drv->fini (audio_state.opaque); } static void audio_save (QEMUFile *f, void *opaque) { + (void) f; + (void) opaque; } static int audio_load (QEMUFile *f, void *opaque, int version_id) { - if (version_id != 1) + (void) f; + (void) opaque; + + if (version_id != 1) { return -EINVAL; + } return 0; } void AUD_init (void) { - int i; + size_t i; int done = 0; const char *drvname; + AudioState *s = &audio_state; - audio_state.fixed_format = - !!audio_get_conf_int (QC_FIXED_FORMAT, audio_state.fixed_format); - audio_state.fixed_freq = - audio_get_conf_int (QC_FIXED_FREQ, audio_state.fixed_freq); - audio_state.nb_hw_voices = - audio_get_conf_int (QC_VOICES, audio_state.nb_hw_voices); + audio_process_options ("AUDIO", audio_options); - if (audio_state.nb_hw_voices <= 0) { - dolog ("Bogus number of voices %d, resetting to 1\n", - audio_state.nb_hw_voices); + if (s->nb_hw_voices_out <= 0) { + dolog ("Bogus number of DAC voices %d\n", + s->nb_hw_voices_out); + s->nb_hw_voices_out = 1; + } + + if (s->nb_hw_voices_in <= 0) { + dolog ("Bogus number of ADC voices %d\n", + s->nb_hw_voices_in); + s->nb_hw_voices_in = 1; + } + + { + int def; + drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def); + } + + s->ts = qemu_new_timer (vm_clock, audio_timer, s); + if (!s->ts) { + dolog ("Can not create audio timer\n"); + return; } - drvname = audio_get_conf_str (QC_AUDIO_DRV, NULL); if (drvname) { int found = 0; + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { if (!strcmp (drvname, drvtab[i]->name)) { - done = voice_init (drvtab[i]); + done = !audio_driver_init (drvtab[i]); found = 1; break; } } + if (!found) { dolog ("Unknown audio driver `%s'\n", drvname); + dolog ("Run with -audio-help to list available drivers\n"); } } @@ -895,17 +1624,32 @@ void AUD_init (void) if (!done) { for (i = 0; !done && i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { - if (drvtab[i]->can_be_default) - done = voice_init (drvtab[i]); + if (drvtab[i]->can_be_default) { + done = !audio_driver_init (drvtab[i]); + } } } - audio_state.ticks_threshold = ticks_per_sec / 50; - audio_state.freq_threshold = 100; - register_savevm ("audio", 0, 1, audio_save, audio_load, NULL); if (!done) { - dolog ("Can not initialize audio subsystem\n"); - voice_init (&no_output_driver); + if (audio_driver_init (&no_audio_driver)) { + dolog ("Can not initialize audio subsystem\n"); + } + else { + dolog ("warning: using timer based audio emulation\n"); + } } + + if (s->period.usec <= 0) { + if (s->period.usec < 0) { + dolog ("warning: timer period is negative - %d treating as zero\n", + s->period.usec); + } + s->period.ticks = 1; + } + else { + s->period.ticks = (ticks_per_sec * s->period.usec) / 1000000; + } + + qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + s->period.ticks); } diff --git a/audio/audio.h b/audio/audio.h index 7520383a47..6dd2fd22e3 100644 --- a/audio/audio.h +++ b/audio/audio.h @@ -1,8 +1,8 @@ /* * QEMU Audio subsystem header - * - * Copyright (c) 2003-2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * * 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 @@ -24,7 +24,7 @@ #ifndef QEMU_AUDIO_H #define QEMU_AUDIO_H -#include "mixeng.h" +typedef void (*audio_callback_fn_t) (void *opaque, int avail); typedef enum { AUD_FMT_U8, @@ -33,22 +33,60 @@ typedef enum { AUD_FMT_S16 } audfmt_e; -typedef struct SWVoice SWVoice; +typedef struct SWVoiceOut SWVoiceOut; +typedef struct SWVoiceIn SWVoiceIn; -SWVoice * AUD_open (SWVoice *sw, const char *name, int freq, - int nchannels, audfmt_e fmt); -void AUD_init (void); -void AUD_log (const char *cap, const char *fmt, ...) - __attribute__ ((__format__ (__printf__, 2, 3)));; -void AUD_close (SWVoice *sw); -int AUD_write (SWVoice *sw, void *pcm_buf, int size); -void AUD_adjust (SWVoice *sw, int leftover); -void AUD_reset (SWVoice *sw); -int AUD_get_free (SWVoice *sw); -int AUD_get_buffer_size (SWVoice *sw); -void AUD_run (void); -void AUD_enable (SWVoice *sw, int on); -int AUD_calc_elapsed (SWVoice *sw); +typedef struct QEMUAudioTimeStamp { + uint64_t old_ts; +} QEMUAudioTimeStamp; + +void AUD_vlog (const char *cap, const char *fmt, va_list ap); +void AUD_log (const char *cap, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__ ((__format__ (__printf__, 2, 3))) +#endif + ; + +void AUD_init (void); +void AUD_help (void); + +SWVoiceOut *AUD_open_out ( + SWVoiceOut *sw, + const char *name, + void *callback_opaque, + audio_callback_fn_t callback_fn, + int freq, + int nchannels, + audfmt_e fmt + ); +void AUD_close_out (SWVoiceOut *sw); +int AUD_write (SWVoiceOut *sw, void *pcm_buf, int size); +int AUD_get_buffer_size_out (SWVoiceOut *sw); +void AUD_set_active_out (SWVoiceOut *sw, int on); +int AUD_is_active_out (SWVoiceOut *sw); +void AUD_init_time_stamp_out (SWVoiceOut *sw, + QEMUAudioTimeStamp *ts); +uint64_t AUD_time_stamp_get_elapsed_usec_out (SWVoiceOut *sw, + QEMUAudioTimeStamp *ts); + +SWVoiceIn *AUD_open_in ( + SWVoiceIn *sw, + const char *name, + void *callback_opaque, + audio_callback_fn_t callback_fn, + int freq, + int nchannels, + audfmt_e fmt + ); +void AUD_close_in (SWVoiceIn *sw); +int AUD_read (SWVoiceIn *sw, void *pcm_buf, int size); +void AUD_adjust_in (SWVoiceIn *sw, int leftover); +void AUD_set_active_in (SWVoiceIn *sw, int on); +int AUD_is_active_in (SWVoiceIn *sw); +void AUD_init_time_stamp_in (SWVoiceIn *sw, + QEMUAudioTimeStamp *ts); +uint64_t AUD_time_stamp_get_elapsed_usec_in (SWVoiceIn *sw, + QEMUAudioTimeStamp *ts); static inline void *advance (void *p, int incr) { @@ -59,7 +97,21 @@ static inline void *advance (void *p, int incr) uint32_t popcount (uint32_t u); inline uint32_t lsbindex (uint32_t u); +#ifdef __GNUC__ +#define audio_MIN(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)>(tb)?(tb):(ta)); \ +})) + +#define audio_MAX(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)<(tb)?(tb):(ta)); \ +})) +#else #define audio_MIN(a, b) ((a)>(b)?(b):(a)) #define audio_MAX(a, b) ((a)<(b)?(b):(a)) +#endif #endif /* audio.h */ diff --git a/audio/audio_int.h b/audio/audio_int.h index 0be2a61662..9d288292a7 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -1,8 +1,8 @@ /* * QEMU Audio subsystem header - * - * Copyright (c) 2003-2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * * 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 @@ -24,140 +24,266 @@ #ifndef QEMU_AUDIO_INT_H #define QEMU_AUDIO_INT_H -#include "vl.h" +#include "sys-queue.h" -struct pcm_ops; +#ifdef CONFIG_COREAUDIO +#define FLOAT_MIXENG +/* #define RECIPROCAL */ +#endif +#include "mixeng.h" -typedef struct HWVoice { +int audio_bug (const char *funcname, int cond); + +struct audio_pcm_ops; + +typedef enum { + AUD_OPT_INT, + AUD_OPT_FMT, + AUD_OPT_STR, + AUD_OPT_BOOL +} audio_option_tag_e; + +struct audio_option { + const char *name; + audio_option_tag_e tag; + void *valp; + const char *descr; + int *overridenp; + int overriden; +}; + +struct audio_callback { + void *opaque; + audio_callback_fn_t fn; +}; + +struct audio_pcm_info { + int bits; + int sign; + int freq; + int nchannels; + int align; + int shift; + int bytes_per_second; + int swap_endian; +}; + +typedef struct HWVoiceOut { int active; int enabled; int pending_disable; int valid; - int freq; + struct audio_pcm_info info; f_sample *clip; - audfmt_e fmt; - int nchannels; - - int align; - int shift; int rpos; int bufsize; + uint64_t ts_helper; - int bytes_per_second; st_sample_t *mix_buf; int samples; - int64_t old_ticks; - int nb_voices; - struct SWVoice **pvoice; - struct pcm_ops *pcm_ops; -} HWVoice; + LIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head; + struct audio_pcm_ops *pcm_ops; + LIST_ENTRY (HWVoiceOut) entries; +} HWVoiceOut; -extern struct pcm_ops no_pcm_ops; -extern struct audio_output_driver no_output_driver; - -extern struct pcm_ops oss_pcm_ops; -extern struct audio_output_driver oss_output_driver; - -extern struct pcm_ops sdl_pcm_ops; -extern struct audio_output_driver sdl_output_driver; - -extern struct pcm_ops wav_pcm_ops; -extern struct audio_output_driver wav_output_driver; - -extern struct pcm_ops fmod_pcm_ops; -extern struct audio_output_driver fmod_output_driver; - -struct audio_output_driver { - const char *name; - void *(*init) (void); - void (*fini) (void *); - struct pcm_ops *pcm_ops; - int can_be_default; - int max_voices; - int voice_size; -}; - -typedef struct AudioState { - int fixed_format; - int fixed_freq; - int fixed_channels; - int fixed_fmt; - int nb_hw_voices; - int64_t ticks_threshold; - int freq_threshold; - void *opaque; - struct audio_output_driver *drv; -} AudioState; -extern AudioState audio_state; - -struct SWVoice { - int freq; - audfmt_e fmt; - int nchannels; - - int shift; - int align; +typedef struct HWVoiceIn { + int enabled; + int active; + struct audio_pcm_info info; t_sample *conv; - int left; - int pos; - int bytes_per_second; + int wpos; + int bufsize; + int total_samples_captured; + uint64_t ts_helper; + + st_sample_t *conv_buf; + + int samples; + LIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head; + struct audio_pcm_ops *pcm_ops; + LIST_ENTRY (HWVoiceIn) entries; +} HWVoiceIn; + +extern struct audio_driver no_audio_driver; +extern struct audio_driver oss_audio_driver; +extern struct audio_driver sdl_audio_driver; +extern struct audio_driver wav_audio_driver; +extern struct audio_driver fmod_audio_driver; +extern struct audio_driver alsa_audio_driver; +extern struct audio_driver coreaudio_audio_driver; +extern struct audio_driver dsound_audio_driver; +extern volume_t nominal_volume; + +struct audio_driver { + const char *name; + const char *descr; + struct audio_option *options; + void *(*init) (void); + void (*fini) (void *); + struct audio_pcm_ops *pcm_ops; + int can_be_default; + int max_voices_out; + int max_voices_in; + int voice_size_out; + int voice_size_in; +}; + +typedef struct AudioState { + int fixed_settings_out; + int fixed_freq_out; + int fixed_channels_out; + int fixed_fmt_out; + int nb_hw_voices_out; + int greedy_out; + + int fixed_settings_in; + int fixed_freq_in; + int fixed_channels_in; + int fixed_fmt_in; + int nb_hw_voices_in; + int greedy_in; + + void *opaque; + struct audio_driver *drv; + + QEMUTimer *ts; + union { + int usec; + int64_t ticks; + } period; + + int plive; +} AudioState; +extern AudioState audio_state; + +struct SWVoiceOut { + struct audio_pcm_info info; + t_sample *conv; int64_t ratio; st_sample_t *buf; void *rate; - - int wpos; - int live; + int total_hw_samples_mixed; int active; - int64_t old_ticks; - HWVoice *hw; + int empty; + HWVoiceOut *hw; char *name; + volume_t vol; + struct audio_callback callback; + LIST_ENTRY (SWVoiceOut) entries; }; -struct pcm_ops { - int (*init) (HWVoice *hw, int freq, int nchannels, audfmt_e fmt); - void (*fini) (HWVoice *hw); - void (*run) (HWVoice *hw); - int (*write) (SWVoice *sw, void *buf, int size); - int (*ctl) (HWVoice *hw, int cmd, ...); +struct SWVoiceIn { + int active; + struct audio_pcm_info info; + int64_t ratio; + void *rate; + int total_hw_samples_acquired; + st_sample_t *conv_buf; + f_sample *clip; + HWVoiceIn *hw; + char *name; + volume_t vol; + struct audio_callback callback; + LIST_ENTRY (SWVoiceIn) entries; }; -void pcm_sw_free_resources (SWVoice *sw); -int pcm_sw_alloc_resources (SWVoice *sw); -void pcm_sw_fini (SWVoice *sw); -int pcm_sw_init (SWVoice *sw, HWVoice *hw, int freq, - int nchannels, audfmt_e fmt); +struct audio_pcm_ops { + int (*init_out)(HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt); + void (*fini_out)(HWVoiceOut *hw); + int (*run_out) (HWVoiceOut *hw); + int (*write) (SWVoiceOut *sw, void *buf, int size); + int (*ctl_out) (HWVoiceOut *hw, int cmd, ...); -void pcm_hw_clear (HWVoice *hw, void *buf, int len); -HWVoice * pcm_hw_find_any (HWVoice *hw); -HWVoice * pcm_hw_find_any_active (HWVoice *hw); -HWVoice * pcm_hw_find_any_passive (HWVoice *hw); -HWVoice * pcm_hw_find_specific (HWVoice *hw, int freq, - int nchannels, audfmt_e fmt); -HWVoice * pcm_hw_add (int freq, int nchannels, audfmt_e fmt); -int pcm_hw_add_sw (HWVoice *hw, SWVoice *sw); -int pcm_hw_del_sw (HWVoice *hw, SWVoice *sw); -SWVoice * pcm_create_voice_pair (int freq, int nchannels, audfmt_e fmt); + int (*init_in) (HWVoiceIn *hw, int freq, int nchannels, audfmt_e fmt); + void (*fini_in) (HWVoiceIn *hw); + int (*run_in) (HWVoiceIn *hw); + int (*read) (SWVoiceIn *sw, void *buf, int size); + int (*ctl_in) (HWVoiceIn *hw, int cmd, ...); +}; -void pcm_hw_free_resources (HWVoice *hw); -int pcm_hw_alloc_resources (HWVoice *hw); -void pcm_hw_fini (HWVoice *hw); -void pcm_hw_gc (HWVoice *hw); -int pcm_hw_get_live (HWVoice *hw); -int pcm_hw_get_live2 (HWVoice *hw, int *nb_active); -void pcm_hw_dec_live (HWVoice *hw, int decr); -int pcm_hw_write (SWVoice *sw, void *buf, int len); +void audio_pcm_init_info (struct audio_pcm_info *info, int freq, + int nchannels, audfmt_e fmt, int swap_endian); +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); -int audio_get_conf_int (const char *key, int defval); -const char *audio_get_conf_str (const char *key, const char *defval); +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int len); +int audio_pcm_hw_get_live_in (HWVoiceIn *hw); -struct audio_output_driver; +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int len); +int audio_pcm_hw_get_live_out (HWVoiceOut *hw); +int audio_pcm_hw_get_live_out2 (HWVoiceOut *hw, int *nb_live); #define VOICE_ENABLE 1 #define VOICE_DISABLE 2 +static inline int audio_ring_dist (int dst, int src, int len) +{ + return (dst >= src) ? (dst - src) : (len - src + dst); +} + +static inline int audio_need_to_swap_endian (int endianness) +{ +#ifdef WORDS_BIGENDIAN + return endianness != 1; +#else + return endianness != 0; +#endif +} + +#if defined __GNUC__ +#define GCC_ATTR __attribute__ ((__unused__, __format__ (__printf__, 1, 2))) +#define INIT_FIELD(f) . f +#define GCC_FMT_ATTR(n, m) __attribute__ ((__format__ (printf, n, m))) +#else +#define GCC_ATTR /**/ +#define INIT_FIELD(f) /**/ +#define GCC_FMT_ATTR(n, m) +#endif + +static void GCC_ATTR dolog (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); +} + +#ifdef DEBUG +static void GCC_ATTR ldebug (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); +} +#else +#if defined NDEBUG && defined __GNUC__ +#define ldebug(...) +#elif defined NDEBUG && defined _MSC_VER +#define ldebug __noop +#else +static void GCC_ATTR ldebug (const char *fmt, ...) +{ + (void) fmt; +} +#endif +#endif + +#undef GCC_ATTR + +#define AUDIO_STRINGIFY_(n) #n +#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n) + +#if defined _MSC_VER || defined __GNUC__ +#define AUDIO_FUNC __FUNCTION__ +#else +#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__) +#endif + #endif /* audio_int.h */ diff --git a/audio/audio_template.h b/audio/audio_template.h new file mode 100644 index 0000000000..25ea72fd41 --- /dev/null +++ b/audio/audio_template.h @@ -0,0 +1,401 @@ +/* + * QEMU Audio subsystem header + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * 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. + */ + +#ifdef DAC +#define TYPE out +#define HW glue (HWVoice, Out) +#define SW glue (SWVoice, Out) +#else +#define TYPE in +#define HW glue (HWVoice, In) +#define SW glue (SWVoice, In) +#endif + +static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw) +{ + glue (audio_pcm_sw_free_resources_, TYPE) (sw); + if (sw->name) { + qemu_free (sw->name); + sw->name = NULL; + } +} + +static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw) +{ + LIST_INSERT_HEAD (&hw->sw_head, sw, entries); +} + +static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) +{ + LIST_REMOVE (sw, entries); +} + +static void glue (audio_pcm_hw_fini_, TYPE) (HW *hw) +{ + if (hw->active) { + glue (audio_pcm_hw_free_resources_ ,TYPE) (hw); + glue (hw->pcm_ops->fini_, TYPE) (hw); + memset (hw, 0, glue (audio_state.drv->voice_size_, TYPE)); + } +} + +static void glue (audio_pcm_hw_gc_, TYPE) (HW *hw) +{ + if (!hw->sw_head.lh_first) { + glue (audio_pcm_hw_fini_, TYPE) (hw); + } +} + +static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw) +{ + return hw ? hw->entries.le_next : glue (hw_head_, TYPE).lh_first; +} + +static HW *glue (audio_pcm_hw_find_any_active_, TYPE) (HW *hw) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (hw->active) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_find_any_active_enabled_, TYPE) (HW *hw) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (hw->active && hw->enabled) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_find_any_passive_, TYPE) (HW *hw) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (!hw->active) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_find_specific_, TYPE) ( + HW *hw, + int freq, + int nchannels, + audfmt_e fmt + ) +{ + while ((hw = glue (audio_pcm_hw_find_any_active_, TYPE) (hw))) { + if (audio_pcm_info_eq (&hw->info, freq, nchannels, fmt)) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_add_new_, TYPE) ( + int freq, + int nchannels, + audfmt_e fmt + ) +{ + HW *hw; + + hw = glue (audio_pcm_hw_find_any_passive_, TYPE) (NULL); + if (hw) { + hw->pcm_ops = audio_state.drv->pcm_ops; + if (!hw->pcm_ops) { + return NULL; + } + + if (glue (audio_pcm_hw_init_, TYPE) (hw, freq, nchannels, fmt)) { + glue (audio_pcm_hw_gc_, TYPE) (hw); + return NULL; + } + else { + return hw; + } + } + + return NULL; +} + +static HW *glue (audio_pcm_hw_add_, TYPE) ( + int freq, + int nchannels, + audfmt_e fmt + ) +{ + HW *hw; + + if (glue (audio_state.greedy_, TYPE)) { + hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt); + if (hw) { + return hw; + } + } + + hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, freq, nchannels, fmt); + if (hw) { + return hw; + } + + hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt); + if (hw) { + return hw; + } + + return glue (audio_pcm_hw_find_any_active_, TYPE) (NULL); +} + +static SW *glue (audio_pcm_create_voice_pair_, TYPE) ( + const char *name, + int freq, + int nchannels, + audfmt_e fmt + ) +{ + SW *sw; + HW *hw; + int hw_freq = freq; + int hw_nchannels = nchannels; + int hw_fmt = fmt; + + if (glue (audio_state.fixed_settings_, TYPE)) { + hw_freq = glue (audio_state.fixed_freq_, TYPE); + hw_nchannels = glue (audio_state.fixed_channels_, TYPE); + hw_fmt = glue (audio_state.fixed_fmt_, TYPE); + } + + sw = qemu_mallocz (sizeof (*sw)); + if (!sw) { + goto err1; + } + + hw = glue (audio_pcm_hw_add_, TYPE) (hw_freq, hw_nchannels, hw_fmt); + if (!hw) { + goto err2; + } + + glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw); + + if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, freq, nchannels, fmt)) { + goto err3; + } + + return sw; + +err3: + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (hw); +err2: + qemu_free (sw); +err1: + return NULL; +} + +void glue (AUD_close_, TYPE) (SW *sw) +{ + if (sw) { + glue (audio_pcm_sw_fini_, TYPE) (sw); + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (sw->hw); + qemu_free (sw); + } +} + +SW *glue (AUD_open_, TYPE) ( + SW *sw, + const char *name, + void *callback_opaque , + audio_callback_fn_t callback_fn, + int freq, + int nchannels, + audfmt_e fmt + ) +{ +#ifdef DAC + int live = 0; + SW *old_sw = NULL; +#endif + + if (!callback_fn) { + dolog ("No callback specifed for voice `%s'\n", name); + goto fail; + } + + if (nchannels != 1 && nchannels != 2) { + dolog ("Bogus channel count %d for voice `%s'\n", nchannels, name); + goto fail; + } + + if (!audio_state.drv) { + dolog ("No audio driver defined\n"); + goto fail; + } + + if (sw && audio_pcm_info_eq (&sw->info, freq, nchannels, fmt)) { + return sw; + } + +#ifdef DAC + if (audio_state.plive && sw && (!sw->active && !sw->empty)) { + live = sw->total_hw_samples_mixed; + +#ifdef DEBUG_PLIVE + dolog ("Replacing voice %s with %d live samples\n", sw->name, live); + dolog ("Old %s freq %d, bits %d, channels %d\n", + sw->name, sw->info.freq, sw->info.bits, sw->info.nchannels); + dolog ("New %s freq %d, bits %d, channels %d\n", + name, freq, (fmt == AUD_FMT_S16 || fmt == AUD_FMT_U16) ? 16 : 8, + nchannels); +#endif + + if (live) { + old_sw = sw; + old_sw->callback.fn = NULL; + sw = NULL; + } + } +#endif + + if (!glue (audio_state.fixed_settings_, TYPE) && sw) { + glue (AUD_close_, TYPE) (sw); + sw = NULL; + } + + if (sw) { + HW *hw = sw->hw; + + if (!hw) { + dolog ("Internal logic error voice %s has no hardware store\n", + name); + goto fail; + } + + if (glue (audio_pcm_sw_init_, TYPE) ( + sw, + hw, + name, + freq, + nchannels, + fmt + )) { + goto fail; + } + } + else { + sw = glue (audio_pcm_create_voice_pair_, TYPE) ( + name, + freq, + nchannels, + fmt); + if (!sw) { + dolog ("Failed to create voice %s\n", name); + goto fail; + } + } + + if (sw) { + sw->vol = nominal_volume; + sw->callback.fn = callback_fn; + sw->callback.opaque = callback_opaque; + +#ifdef DAC + if (live) { + int mixed = + (live << old_sw->info.shift) + * old_sw->info.bytes_per_second + / sw->info.bytes_per_second; + +#ifdef DEBUG_PLIVE + dolog ("Silence will be mixed %d\n", mixed); +#endif + sw->total_hw_samples_mixed += mixed; + } +#endif + +#ifdef DEBUG_AUDIO + dolog ("%s\n", name); + audio_pcm_print_info ("hw", &sw->hw->info); + audio_pcm_print_info ("sw", &sw->info); +#endif + } + + return sw; + + fail: + glue (AUD_close_, TYPE) (sw); + return NULL; +} + +int glue (AUD_is_active_, TYPE) (SW *sw) +{ + return sw ? sw->active : 0; +} + +void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) +{ + if (!sw) { + return; + } + + ts->old_ts = sw->hw->ts_helper; +} + +uint64_t glue (AUD_time_stamp_get_elapsed_usec_, TYPE) ( + SW *sw, + QEMUAudioTimeStamp *ts + ) +{ + uint64_t delta, cur_ts, old_ts; + + if (!sw) { + return 0; + } + + cur_ts = sw->hw->ts_helper; + old_ts = ts->old_ts; + /* dolog ("cur %lld old %lld\n", cur_ts, old_ts); */ + + if (cur_ts >= old_ts) { + delta = cur_ts - old_ts; + } + else { + delta = UINT64_MAX - old_ts + cur_ts; + } + + if (!delta) { + return 0; + } + + return (delta * sw->hw->info.freq) / 1000000; +} + +#undef TYPE +#undef HW +#undef SW diff --git a/audio/coreaudio.c b/audio/coreaudio.c new file mode 100644 index 0000000000..eee12386ce --- /dev/null +++ b/audio/coreaudio.c @@ -0,0 +1,513 @@ +/* + * QEMU OS X CoreAudio audio driver + * + * Copyright (c) 2005 Mike Kronenberg + * + * 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 +#include /* strerror */ +#include /* pthread_X */ + +#include "vl.h" + +#define AUDIO_CAP "coreaudio" +#include "audio_int.h" + +#define DEVICE_BUFFER_FRAMES (512) + +struct { + int buffer_frames; +} conf = { + .buffer_frames = 512 +}; + +typedef struct coreaudioVoiceOut { + HWVoiceOut hw; + pthread_mutex_t mutex; + AudioDeviceID outputDeviceID; + UInt32 audioDevicePropertyBufferSize; + AudioStreamBasicDescription outputStreamBasicDescription; + int isPlaying; + int live; + int decr; + int rpos; +} coreaudioVoiceOut; + +static void coreaudio_logstatus (OSStatus status) +{ + char *str = "BUG"; + + switch(status) { + case kAudioHardwareNoError: + str = "kAudioHardwareNoError"; + break; + + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + + case kAudioHardwareBadDeviceError: + str = "kAudioHardwareBadDeviceError"; + break; + + case kAudioHardwareBadStreamError: + str = "kAudioHardwareBadStreamError"; + break; + + case kAudioHardwareUnsupportedOperationError: + str = "kAudioHardwareUnsupportedOperationError"; + break; + + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + + case kAudioDevicePermissionsError: + str = "kAudioDevicePermissionsError"; + break; + + default: + AUD_log (AUDIO_CAP, "Reason: status code %ld\n", status); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( + OSStatus status, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_log (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( + OSStatus status, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_lock (&core->mutex); + if (err) { + dolog ("Can not lock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_unlock (&core->mutex); + if (err) { + dolog ("Can not unlock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_run_out (HWVoiceOut *hw) +{ + int live, decr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + if (coreaudio_lock (core, "coreaudio_run_out")) { + return 0; + } + + live = audio_pcm_hw_get_live_out (hw); + + if (core->decr > live) { + ldebug ("core->decr %d live %d core->live %d\n", + core->decr, + live, + core->live); + } + + decr = audio_MIN (core->decr, live); + core->decr -= decr; + + core->live = live - decr; + hw->rpos = core->rpos; + + coreaudio_unlock (core, "coreaudio_run_out"); + return decr; +} + +/* callback to feed audiooutput buffer */ +static OSStatus audioDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* hwptr) +{ + unsigned int frame, frameCount; + float *out = outOutputData->mBuffers[0].mData; + HWVoiceOut *hw = hwptr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; + int rpos, live; + st_sample_t *src; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL + const float scale = 1.f / UINT_MAX; +#else + const float scale = UINT_MAX; +#endif +#endif + + if (coreaudio_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; + } + + frameCount = conf.buffer_frames; + live = core->live; + + /* if there are not enough samples, set signal and return */ + if (live < frameCount) { + inInputTime = 0; + coreaudio_unlock (core, "audioDeviceIOProc(empty)"); + return 0; + } + + rpos = core->rpos; + src = hw->mix_buf + rpos; + + /* fill buffer */ + for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG + *out++ = src[frame].l; /* left channel */ + *out++ = src[frame].r; /* right channel */ +#else +#ifdef RECIPROCAL + *out++ = src[frame].l * scale; /* left channel */ + *out++ = src[frame].r * scale; /* right channel */ +#else + *out++ = src[frame].l / scale; /* left channel */ + *out++ = src[frame].r / scale; /* right channel */ +#endif +#endif + } + + /* cleanup */ + mixeng_clear (src, frameCount); + rpos = (rpos + frameCount) % hw->samples; + core->decr = frameCount; + core->rpos = rpos; + + coreaudio_unlock (core, "audioDeviceIOProc"); + return 0; +} + +static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int coreaudio_init_out (HWVoiceOut *hw, int freq, + int nchannels, audfmt_e fmt) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + UInt32 propertySize; + int err; + int bits = 8; + int endianess = 0; + const char *typ = "DAC"; + + /* create mutex */ + err = pthread_mutex_init(&core->mutex, NULL); + if (err) { + dolog("Can not create mutex\nReason: %s\n", strerror (err)); + return -1; + } + + if (fmt == AUD_FMT_S16 || fmt == AUD_FMT_U16) { + bits = 16; + endianess = 1; + } + + audio_pcm_init_info ( + &hw->info, + freq, + nchannels, + fmt, + /* Following is irrelevant actually since we do not use + mixengs clipping routines */ + audio_need_to_swap_endian (endianess) + ); + hw->bufsize = 4 * conf.buffer_frames * nchannels * bits; + + /* open default output device */ + propertySize = sizeof(core->outputDeviceID); + status = AudioHardwareGetProperty( + kAudioHardwarePropertyDefaultOutputDevice, + &propertySize, + &core->outputDeviceID); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Can not get default output Device\n"); + return -1; + } + if (core->outputDeviceID == kAudioDeviceUnknown) { + dolog ("Can not initialize %s - Unknown Audiodevice\n", typ); + return -1; + } + + /* set Buffersize to conf.buffer_frames frames */ + propertySize = sizeof(core->audioDevicePropertyBufferSize); + core->audioDevicePropertyBufferSize = + conf.buffer_frames * sizeof(float) * 2; + status = AudioDeviceSetProperty( + core->outputDeviceID, + NULL, + 0, + false, + kAudioDevicePropertyBufferSize, + propertySize, + &core->audioDevicePropertyBufferSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Can not set device buffer size %d\n", + kAudioDevicePropertyBufferSize); + return -1; + } + + /* get Buffersize */ + propertySize = sizeof(core->audioDevicePropertyBufferSize); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyBufferSize, + &propertySize, + &core->audioDevicePropertyBufferSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Can not get device buffer size\n"); + return -1; + } + + /* get StreamFormat */ + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyStreamFormat, + &propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Can not get Device Stream properties\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Samplerate */ + core->outputStreamBasicDescription.mSampleRate = (Float64)freq; + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceSetProperty( + core->outputDeviceID, + 0, + 0, + 0, + kAudioDevicePropertyStreamFormat, + propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Can not set samplerate %d\n", freq); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Callback */ + status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Can not set IOProc\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* start Playback */ + if (!core->isPlaying) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Can not start playback\n"); + AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + core->isPlaying = 1; + } + + return 0; +} + +static void coreaudio_fini_out (HWVoiceOut *hw) +{ + OSStatus status; + int err; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + /* stop playback */ + if (core->isPlaying) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Can not stop playback\n"); + } + core->isPlaying = 0; + } + + /* remove callback */ + status = AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Can not remove IOProc\n"); + } + core->outputDeviceID = kAudioDeviceUnknown; + + /* destroy mutex */ + err = pthread_mutex_destroy(&core->mutex); + if (err) { + dolog("Can not destroy mutex\nReason: %s\n", strerror (err)); + } +} + +static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + /* start playback */ + if (!core->isPlaying) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Can not unpause playback\n"); + } + core->isPlaying = 1; + } + break; + + case VOICE_DISABLE: + /* stop playback */ + if (core->isPlaying) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Can not pause playback\n"); + } + core->isPlaying = 0; + } + break; + } + return 0; +} + +static void *coreaudio_audio_init (void) +{ + return &coreaudio_audio_init; +} + +static void coreaudio_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option coreaudio_options[] = { + {"BUFFER_SIZE", AUD_OPT_INT, &conf.buffer_frames, + "Size of the buffer in frames", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} +}; + +static struct audio_pcm_ops coreaudio_pcm_ops = { + coreaudio_init_out, + coreaudio_fini_out, + coreaudio_run_out, + coreaudio_write, + coreaudio_ctl_out, + + NULL, + NULL, + NULL, + NULL, + NULL +}; + +struct audio_driver coreaudio_audio_driver = { + INIT_FIELD (name = ) "coreaudio", + INIT_FIELD (descr = ) + "CoreAudio http://developer.apple.com/audio/coreaudio.html", + INIT_FIELD (options = ) coreaudio_options, + INIT_FIELD (init = ) coreaudio_audio_init, + INIT_FIELD (fini = ) coreaudio_audio_fini, + INIT_FIELD (pcm_ops = ) &coreaudio_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 0, + INIT_FIELD (voice_size_out = ) sizeof (coreaudioVoiceOut), + INIT_FIELD (voice_size_in = ) 0 +}; diff --git a/audio/dsound_template.h b/audio/dsound_template.h new file mode 100644 index 0000000000..a04806eae0 --- /dev/null +++ b/audio/dsound_template.h @@ -0,0 +1,298 @@ +/* + * QEMU DirectSound audio driver header + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * 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. + */ +#ifdef DSBTYPE_IN +#define NAME "capture buffer" +#define TYPE in +#define IFACE IDirectSoundCaptureBuffer +#define BUFPTR LPDIRECTSOUNDCAPTUREBUFFER +#define FIELD dsound_capture_buffer +#else +#define NAME "playback buffer" +#define TYPE out +#define IFACE IDirectSoundBuffer +#define BUFPTR LPDIRECTSOUNDBUFFER +#define FIELD dsound_buffer +#endif + +static int glue (dsound_unlock_, TYPE) ( + BUFPTR buf, + LPVOID p1, + LPVOID p2, + DWORD blen1, + DWORD blen2 + ) +{ + HRESULT hr; + + hr = glue (IFACE, _Unlock) (buf, p1, blen1, p2, blen2); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not unlock " NAME "\n"); + return -1; + } + + return 0; +} + +static int glue (dsound_lock_, TYPE) ( + BUFPTR buf, + struct audio_pcm_info *info, + DWORD pos, + DWORD len, + LPVOID *p1p, + LPVOID *p2p, + DWORD *blen1p, + DWORD *blen2p, + int entire + ) +{ + HRESULT hr; + int i; + LPVOID p1 = NULL, p2 = NULL; + DWORD blen1 = 0, blen2 = 0; + + for (i = 0; i < conf.lock_retries; ++i) { + hr = glue (IFACE, _Lock) ( + buf, + pos, + len, + &p1, + &blen1, + &p2, + &blen2, + (entire +#ifdef DSBTYPE_IN + ? DSCBLOCK_ENTIREBUFFER +#else + ? DSBLOCK_ENTIREBUFFER +#endif + : 0) + ); + + if (FAILED (hr)) { +#ifndef DSBTYPE_IN + if (hr == DSERR_BUFFERLOST) { + if (glue (dsound_restore_, TYPE) (buf)) { + dsound_logerr (hr, "Can not lock " NAME "\n"); + goto fail; + } + continue; + } +#endif + dsound_logerr (hr, "Can not lock " NAME "\n"); + goto fail; + } + + break; + } + + if (i == conf.lock_retries) { + dolog ("%d attempts to lock " NAME " failed\n", i); + goto fail; + } + + if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) { + dolog ("DirectSound returned misaligned buffer %ld %ld\n", + blen1, blen2); + glue (dsound_unlock_, TYPE) (buf, p1, p2, blen1, blen2); + goto fail; + } + + if (!p1 && blen1) { + dolog ("warning: !p1 && blen1=%ld\n", blen1); + blen1 = 0; + } + + if (!p2 && blen2) { + dolog ("warning: !p2 && blen2=%ld\n", blen2); + blen2 = 0; + } + + *p1p = p1; + *p2p = p2; + *blen1p = blen1; + *blen2p = blen2; + return 0; + + fail: + *p1p = NULL - 1; + *p2p = NULL - 1; + *blen1p = -1; + *blen2p = -1; + return -1; +} + +#ifdef DSBTYPE_IN +static void dsound_fini_in (HWVoiceIn *hw) +#else +static void dsound_fini_out (HWVoiceOut *hw) +#endif +{ + HRESULT hr; +#ifdef DSBTYPE_IN + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +#else + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +#endif + + if (ds->FIELD) { + hr = glue (IFACE, _Stop) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not stop " NAME "\n"); + } + + hr = glue (IFACE, _Release) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not release " NAME "\n"); + } + ds->FIELD = NULL; + } +} + +#ifdef DSBTYPE_IN +static int dsound_init_in ( + HWVoiceIn *hw, + int freq, + int nchannels, + audfmt_e fmt + ) +#else +static int dsound_init_out ( + HWVoiceOut *hw, + int freq, + int nchannels, + audfmt_e fmt + ) +#endif +{ + int err; + HRESULT hr; + dsound *s = &glob_dsound; + WAVEFORMATEX wfx; + struct full_fmt full_fmt; +#ifdef DSBTYPE_IN + const char *typ = "ADC"; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + DSCBUFFERDESC bd; + DSCBCAPS bc; +#else + const char *typ = "DAC"; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + DSBUFFERDESC bd; + DSBCAPS bc; +#endif + + full_fmt.freq = freq; + full_fmt.nchannels = nchannels; + full_fmt.fmt = fmt; + err = waveformat_from_full_fmt (&wfx, &full_fmt); + if (err) { + return -1; + } + + memset (&bd, 0, sizeof (bd)); + bd.dwSize = sizeof (bd); + bd.lpwfxFormat = &wfx; +#ifdef DSBTYPE_IN + bd.dwBufferBytes = conf.bufsize_in; + hr = IDirectSoundCapture_CreateCaptureBuffer ( + s->dsound_capture, + &bd, + &ds->dsound_capture_buffer, + NULL + ); +#else + bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; + bd.dwBufferBytes = conf.bufsize_out; + hr = IDirectSound_CreateSoundBuffer ( + s->dsound, + &bd, + &ds->dsound_buffer, + NULL + ); +#endif + + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Can not create " NAME "\n"); + return -1; + } + + hr = glue (IFACE, _GetFormat) ( + ds->FIELD, + &wfx, + sizeof (wfx), + NULL + ); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Can not get " NAME " format\n"); + goto fail0; + } + +#ifdef DEBUG_DSOUND + dolog (NAME "\n"); + print_wave_format (&wfx); +#endif + + memset (&bc, 0, sizeof (bc)); + bc.dwSize = sizeof (bc); + + hr = glue (IFACE, _GetCaps) (ds->FIELD, &bc); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Can not get " NAME " format\n"); + goto fail0; + } + + err = waveformat_to_full_fmt (&wfx, &full_fmt); + if (err) { + goto fail0; + } + + ds->first_time = 1; + hw->bufsize = bc.dwBufferBytes; + audio_pcm_init_info ( + &hw->info, + full_fmt.freq, + full_fmt.nchannels, + full_fmt.fmt, + audio_need_to_swap_endian (0) + ); + +#ifdef DEBUG_DSOUND + dolog ("caps %ld, desc %ld\n", + bc.dwBufferBytes, bd.dwBufferBytes); + + dolog ("bufsize %d, freq %d, chan %d, fmt %d\n", + hw->bufsize, full_fmt.freq, full_fmt.nchannels, full_fmt.fmt); +#endif + return 0; + + fail0: + glue (dsound_fini_, TYPE) (hw); + return -1; +} + +#undef NAME +#undef TYPE +#undef IFACE +#undef BUFPTR +#undef FIELD diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c new file mode 100644 index 0000000000..64b84174d1 --- /dev/null +++ b/audio/dsoundaudio.c @@ -0,0 +1,1082 @@ +/* + * QEMU DirectSound audio driver + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * 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. + */ + +/* + * SEAL 1.07 by Carlos 'pel' Hasan was used as documentation + */ + +#include "vl.h" + +#define AUDIO_CAP "dsound" +#include "audio_int.h" + +#include +#include +#include + +/* #define DEBUG_DSOUND */ + +struct full_fmt { + int freq; + int nchannels; + audfmt_e fmt; +}; + +static struct { + int lock_retries; + int restore_retries; + int getstatus_retries; + int set_primary; + int bufsize_in; + int bufsize_out; + struct full_fmt full_fmt; + int latency_millis; +} conf = { + 1, + 1, + 1, + 0, + 16384, + 16384, + { + 44100, + 2, + AUD_FMT_S16 + }, + 10 +}; + +typedef struct { + LPDIRECTSOUND dsound; + LPDIRECTSOUNDCAPTURE dsound_capture; + LPDIRECTSOUNDBUFFER dsound_primary_buffer; + struct full_fmt fmt; +} dsound; + +static dsound glob_dsound; + +typedef struct { + HWVoiceOut hw; + LPDIRECTSOUNDBUFFER dsound_buffer; + DWORD old_pos; + int first_time; +#ifdef DEBUG_DSOUND + DWORD old_ppos; + DWORD played; + DWORD mixed; +#endif +} DSoundVoiceOut; + +typedef struct { + HWVoiceIn hw; + int first_time; + LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer; +} DSoundVoiceIn; + +static void dsound_log_hresult (HRESULT hr) +{ + const char *str = "BUG"; + + switch (hr) { + case DS_OK: + str = "The method succeeded"; + break; +#ifdef DS_NO_VIRTUALIZATION + case DS_NO_VIRTUALIZATION: + str = "The buffer was created, but another 3D algorithm was substituted"; + break; +#endif +#ifdef DS_INCOMPLETE + case DS_INCOMPLETE: + str = "The method succeeded, but not all the optional effects were obtained"; + break; +#endif +#ifdef DSERR_ACCESSDENIED + case DSERR_ACCESSDENIED: + str = "The request failed because access was denied"; + break; +#endif +#ifdef DSERR_ALLOCATED + case DSERR_ALLOCATED: + str = "The request failed because resources, such as a priority level, were already in use by another caller"; + break; +#endif +#ifdef DSERR_ALREADYINITIALIZED + case DSERR_ALREADYINITIALIZED: + str = "The object is already initialized"; + break; +#endif +#ifdef DSERR_BADFORMAT + case DSERR_BADFORMAT: + str = "The specified wave format is not supported"; + break; +#endif +#ifdef DSERR_BADSENDBUFFERGUID + case DSERR_BADSENDBUFFERGUID: + str = "The GUID specified in an audiopath file does not match a valid mix-in buffer"; + break; +#endif +#ifdef DSERR_BUFFERLOST + case DSERR_BUFFERLOST: + str = "The buffer memory has been lost and must be restored"; + break; +#endif +#ifdef DSERR_BUFFERTOOSMALL + case DSERR_BUFFERTOOSMALL: + str = "The buffer size is not great enough to enable effects processing"; + break; +#endif +#ifdef DSERR_CONTROLUNAVAIL + case DSERR_CONTROLUNAVAIL: + str = "The buffer control (volume, pan, and so on) requested by the caller is not available. Controls must be specified when the buffer is created, using the dwFlags member of DSBUFFERDESC"; + break; +#endif +#ifdef DSERR_DS8_REQUIRED + case DSERR_DS8_REQUIRED: + str = "A DirectSound object of class CLSID_DirectSound8 or later is required for the requested functionality. For more information, see IDirectSound8 Interface"; + break; +#endif +#ifdef DSERR_FXUNAVAILABLE + case DSERR_FXUNAVAILABLE: + str = "The effects requested could not be found on the system, or they are in the wrong order or in the wrong location; for example, an effect expected in hardware was found in software"; + break; +#endif +#ifdef DSERR_GENERIC + case DSERR_GENERIC : + str = "An undetermined error occurred inside the DirectSound subsystem"; + break; +#endif +#ifdef DSERR_INVALIDCALL + case DSERR_INVALIDCALL: + str = "This function is not valid for the current state of this object"; + break; +#endif +#ifdef DSERR_INVALIDPARAM + case DSERR_INVALIDPARAM: + str = "An invalid parameter was passed to the returning function"; + break; +#endif +#ifdef DSERR_NOAGGREGATION + case DSERR_NOAGGREGATION: + str = "The object does not support aggregation"; + break; +#endif +#ifdef DSERR_NODRIVER + case DSERR_NODRIVER: + str = "No sound driver is available for use, or the given GUID is not a valid DirectSound device ID"; + break; +#endif +#ifdef DSERR_NOINTERFACE + case DSERR_NOINTERFACE: + str = "The requested COM interface is not available"; + break; +#endif +#ifdef DSERR_OBJECTNOTFOUND + case DSERR_OBJECTNOTFOUND: + str = "The requested object was not found"; + break; +#endif +#ifdef DSERR_OTHERAPPHASPRIO + case DSERR_OTHERAPPHASPRIO: + str = "Another application has a higher priority level, preventing this call from succeeding"; + break; +#endif +#ifdef DSERR_OUTOFMEMORY + case DSERR_OUTOFMEMORY: + str = "The DirectSound subsystem could not allocate sufficient memory to complete the caller's request"; + break; +#endif +#ifdef DSERR_PRIOLEVELNEEDED + case DSERR_PRIOLEVELNEEDED: + str = "A cooperative level of DSSCL_PRIORITY or higher is required"; + break; +#endif +#ifdef DSERR_SENDLOOP + case DSERR_SENDLOOP: + str = "A circular loop of send effects was detected"; + break; +#endif +#ifdef DSERR_UNINITIALIZED + case DSERR_UNINITIALIZED: + str = "The Initialize method has not been called or has not been called successfully before other methods were called"; + break; +#endif +#ifdef DSERR_UNSUPPORTED + case DSERR_UNSUPPORTED: + str = "The function called is not supported at this time"; + break; +#endif + default: + AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT %#lx)\n", hr); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) dsound_logerr ( + HRESULT hr, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + dsound_log_hresult (hr); +} + +static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( + HRESULT hr, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + dsound_log_hresult (hr); +} + +static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +{ + return (millis * info->bytes_per_second) / 1000; +} + +#ifdef DEBUG_DSOUND +static void print_wave_format (WAVEFORMATEX *wfx) +{ + dolog ("tag = %d\n", wfx->wFormatTag); + dolog ("nChannels = %d\n", wfx->nChannels); + dolog ("nSamplesPerSec = %ld\n", wfx->nSamplesPerSec); + dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec); + dolog ("nBlockAlign = %d\n", wfx->nBlockAlign); + dolog ("wBitsPerSample = %d\n", wfx->wBitsPerSample); + dolog ("cbSize = %d\n", wfx->cbSize); +} +#endif + +static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb) +{ + HRESULT hr; + int i; + + for (i = 0; i < conf.restore_retries; ++i) { + hr = IDirectSoundBuffer_Restore (dsb); + + switch (hr) { + case DS_OK: + return 0; + + case DSERR_BUFFERLOST: + continue; + + default: + dsound_logerr (hr, "Can not restore playback buffer\n"); + return -1; + } + } + + dolog ("%d attempts to restore playback buffer failed\n", i); + return -1; +} + +static int waveformat_from_full_fmt (WAVEFORMATEX *wfx, + struct full_fmt *full_fmt) +{ + memset (wfx, 0, sizeof (*wfx)); + + wfx->wFormatTag = WAVE_FORMAT_PCM; + wfx->nChannels = full_fmt->nchannels; + wfx->nSamplesPerSec = full_fmt->freq; + wfx->nAvgBytesPerSec = full_fmt->freq << (full_fmt->nchannels == 2); + wfx->nBlockAlign = 1 << (full_fmt->nchannels == 2); + wfx->cbSize = 0; + + switch (full_fmt->fmt) { + case AUD_FMT_S8: + wfx->wBitsPerSample = 8; + break; + + case AUD_FMT_U8: + wfx->wBitsPerSample = 8; + break; + + case AUD_FMT_S16: + wfx->wBitsPerSample = 16; + wfx->nAvgBytesPerSec <<= 1; + wfx->nBlockAlign <<= 1; + break; + + case AUD_FMT_U16: + wfx->wBitsPerSample = 16; + wfx->nAvgBytesPerSec <<= 1; + wfx->nBlockAlign <<= 1; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", + full_fmt->freq); + return -1; + } + + return 0; +} + +static int waveformat_to_full_fmt (WAVEFORMATEX *wfx, + struct full_fmt *full_fmt) +{ + 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; + } + full_fmt->freq = wfx->nSamplesPerSec; + + switch (wfx->nChannels) { + case 1: + full_fmt->nchannels = 1; + break; + + case 2: + full_fmt->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: + full_fmt->fmt = AUD_FMT_U8; + break; + + case 16: + full_fmt->fmt = AUD_FMT_S16; + break; + + default: + dolog ("Invalid wave format, bits per sample is not 8 or 16, but %d\n", + wfx->wBitsPerSample); + return -1; + } + + return 0; +} + +#include "dsound_template.h" +#define DSBTYPE_IN +#include "dsound_template.h" +#undef DSBTYPE_IN + +static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp) +{ + HRESULT hr; + int i; + + for (i = 0; i < conf.getstatus_retries; ++i) { + hr = IDirectSoundBuffer_GetStatus (dsb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not get playback buffer status\n"); + return -1; + } + + if (*statusp & DSERR_BUFFERLOST) { + if (dsound_restore_out (dsb)) { + return -1; + } + continue; + } + break; + } + + return 0; +} + +static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, + DWORD *statusp) +{ + HRESULT hr; + + hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not get capture buffer status\n"); + return -1; + } + + return 0; +} + +static void dsound_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) +{ + int src_len1 = dst_len; + int src_len2 = 0; + int pos = hw->rpos + dst_len; + st_sample_t *src1 = hw->mix_buf + hw->rpos; + st_sample_t *src2 = NULL; + + if (pos > hw->samples) { + src_len1 = hw->samples - hw->rpos; + src2 = hw->mix_buf; + src_len2 = dst_len - src_len1; + pos = src_len2; + } + + if (src_len1) { + hw->clip (dst, src1, src_len1); + mixeng_clear (src1, src_len1); + } + + if (src_len2) { + dst = advance (dst, src_len1 << hw->info.shift); + hw->clip (dst, src2, src_len2); + mixeng_clear (src2, src_len2); + } + + hw->rpos = pos % hw->samples; +} + +static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb) +{ + int err; + LPVOID p1, p2; + DWORD blen1, blen2, len1, len2; + + err = dsound_lock_out ( + dsb, + &hw->info, + 0, + hw->samples << hw->info.shift, + &p1, &p2, + &blen1, &blen2, + 1 + ); + if (err) { + return; + } + + len1 = blen1 >> hw->info.shift; + len2 = blen2 >> hw->info.shift; + +#ifdef DEBUG_DSOUND + dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", + p1, blen1, len1, + p2, blen2, len2); +#endif + + if (p1 && len1) { + audio_pcm_info_clear_buf (&hw->info, p1, len1); + } + + if (p2 && len2) { + audio_pcm_info_clear_buf (&hw->info, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); +} + +static void dsound_close (dsound *s) +{ + HRESULT hr; + + if (s->dsound_primary_buffer) { + hr = IDirectSoundBuffer_Release (s->dsound_primary_buffer); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not release primary buffer\n"); + } + s->dsound_primary_buffer = NULL; + } +} + +static int dsound_open (dsound *s) +{ + int err; + HRESULT hr; + WAVEFORMATEX wfx; + DSBUFFERDESC dsbd; + HWND hwnd; + + hwnd = GetForegroundWindow (); + hr = IDirectSound_SetCooperativeLevel ( + s->dsound, + hwnd, + DSSCL_PRIORITY + ); + + if (FAILED (hr)) { + dsound_logerr (hr, "Can not set cooperative level for window %p\n", + hwnd); + return -1; + } + + if (!conf.set_primary) { + return 0; + } + + err = waveformat_from_full_fmt (&wfx, &conf.full_fmt); + if (err) { + return -1; + } + + memset (&dsbd, 0, sizeof (dsbd)); + dsbd.dwSize = sizeof (dsbd); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = NULL; + + hr = IDirectSound_CreateSoundBuffer ( + s->dsound, + &dsbd, + &s->dsound_primary_buffer, + NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not create primary playback buffer\n"); + return -1; + } + + hr = IDirectSoundBuffer_SetFormat (s->dsound_primary_buffer, &wfx); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not set primary playback buffer format\n"); + } + + hr = IDirectSoundBuffer_GetFormat ( + s->dsound_primary_buffer, + &wfx, + sizeof (wfx), + NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not get primary playback buffer format\n"); + goto fail0; + } + +#ifdef DEBUG_DSOUND + dolog ("Primary\n"); + print_wave_format (&wfx); +#endif + + err = waveformat_to_full_fmt (&wfx, &s->fmt); + if (err) { + goto fail0; + } + + return 0; + + fail0: + dsound_close (s); + return -1; +} + +static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + + if (!dsb) { + dolog ("Attempt to control voice without a buffer\n"); + return 0; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_out (dsb, &status)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + dolog ("warning: voice is already playing\n"); + return 0; + } + + dsound_clear_sample (hw, dsb); + + hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not start playing buffer\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_out (dsb, &status)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + hr = IDirectSoundBuffer_Stop (dsb); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not stop playing buffer\n"); + return -1; + } + } + else { + dolog ("warning: voice is not playing\n"); + } + break; + } + return 0; +} + +static int dsound_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int dsound_run_out (HWVoiceOut *hw) +{ + int err; + HRESULT hr; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + int live, len, hwshift; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD wpos, ppos, old_pos; + LPVOID p1, p2; + + if (!dsb) { + dolog ("Attempt to run empty with playback buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + + live = audio_pcm_hw_get_live_out (hw); + + hr = IDirectSoundBuffer_GetCurrentPosition ( + dsb, + &ppos, + ds->first_time ? &wpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not get playback buffer position\n"); + return 0; + } + + len = live << hwshift; + + if (ds->first_time) { + if (conf.latency_millis) { + DWORD cur_blat = audio_ring_dist (wpos, ppos, hw->bufsize); + + ds->first_time = 0; + old_pos = wpos; + old_pos += + millis_to_bytes (&hw->info, conf.latency_millis) - cur_blat; + old_pos %= hw->bufsize; + old_pos &= ~hw->info.align; + } + else { + old_pos = wpos; + } +#ifdef DEBUG_DSOUND + ds->played = 0; + ds->mixed = 0; +#endif + } + else { + if (ds->old_pos == ppos) { +#ifdef DEBUG_DSOUND + dolog ("old_pos == ppos\n"); +#endif + return 0; + } + +#ifdef DEBUG_DSOUND + ds->played += audio_ring_dist (ds->old_pos, ppos, hw->bufsize); +#endif + old_pos = ds->old_pos; + } + + if ((old_pos < ppos) && ((old_pos + len) > ppos)) { + len = ppos - old_pos; + } + else { + if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->bufsize))) { + len = hw->bufsize - old_pos + ppos; + } + } + + if (audio_bug (AUDIO_FUNC, len < 0 || len > hw->bufsize)) { + dolog ("len=%d hw->bufsize=%d old_pos=%ld ppos=%ld\n", + len, hw->bufsize, old_pos, ppos); + return 0; + } + + len &= ~hw->info.align; + if (!len) { + return 0; + } + +#ifdef DEBUG_DSOUND + ds->old_ppos = ppos; +#endif + err = dsound_lock_out ( + dsb, + &hw->info, + old_pos, + len, + &p1, &p2, + &blen1, &blen2, + 0 + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + dsound_write_sample (hw, p1, len1); + } + + if (p2 && len2) { + dsound_write_sample (hw, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); + ds->old_pos = (old_pos + (decr << hwshift)) % hw->bufsize; + +#ifdef DEBUG_DSOUND + ds->mixed += decr << hwshift; + + dolog ("played %lu mixed %lu diff %ld sec %f\n", + ds->played, + ds->mixed, + ds->mixed - ds->played, + abs (ds->mixed - ds->played) / (double) hw->info.bytes_per_second); +#endif + return decr; +} + +static int dsound_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + + if (!dscb) { + dolog ("Attempt to control capture voice without a buffer\n"); + return -1; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + dolog ("warning: voice is already capturing\n"); + return 0; + } + + /* clear ?? */ + + hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not start capturing\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + hr = IDirectSoundCaptureBuffer_Stop (dscb); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not stop capturing\n"); + return -1; + } + } + else { + dolog ("warning: voice is not capturing\n"); + } + break; + } + return 0; +} + +static int dsound_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int dsound_run_in (HWVoiceIn *hw) +{ + int err; + HRESULT hr; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + int live, len, dead; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD cpos, rpos; + LPVOID p1, p2; + int hwshift; + + if (!dscb) { + dolog ("Attempt to run without capture buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + if (!dead) { + return 0; + } + + hr = IDirectSoundCaptureBuffer_GetCurrentPosition ( + dscb, + &cpos, + ds->first_time ? &rpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not get capture buffer position\n"); + return 0; + } + + if (ds->first_time) { + ds->first_time = 0; + if (rpos & hw->info.align) { + ldebug ("warning: misaligned capture read position %ld(%d)\n", + rpos, hw->info.align); + } + hw->wpos = rpos >> hwshift; + } + + if (cpos & hw->info.align) { + ldebug ("warning: misaligned capture position %ld(%d)\n", + cpos, hw->info.align); + } + cpos >>= hwshift; + + len = audio_ring_dist (cpos, hw->wpos, hw->samples); + if (!len) { + return 0; + } + len = audio_MIN (len, dead); + + err = dsound_lock_in ( + dscb, + &hw->info, + hw->wpos << hwshift, + len << hwshift, + &p1, + &p2, + &blen1, + &blen2, + 0 + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + hw->conv (hw->conv_buf + hw->wpos, p1, len1, &nominal_volume); + } + + if (p2 && len2) { + hw->conv (hw->conv_buf, p2, len2, &nominal_volume); + } + + dsound_unlock_in (dscb, p1, p2, blen1, blen2); + hw->wpos = (hw->wpos + decr) % hw->samples; + return decr; +} + +static void dsound_audio_fini (void *opaque) +{ + HRESULT hr; + dsound *s = opaque; + + if (!s->dsound) { + return; + } + + hr = IDirectSound_Release (s->dsound); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not release DirectSound\n"); + } + s->dsound = NULL; + + if (!s->dsound_capture) { + return; + } + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; +} + +static void *dsound_audio_init (void) +{ + int err; + HRESULT hr; + dsound *s = &glob_dsound; + + hr = CoInitialize (NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not initialize COM\n"); + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSound, + NULL, + CLSCTX_ALL, + &IID_IDirectSound, + (void **) &s->dsound + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not create DirectSound instance\n"); + return NULL; + } + + hr = IDirectSound_Initialize (s->dsound, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not initialize DirectSound\n"); + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSoundCapture, + NULL, + CLSCTX_ALL, + &IID_IDirectSoundCapture, + (void **) &s->dsound_capture + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not create DirectSoundCapture instance\n"); + } + else { + hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not initialize DirectSoundCapture\n"); + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Can not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; + } + } + + err = dsound_open (s); + if (err) { + dsound_audio_fini (s); + return NULL; + } + + return s; +} + +static struct audio_option dsound_options[] = { + {"LOCK_RETRIES", AUD_OPT_INT, &conf.lock_retries, + "Number of times to attempt locking the buffer", NULL, 0}, + {"RESTOURE_RETRIES", AUD_OPT_INT, &conf.restore_retries, + "Number of times to attempt restoring the buffer", NULL, 0}, + {"GETSTATUS_RETRIES", AUD_OPT_INT, &conf.getstatus_retries, + "Number of times to attempt getting status of the buffer", NULL, 0}, + {"SET_PRIMARY", AUD_OPT_BOOL, &conf.set_primary, + "Set the parameters of primary buffer", NULL, 0}, + {"LATENCY_MILLIS", AUD_OPT_INT, &conf.latency_millis, + "(undocumented)", NULL, 0}, + {"PRIMARY_FREQ", AUD_OPT_INT, &conf.full_fmt.freq, + "Primary buffer frequency", NULL, 0}, + {"PRIMARY_CHANNELS", AUD_OPT_INT, &conf.full_fmt.nchannels, + "Primary buffer number of channels (1 - mono, 2 - stereo)", NULL, 0}, + {"PRIMARY_FMT", AUD_OPT_FMT, &conf.full_fmt.fmt, + "Primary buffer format", NULL, 0}, + {"BUFSIZE_OUT", AUD_OPT_INT, &conf.bufsize_out, + "(undocumented)", NULL, 0}, + {"BUFSIZE_IN", AUD_OPT_INT, &conf.bufsize_in, + "(undocumented)", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} +}; + +static struct audio_pcm_ops dsound_pcm_ops = { + dsound_init_out, + dsound_fini_out, + dsound_run_out, + dsound_write, + dsound_ctl_out, + + dsound_init_in, + dsound_fini_in, + dsound_run_in, + dsound_read, + dsound_ctl_in +}; + +struct audio_driver dsound_audio_driver = { + INIT_FIELD (name = ) "dsound", + INIT_FIELD (descr = ) + "DirectSound http://wikipedia.org/wiki/DirectSound", + INIT_FIELD (options = ) dsound_options, + INIT_FIELD (init = ) dsound_audio_init, + INIT_FIELD (fini = ) dsound_audio_fini, + INIT_FIELD (pcm_ops = ) &dsound_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) 1, + INIT_FIELD (voice_size_out = ) sizeof (DSoundVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (DSoundVoiceIn) +}; diff --git a/audio/fmodaudio.c b/audio/fmodaudio.c index 7b79026a82..36b8d47c43 100644 --- a/audio/fmodaudio.c +++ b/audio/fmodaudio.c @@ -1,8 +1,8 @@ /* - * QEMU FMOD audio output driver - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * QEMU FMOD audio driver + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -25,53 +25,77 @@ #include #include "vl.h" -#include "audio/audio_int.h" +#define AUDIO_CAP "fmod" +#include "audio_int.h" -typedef struct FMODVoice { - HWVoice hw; +typedef struct FMODVoiceOut { + HWVoiceOut hw; unsigned int old_pos; FSOUND_SAMPLE *fmod_sample; int channel; -} FMODVoice; +} FMODVoiceOut; -#define dolog(...) AUD_log ("fmod", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) -#endif - -#define QC_FMOD_DRV "QEMU_FMOD_DRV" -#define QC_FMOD_FREQ "QEMU_FMOD_FREQ" -#define QC_FMOD_SAMPLES "QEMU_FMOD_SAMPLES" -#define QC_FMOD_CHANNELS "QEMU_FMOD_CHANNELS" -#define QC_FMOD_BUFSIZE "QEMU_FMOD_BUFSIZE" -#define QC_FMOD_THRESHOLD "QEMU_FMOD_THRESHOLD" +typedef struct FMODVoiceIn { + HWVoiceIn hw; + FSOUND_SAMPLE *fmod_sample; +} FMODVoiceIn; static struct { + const char *drvname; int nb_samples; int freq; int nb_channels; int bufsize; int threshold; + int broken_adc; } conf = { - 2048, + NULL, + 2048 * 2, 44100, - 1, + 2, 0, - 128 + 0, + 0 }; -#define errstr() FMOD_ErrorString (FSOUND_GetError ()) - -static int fmod_hw_write (SWVoice *sw, void *buf, int len) +static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...) { - return pcm_hw_write (sw, buf, len); + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", + FMOD_ErrorString (FSOUND_GetError ())); } -static void fmod_clear_sample (FMODVoice *fmd) +static void GCC_FMT_ATTR (2, 3) fmod_logerr2 ( + const char *typ, + const char *fmt, + ... + ) { - HWVoice *hw = &fmd->hw; + va_list ap; + + AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", + FMOD_ErrorString (FSOUND_GetError ())); +} + +static int fmod_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static void fmod_clear_sample (FMODVoiceOut *fmd) +{ + HWVoiceOut *hw = &fmd->hw; int status; void *p1 = 0, *p2 = 0; unsigned int len1 = 0, len2 = 0; @@ -79,7 +103,7 @@ static void fmod_clear_sample (FMODVoice *fmd) status = FSOUND_Sample_Lock ( fmd->fmod_sample, 0, - hw->samples << hw->shift, + hw->samples << hw->info.shift, &p1, &p2, &len1, @@ -87,78 +111,88 @@ static void fmod_clear_sample (FMODVoice *fmd) ); if (!status) { - dolog ("Failed to lock sample\nReason: %s\n", errstr ()); + fmod_logerr ("Failed to lock sample\n"); return; } - if ((len1 & hw->align) || (len2 & hw->align)) { - dolog ("Locking sample returned unaligned length %d, %d\n", - len1, len2); + if ((len1 & hw->info.align) || (len2 & hw->info.align)) { + dolog ("Lock returned misaligned length %d, %d, alignment %d\n", + len1, len2, hw->info.align + 1); goto fail; } - if (len1 + len2 != hw->samples << hw->shift) { - dolog ("Locking sample returned incomplete length %d, %d\n", - len1 + len2, hw->samples << hw->shift); + if ((len1 + len2) - (hw->samples << hw->info.shift)) { + dolog ("Lock returned incomplete length %d, %d\n", + len1 + len2, hw->samples << hw->info.shift); goto fail; } - pcm_hw_clear (hw, p1, hw->samples); + + audio_pcm_info_clear_buf (&hw->info, p1, hw->samples); fail: status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2); if (!status) { - dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); + fmod_logerr ("Failed to unlock sample\n"); } } -static int fmod_write_sample (HWVoice *hw, uint8_t *dst, st_sample_t *src, - int src_size, int src_pos, int dst_len) +static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) { - int src_len1 = dst_len, src_len2 = 0, pos = src_pos + dst_len; - st_sample_t *src1 = src + src_pos, *src2 = 0; + int src_len1 = dst_len; + int src_len2 = 0; + int pos = hw->rpos + dst_len; + st_sample_t *src1 = hw->mix_buf + hw->rpos; + st_sample_t *src2 = NULL; - if (src_pos + dst_len > src_size) { - src_len1 = src_size - src_pos; - src2 = src; + if (pos > hw->samples) { + src_len1 = hw->samples - hw->rpos; + src2 = hw->mix_buf; src_len2 = dst_len - src_len1; pos = src_len2; } if (src_len1) { hw->clip (dst, src1, src_len1); - memset (src1, 0, src_len1 * sizeof (st_sample_t)); - advance (dst, src_len1); + mixeng_clear (src1, src_len1); } if (src_len2) { + dst = advance (dst, src_len1 << hw->info.shift); hw->clip (dst, src2, src_len2); - memset (src2, 0, src_len2 * sizeof (st_sample_t)); + mixeng_clear (src2, src_len2); } - return pos; + + hw->rpos = pos % hw->samples; } -static int fmod_unlock_sample (FMODVoice *fmd, void *p1, void *p2, +static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2, unsigned int blen1, unsigned int blen2) { - int status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, blen1, blen2); + int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2); if (!status) { - dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); + fmod_logerr ("Failed to unlock sample\n"); return -1; } return 0; } -static int fmod_lock_sample (FMODVoice *fmd, int pos, int len, - void **p1, void **p2, - unsigned int *blen1, unsigned int *blen2) +static int fmod_lock_sample ( + FSOUND_SAMPLE *sample, + struct audio_pcm_info *info, + int pos, + int len, + void **p1, + void **p2, + unsigned int *blen1, + unsigned int *blen2 + ) { - HWVoice *hw = &fmd->hw; int status; status = FSOUND_Sample_Lock ( - fmd->fmod_sample, - pos << hw->shift, - len << hw->shift, + sample, + pos << info->shift, + len << info->shift, p1, p2, blen1, @@ -166,89 +200,117 @@ static int fmod_lock_sample (FMODVoice *fmd, int pos, int len, ); if (!status) { - dolog ("Failed to lock sample\nReason: %s\n", errstr ()); + fmod_logerr ("Failed to lock sample\n"); return -1; } - if ((*blen1 & hw->align) || (*blen2 & hw->align)) { - dolog ("Locking sample returned unaligned length %d, %d\n", - *blen1, *blen2); - fmod_unlock_sample (fmd, *p1, *p2, *blen1, *blen2); + if ((*blen1 & info->align) || (*blen2 & info->align)) { + dolog ("Lock returned misaligned length %d, %d, alignment %d\n", + *blen1, *blen2, info->align + 1); + + fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2); + + *p1 = NULL - 1; + *p2 = NULL - 1; + *blen1 = ~0U; + *blen2 = ~0U; return -1; } + + if (!*p1 && *blen1) { + dolog ("warning: !p1 && blen1=%d\n", *blen1); + *blen1 = 0; + } + + if (!p2 && *blen2) { + dolog ("warning: !p2 && blen2=%d\n", *blen2); + *blen2 = 0; + } + return 0; } -static void fmod_hw_run (HWVoice *hw) +static int fmod_run_out (HWVoiceOut *hw) { - FMODVoice *fmd = (FMODVoice *) hw; - int rpos, live, decr; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; + int live, decr; void *p1 = 0, *p2 = 0; unsigned int blen1 = 0, blen2 = 0; unsigned int len1 = 0, len2 = 0; - int nb_active; + int nb_live; - live = pcm_hw_get_live2 (hw, &nb_active); - if (live <= 0) { - return; + live = audio_pcm_hw_get_live_out2 (hw, &nb_live); + if (!live) { + return 0; } if (!hw->pending_disable - && nb_active - && conf.threshold - && live <= conf.threshold) { - ldebug ("live=%d nb_active=%d\n", live, nb_active); - return; + && nb_live + && (conf.threshold && live <= conf.threshold)) { + ldebug ("live=%d nb_live=%d\n", live, nb_live); + return 0; } decr = live; -#if 1 if (fmd->channel >= 0) { - int pos2 = (fmd->old_pos + decr) % hw->samples; - int pos = FSOUND_GetCurrentPosition (fmd->channel); + int len = decr; + int old_pos = fmd->old_pos; + int ppos = FSOUND_GetCurrentPosition (fmd->channel); - if (fmd->old_pos < pos && pos2 >= pos) { - decr = pos - fmd->old_pos - (pos2 == pos) - 1; + if (ppos == old_pos || !ppos) { + return 0; } - else if (fmd->old_pos > pos && pos2 >= pos && pos2 < fmd->old_pos) { - decr = (hw->samples - fmd->old_pos) + pos - (pos2 == pos) - 1; + + if ((old_pos < ppos) && ((old_pos + len) > ppos)) { + len = ppos - old_pos; } -/* ldebug ("pos=%d pos2=%d old=%d live=%d decr=%d\n", */ -/* pos, pos2, fmd->old_pos, live, decr); */ - } -#endif + else { + if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) { + len = hw->samples - old_pos + ppos; + } + } + decr = len; - if (decr <= 0) { - return; + if (audio_bug (AUDIO_FUNC, decr < 0)) { + dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n", + decr, live, ppos, old_pos, len); + return 0; + } } - if (fmod_lock_sample (fmd, fmd->old_pos, decr, &p1, &p2, &blen1, &blen2)) { - return; + + if (!decr) { + return 0; } - len1 = blen1 >> hw->shift; - len2 = blen2 >> hw->shift; + if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, + fmd->old_pos, decr, + &p1, &p2, + &blen1, &blen2)) { + return 0; + } + + len1 = blen1 >> hw->info.shift; + len2 = blen2 >> hw->info.shift; ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2); decr = len1 + len2; - rpos = hw->rpos; - if (len1) { - rpos = fmod_write_sample (hw, p1, hw->mix_buf, hw->samples, rpos, len1); + if (p1 && len1) { + fmod_write_sample (hw, p1, len1); } - if (len2) { - rpos = fmod_write_sample (hw, p2, hw->mix_buf, hw->samples, rpos, len2); + if (p2 && len2) { + fmod_write_sample (hw, p2, len2); } - fmod_unlock_sample (fmd, p1, p2, blen1, blen2); + fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); - pcm_hw_dec_live (hw, decr); - hw->rpos = rpos % hw->samples; fmd->old_pos = (fmd->old_pos + decr) % hw->samples; + return decr; } -static int AUD_to_fmodfmt (audfmt_e fmt, int stereo) +static int aud_to_fmodfmt (audfmt_e fmt, int stereo) { int mode = FSOUND_LOOP_NORMAL; @@ -270,16 +332,19 @@ static int AUD_to_fmodfmt (audfmt_e fmt, int stereo) break; default: - dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); - exit (EXIT_FAILURE); + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_FMOD + abort (); +#endif + mode |= FSOUND_8BITS; } mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; return mode; } -static void fmod_hw_fini (HWVoice *hw) +static void fmod_fini_out (HWVoiceOut *hw) { - FMODVoice *fmd = (FMODVoice *) hw; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; if (fmd->fmod_sample) { FSOUND_Sample_Free (fmd->fmod_sample); @@ -291,12 +356,12 @@ static void fmod_hw_fini (HWVoice *hw) } } -static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +static int fmod_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) { int bits16, mode, channel; - FMODVoice *fmd = (FMODVoice *) hw; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; - mode = AUD_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); + mode = aud_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); fmd->fmod_sample = FSOUND_Sample_Alloc ( FSOUND_FREE, /* index */ conf.nb_samples, /* length */ @@ -308,52 +373,145 @@ static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) ); if (!fmd->fmod_sample) { - dolog ("Failed to allocate FMOD sample\nReason: %s\n", errstr ()); + fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n"); return -1; } channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1); if (channel < 0) { - dolog ("Failed to start playing sound\nReason: %s\n", errstr ()); + fmod_logerr2 ("DAC", "Failed to start playing sound\n"); FSOUND_Sample_Free (fmd->fmod_sample); return -1; } fmd->channel = channel; - hw->freq = freq; - hw->fmt = fmt; - hw->nchannels = nchannels; - bits16 = fmt == AUD_FMT_U16 || fmt == AUD_FMT_S16; + /* FMOD always operates on little endian frames? */ + audio_pcm_init_info (&hw->info, freq, nchannels, fmt, + audio_need_to_swap_endian (0)); + bits16 = (mode & FSOUND_16BITS) != 0; hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; return 0; } -static int fmod_hw_ctl (HWVoice *hw, int cmd, ...) +static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...) { int status; - FMODVoice *fmd = (FMODVoice *) hw; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; switch (cmd) { case VOICE_ENABLE: fmod_clear_sample (fmd); status = FSOUND_SetPaused (fmd->channel, 0); if (!status) { - dolog ("Failed to resume channel %d\nReason: %s\n", - fmd->channel, errstr ()); + fmod_logerr ("Failed to resume channel %d\n", fmd->channel); } break; case VOICE_DISABLE: status = FSOUND_SetPaused (fmd->channel, 1); if (!status) { - dolog ("Failed to pause channel %d\nReason: %s\n", - fmd->channel, errstr ()); + fmod_logerr ("Failed to pause channel %d\n", fmd->channel); } break; } return 0; } +static int fmod_init_in (HWVoiceIn *hw, int freq, int nchannels, audfmt_e fmt) +{ + int bits16, mode; + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + + if (conf.broken_adc) { + return -1; + } + + mode = aud_to_fmodfmt (fmt, nchannels == 2 ? 1 : 0); + fmd->fmod_sample = FSOUND_Sample_Alloc ( + FSOUND_FREE, /* index */ + conf.nb_samples, /* length */ + mode, /* mode */ + freq, /* freq */ + 255, /* volume */ + 128, /* pan */ + 255 /* priority */ + ); + + if (!fmd->fmod_sample) { + fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n"); + return -1; + } + + /* FMOD always operates on little endian frames? */ + audio_pcm_init_info (&hw->info, freq, nchannels, fmt, + audio_need_to_swap_endian (0)); + bits16 = (mode & FSOUND_16BITS) != 0; + hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; + return 0; +} + +static void fmod_fini_in (HWVoiceIn *hw) +{ + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + + if (fmd->fmod_sample) { + FSOUND_Record_Stop (); + FSOUND_Sample_Free (fmd->fmod_sample); + fmd->fmod_sample = 0; + } +} + +static int fmod_run_in (HWVoiceIn *hw) +{ + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + int hwshift = hw->info.shift; + int live, dead, new_pos, len; + unsigned int blen1 = 0, blen2 = 0; + unsigned int len1, len2; + unsigned int decr; + void *p1, *p2; + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + if (!dead) { + return 0; + } + + new_pos = FSOUND_Record_GetPosition (); + if (new_pos < 0) { + fmod_logerr ("Can not get recording position\n"); + return 0; + } + + len = audio_ring_dist (new_pos, hw->wpos, hw->samples); + if (!len) { + return 0; + } + len = audio_MIN (len, dead); + + if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, + hw->wpos, len, + &p1, &p2, + &blen1, &blen2)) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && blen1) { + hw->conv (hw->conv_buf + hw->wpos, p1, len1, &nominal_volume); + } + if (p2 && len2) { + hw->conv (hw->conv_buf, p2, len2, &nominal_volume); + } + + fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); + hw->wpos = (hw->wpos + decr) % hw->samples; + return decr; +} + static struct { const char *name; int type; @@ -378,16 +536,16 @@ static struct { {"ps2", FSOUND_OUTPUT_PS2}, {"gcube", FSOUND_OUTPUT_GC}, #endif - {"nort", FSOUND_OUTPUT_NOSOUND_NONREALTIME} + {"none-realtime", FSOUND_OUTPUT_NOSOUND_NONREALTIME} }; static void *fmod_audio_init (void) { - int i; + size_t i; double ver; int status; int output_type = -1; - const char *drv = audio_get_conf_str (QC_FMOD_DRV, NULL); + const char *drv = conf.drvname; ver = FSOUND_GetVersion (); if (ver < FMOD_VERSION) { @@ -395,6 +553,14 @@ static void *fmod_audio_init (void) return NULL; } +#ifdef __linux__ + if (ver < 3.75) { + dolog ("FMOD before 3.75 has bug preventing ADC from working\n" + "ADC will be disabled.\n"); + conf.broken_adc = 1; + } +#endif + if (drv) { int found = 0; for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { @@ -405,65 +571,115 @@ static void *fmod_audio_init (void) } } if (!found) { - dolog ("Unknown FMOD output driver `%s'\n", drv); + dolog ("Unknown FMOD driver `%s'\n", drv); + dolog ("Valid drivers:\n"); + for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { + dolog (" %s\n", drvtab[i].name); + } } } if (output_type != -1) { status = FSOUND_SetOutput (output_type); if (!status) { - dolog ("FSOUND_SetOutput(%d) failed\nReason: %s\n", - output_type, errstr ()); + fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type); return NULL; } } - conf.freq = audio_get_conf_int (QC_FMOD_FREQ, conf.freq); - conf.nb_samples = audio_get_conf_int (QC_FMOD_SAMPLES, conf.nb_samples); - conf.nb_channels = - audio_get_conf_int (QC_FMOD_CHANNELS, - (audio_state.nb_hw_voices > 1 - ? audio_state.nb_hw_voices - : conf.nb_channels)); - conf.bufsize = audio_get_conf_int (QC_FMOD_BUFSIZE, conf.bufsize); - conf.threshold = audio_get_conf_int (QC_FMOD_THRESHOLD, conf.threshold); - if (conf.bufsize) { status = FSOUND_SetBufferSize (conf.bufsize); if (!status) { - dolog ("FSOUND_SetBufferSize (%d) failed\nReason: %s\n", - conf.bufsize, errstr ()); + fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize); } } status = FSOUND_Init (conf.freq, conf.nb_channels, 0); if (!status) { - dolog ("FSOUND_Init failed\nReason: %s\n", errstr ()); + fmod_logerr ("FSOUND_Init failed\n"); return NULL; } return &conf; } +static int fmod_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + int status; + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + + switch (cmd) { + case VOICE_ENABLE: + status = FSOUND_Record_StartSample (fmd->fmod_sample, 1); + if (!status) { + fmod_logerr ("Failed to start recording\n"); + } + break; + + case VOICE_DISABLE: + status = FSOUND_Record_Stop (); + if (!status) { + fmod_logerr ("Failed to stop recording\n"); + } + break; + } + return 0; +} + static void fmod_audio_fini (void *opaque) { + (void) opaque; FSOUND_Close (); } -struct pcm_ops fmod_pcm_ops = { - fmod_hw_init, - fmod_hw_fini, - fmod_hw_run, - fmod_hw_write, - fmod_hw_ctl +static struct audio_option fmod_options[] = { + {"DRV", AUD_OPT_STR, &conf.drvname, + "FMOD driver", NULL, 0}, + {"FREQ", AUD_OPT_INT, &conf.freq, + "Default frequency", NULL, 0}, + {"SAMPLES", AUD_OPT_INT, &conf.nb_samples, + "Buffer size in samples", NULL, 0}, + {"CHANNELS", AUD_OPT_INT, &conf.nb_channels, + "Number of default channels (1 - mono, 2 - stereo)", NULL, 0}, + {"BUFSIZE", AUD_OPT_INT, &conf.bufsize, + "(undocumented)", NULL, 0}, +#if 0 + {"THRESHOLD", AUD_OPT_INT, &conf.threshold, + "(undocumented)"}, +#endif + + {NULL, 0, NULL, NULL, NULL, 0} }; -struct audio_output_driver fmod_output_driver = { - "fmod", - fmod_audio_init, - fmod_audio_fini, - &fmod_pcm_ops, - 1, - INT_MAX, - sizeof (FMODVoice) +static struct audio_pcm_ops fmod_pcm_ops = { + fmod_init_out, + fmod_fini_out, + fmod_run_out, + fmod_write, + fmod_ctl_out, + + fmod_init_in, + fmod_fini_in, + fmod_run_in, + fmod_read, + fmod_ctl_in +}; + +struct audio_driver fmod_audio_driver = { + INIT_FIELD (name = ) "fmod", + INIT_FIELD (descr = ) "FMOD 3.xx http://www.fmod.org", + INIT_FIELD (options = ) fmod_options, + INIT_FIELD (init = ) fmod_audio_init, + INIT_FIELD (fini = ) fmod_audio_fini, + INIT_FIELD (pcm_ops = ) &fmod_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) INT_MAX, + INIT_FIELD (voice_size_out = ) sizeof (FMODVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (FMODVoiceIn) }; diff --git a/audio/mixeng.c b/audio/mixeng.c index b0bb412c63..d43c5e59d7 100644 --- a/audio/mixeng.c +++ b/audio/mixeng.c @@ -1,7 +1,7 @@ /* * QEMU Mixing engine * - * Copyright (c) 2004 Vassili Karpov (malc) + * Copyright (c) 2004-2005 Vassili Karpov (malc) * Copyright (c) 1998 Fabrice Bellard * * Permission is hereby granted, free of charge, to any person obtaining a copy @@ -23,87 +23,174 @@ * THE SOFTWARE. */ #include "vl.h" -//#define DEBUG_FP -#include "audio/mixeng.h" +#define AUDIO_CAP "mixeng" +#include "audio_int.h" + +#define NOVOL + +/* 8 bit */ +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) + +/* Signed 8 bit */ #define IN_T int8_t -#define IN_MIN CHAR_MIN -#define IN_MAX CHAR_MAX +#define IN_MIN SCHAR_MIN +#define IN_MAX SCHAR_MAX #define SIGNED +#define SHIFT 8 #include "mixeng_template.h" #undef SIGNED #undef IN_MAX #undef IN_MIN #undef IN_T +#undef SHIFT +/* Unsigned 8 bit */ #define IN_T uint8_t #define IN_MIN 0 #define IN_MAX UCHAR_MAX +#define SHIFT 8 #include "mixeng_template.h" #undef IN_MAX #undef IN_MIN #undef IN_T +#undef SHIFT +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION + +/* Signed 16 bit */ #define IN_T int16_t #define IN_MIN SHRT_MIN #define IN_MAX SHRT_MAX #define SIGNED +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) #include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION #undef SIGNED #undef IN_MAX #undef IN_MIN #undef IN_T +#undef SHIFT #define IN_T uint16_t #define IN_MIN 0 #define IN_MAX USHRT_MAX +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) #include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION #undef IN_MAX #undef IN_MIN #undef IN_T +#undef SHIFT -t_sample *mixeng_conv[2][2][2] = { +t_sample *mixeng_conv[2][2][2][2] = { { { - conv_uint8_t_to_mono, - conv_uint16_t_to_mono + { + conv_natural_uint8_t_to_mono, + conv_natural_uint16_t_to_mono + }, + { + conv_natural_uint8_t_to_mono, + conv_swap_uint16_t_to_mono + } }, { - conv_int8_t_to_mono, - conv_int16_t_to_mono + { + conv_natural_int8_t_to_mono, + conv_natural_int16_t_to_mono + }, + { + conv_natural_int8_t_to_mono, + conv_swap_int16_t_to_mono + } } }, { { - conv_uint8_t_to_stereo, - conv_uint16_t_to_stereo + { + conv_natural_uint8_t_to_stereo, + conv_natural_uint16_t_to_stereo + }, + { + conv_natural_uint8_t_to_stereo, + conv_swap_uint16_t_to_stereo + } }, { - conv_int8_t_to_stereo, - conv_int16_t_to_stereo + { + conv_natural_int8_t_to_stereo, + conv_natural_int16_t_to_stereo + }, + { + conv_natural_int8_t_to_stereo, + conv_swap_int16_t_to_stereo + } } } }; -f_sample *mixeng_clip[2][2][2] = { +f_sample *mixeng_clip[2][2][2][2] = { { { - clip_uint8_t_from_mono, - clip_uint16_t_from_mono + { + clip_natural_uint8_t_from_mono, + clip_natural_uint16_t_from_mono + }, + { + clip_natural_uint8_t_from_mono, + clip_swap_uint16_t_from_mono + } }, { - clip_int8_t_from_mono, - clip_int16_t_from_mono + { + clip_natural_int8_t_from_mono, + clip_natural_int16_t_from_mono + }, + { + clip_natural_int8_t_from_mono, + clip_swap_int16_t_from_mono + } } }, { { - clip_uint8_t_from_stereo, - clip_uint16_t_from_stereo + { + clip_natural_uint8_t_from_stereo, + clip_natural_uint16_t_from_stereo + }, + { + clip_natural_uint8_t_from_stereo, + clip_swap_uint16_t_from_stereo + } }, { - clip_int8_t_from_stereo, - clip_int16_t_from_stereo + { + clip_natural_int8_t_from_stereo, + clip_natural_int16_t_from_stereo + }, + { + clip_natural_int8_t_from_stereo, + clip_swap_int16_t_from_stereo + } } } }; @@ -116,9 +203,9 @@ f_sample *mixeng_clip[2][2][2] = { * Contributors with a more efficient algorithm.] * * This source code is freely redistributable and may be used for - * any purpose. This copyright notice must be maintained. - * Lance Norskog And Sundry Contributors are not responsible for - * the consequences of using this software. + * any purpose. This copyright notice must be maintained. + * Lance Norskog And Sundry Contributors are not responsible for + * the consequences of using this software. */ /* @@ -156,21 +243,13 @@ void *st_rate_start (int inrate, int outrate) rate_t rate = (rate_t) qemu_mallocz (sizeof (struct ratestuff)); if (!rate) { - exit (EXIT_FAILURE); - } - - if (inrate == outrate) { - // exit (EXIT_FAILURE); - } - - if (inrate >= 65535 || outrate >= 65535) { - // exit (EXIT_FAILURE); + return NULL; } rate->opos = 0; /* increment */ - rate->opos_inc = (inrate * ((int64_t) UINT_MAX)) / outrate; + rate->opos_inc = ((uint64_t) inrate << 32) / outrate; rate->ipos = 0; rate->ilast.l = 0; @@ -178,78 +257,20 @@ void *st_rate_start (int inrate, int outrate) return rate; } -/* - * Processed signed long samples from ibuf to obuf. - * Return number of samples processed. - */ -void st_rate_flow (void *opaque, st_sample_t *ibuf, st_sample_t *obuf, - int *isamp, int *osamp) -{ - rate_t rate = (rate_t) opaque; - st_sample_t *istart, *iend; - st_sample_t *ostart, *oend; - st_sample_t ilast, icur, out; - int64_t t; +#define NAME st_rate_flow_mix +#define OP(a, b) a += b +#include "rate_template.h" - ilast = rate->ilast; - - istart = ibuf; - iend = ibuf + *isamp; - - ostart = obuf; - oend = obuf + *osamp; - - if (rate->opos_inc == 1ULL << 32) { - int i, n = *isamp > *osamp ? *osamp : *isamp; - for (i = 0; i < n; i++) { - obuf[i].l += ibuf[i].r; - obuf[i].r += ibuf[i].r; - } - *isamp = n; - *osamp = n; - return; - } - - while (obuf < oend) { - - /* Safety catch to make sure we have input samples. */ - if (ibuf >= iend) - break; - - /* read as many input samples so that ipos > opos */ - - while (rate->ipos <= (rate->opos >> 32)) { - ilast = *ibuf++; - rate->ipos++; - /* See if we finished the input buffer yet */ - if (ibuf >= iend) goto the_end; - } - - icur = *ibuf; - - /* interpolate */ - t = rate->opos & 0xffffffff; - out.l = (ilast.l * (INT_MAX - t) + icur.l * t) / INT_MAX; - out.r = (ilast.r * (INT_MAX - t) + icur.r * t) / INT_MAX; - - /* output sample & increment position */ -#if 0 - *obuf++ = out; -#else - obuf->l += out.l; - obuf->r += out.r; - obuf += 1; -#endif - rate->opos += rate->opos_inc; - } - -the_end: - *isamp = ibuf - istart; - *osamp = obuf - ostart; - rate->ilast = ilast; -} +#define NAME st_rate_flow +#define OP(a, b) a = b +#include "rate_template.h" void st_rate_stop (void *opaque) { qemu_free (opaque); } + +void mixeng_clear (st_sample_t *buf, int len) +{ + memset (buf, 0, len * sizeof (st_sample_t)); +} diff --git a/audio/mixeng.h b/audio/mixeng.h index 699435ea25..9e3bac1744 100644 --- a/audio/mixeng.h +++ b/audio/mixeng.h @@ -1,8 +1,8 @@ /* * QEMU Mixing engine header - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -24,16 +24,28 @@ #ifndef QEMU_MIXENG_H #define QEMU_MIXENG_H -typedef void (t_sample) (void *dst, const void *src, int samples); -typedef void (f_sample) (void *dst, const void *src, int samples); +#ifdef FLOAT_MIXENG +typedef float real_t; +typedef struct { int mute; real_t r; real_t l; } volume_t; +typedef struct { real_t l; real_t r; } st_sample_t; +#else +typedef struct { int mute; int64_t r; int64_t l; } volume_t; typedef struct { int64_t l; int64_t r; } st_sample_t; +#endif -extern t_sample *mixeng_conv[2][2][2]; -extern f_sample *mixeng_clip[2][2][2]; +typedef void (t_sample) (st_sample_t *dst, const void *src, + int samples, volume_t *vol); +typedef void (f_sample) (void *dst, const st_sample_t *src, int samples); + +extern t_sample *mixeng_conv[2][2][2][2]; +extern f_sample *mixeng_clip[2][2][2][2]; void *st_rate_start (int inrate, int outrate); void st_rate_flow (void *opaque, st_sample_t *ibuf, st_sample_t *obuf, int *isamp, int *osamp); +void st_rate_flow_mix (void *opaque, st_sample_t *ibuf, st_sample_t *obuf, + int *isamp, int *osamp); void st_rate_stop (void *opaque); +void mixeng_clear (st_sample_t *buf, int len); #endif /* mixeng.h */ diff --git a/audio/mixeng_template.h b/audio/mixeng_template.h index f3b3f654fd..d726441e2e 100644 --- a/audio/mixeng_template.h +++ b/audio/mixeng_template.h @@ -1,8 +1,8 @@ /* * QEMU Mixing engine - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -27,85 +27,151 @@ * dec++'ified by Dscho */ -#ifdef SIGNED -#define HALFT IN_MAX -#define HALF IN_MAX -#else -#define HALFT ((IN_MAX)>>1) -#define HALF HALFT +#ifndef SIGNED +#define HALF (IN_MAX >> 1) #endif -static int64_t inline glue(conv_,IN_T) (IN_T v) -{ -#ifdef SIGNED - return (INT_MAX*(int64_t)v)/HALF; +#ifdef NOVOL +#define VOL(a, b) a #else - return (INT_MAX*((int64_t)v-HALFT))/HALF; +#ifdef FLOAT_MIXENG +#define VOL(a, b) ((a) * (b)) +#else +#define VOL(a, b) ((a) * (b)) >> 32 +#endif +#endif + +#define ET glue (ENDIAN_CONVERSION, glue (_, IN_T)) + +#ifdef FLOAT_MIXENG +static real_t inline glue (conv_, ET) (IN_T v) +{ + IN_T nv = ENDIAN_CONVERT (v); + +#ifdef RECIPROCAL +#ifdef SIGNED + return nv * (1.f / (real_t) (IN_MAX - IN_MIN)); +#else + return (nv - HALF) * (1.f / (real_t) IN_MAX); +#endif +#else /* !RECIPROCAL */ +#ifdef SIGNED + return nv / (real_t) (IN_MAX - IN_MIN); +#else + return (nv - HALF) / (real_t) IN_MAX; +#endif #endif } -static IN_T inline glue(clip_,IN_T) (int64_t v) +static IN_T inline glue (clip_, ET) (real_t v) { - if (v >= INT_MAX) + if (v >= 0.5) { return IN_MAX; - else if (v < -INT_MAX) + } + else if (v < -0.5) { return IN_MIN; + } #ifdef SIGNED - return (IN_T) (v*HALF/INT_MAX); + return ENDIAN_CONVERT ((IN_T) (v * (IN_MAX - IN_MIN))); #else - return (IN_T) (v+INT_MAX/2)*HALF/INT_MAX; + return ENDIAN_CONVERT ((IN_T) ((v * IN_MAX) + HALF)); #endif } -static void glue(glue(conv_,IN_T),_to_stereo) (void *dst, const void *src, - int samples) +#else /* !FLOAT_MIXENG */ + +static inline int64_t glue (conv_, ET) (IN_T v) { - st_sample_t *out = (st_sample_t *) dst; + IN_T nv = ENDIAN_CONVERT (v); +#ifdef SIGNED + return ((int64_t) nv) << (32 - SHIFT); +#else + return ((int64_t) nv - HALF) << (32 - SHIFT); +#endif +} + +static inline IN_T glue (clip_, ET) (int64_t v) +{ + if (v >= 0x7f000000) { + return IN_MAX; + } + else if (v < -2147483648LL) { + return IN_MIN; + } + +#ifdef SIGNED + return ENDIAN_CONVERT ((IN_T) (v >> (32 - SHIFT))); +#else + return ENDIAN_CONVERT ((IN_T) ((v >> (32 - SHIFT)) + HALF)); +#endif +} +#endif + +static void glue (glue (conv_, ET), _to_stereo) + (st_sample_t *dst, const void *src, int samples, volume_t *vol) +{ + st_sample_t *out = dst; IN_T *in = (IN_T *) src; +#ifndef NOVOL + if (vol->mute) { + mixeng_clear (dst, samples); + return; + } +#else + (void) vol; +#endif while (samples--) { - out->l = glue(conv_,IN_T) (*in++); - out->r = glue(conv_,IN_T) (*in++); + out->l = VOL (glue (conv_, ET) (*in++), vol->l); + out->r = VOL (glue (conv_, ET) (*in++), vol->r); out += 1; } } -static void glue(glue(conv_,IN_T),_to_mono) (void *dst, const void *src, - int samples) +static void glue (glue (conv_, ET), _to_mono) + (st_sample_t *dst, const void *src, int samples, volume_t *vol) { - st_sample_t *out = (st_sample_t *) dst; + st_sample_t *out = dst; IN_T *in = (IN_T *) src; +#ifndef NOVOL + if (vol->mute) { + mixeng_clear (dst, samples); + return; + } +#else + (void) vol; +#endif while (samples--) { - out->l = glue(conv_,IN_T) (in[0]); + out->l = VOL (glue (conv_, ET) (in[0]), vol->l); out->r = out->l; out += 1; in += 1; } } -static void glue(glue(clip_,IN_T),_from_stereo) (void *dst, const void *src, - int samples) +static void glue (glue (clip_, ET), _from_stereo) + (void *dst, const st_sample_t *src, int samples) { - st_sample_t *in = (st_sample_t *) src; + const st_sample_t *in = src; IN_T *out = (IN_T *) dst; while (samples--) { - *out++ = glue(clip_,IN_T) (in->l); - *out++ = glue(clip_,IN_T) (in->r); + *out++ = glue (clip_, ET) (in->l); + *out++ = glue (clip_, ET) (in->r); in += 1; } } -static void glue(glue(clip_,IN_T),_from_mono) (void *dst, const void *src, - int samples) +static void glue (glue (clip_, ET), _from_mono) + (void *dst, const st_sample_t *src, int samples) { - st_sample_t *in = (st_sample_t *) src; + const st_sample_t *in = src; IN_T *out = (IN_T *) dst; while (samples--) { - *out++ = glue(clip_,IN_T) (in->l + in->r); + *out++ = glue (clip_, ET) (in->l + in->r); in += 1; } } +#undef ET #undef HALF -#undef HALFT - +#undef VOL diff --git a/audio/noaudio.c b/audio/noaudio.c index a192885a72..e7936cc7b9 100644 --- a/audio/noaudio.c +++ b/audio/noaudio.c @@ -1,8 +1,8 @@ /* - * QEMU NULL audio output driver - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * QEMU Timer based audio emulation + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -23,77 +23,110 @@ */ #include "vl.h" -#include "audio/audio_int.h" +#define AUDIO_CAP "noaudio" +#include "audio_int.h" -typedef struct NoVoice { - HWVoice hw; +typedef struct NoVoiceOut { + HWVoiceOut hw; int64_t old_ticks; -} NoVoice; +} NoVoiceOut; -#define dolog(...) AUD_log ("noaudio", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) -#endif +typedef struct NoVoiceIn { + HWVoiceIn hw; + int64_t old_ticks; +} NoVoiceIn; -static void no_hw_run (HWVoice *hw) +static int no_run_out (HWVoiceOut *hw) { - NoVoice *no = (NoVoice *) hw; - int rpos, live, decr, samples; - st_sample_t *src; + NoVoiceOut *no = (NoVoiceOut *) hw; + int live, decr, samples; int64_t now = qemu_get_clock (vm_clock); int64_t ticks = now - no->old_ticks; - int64_t bytes = (ticks * hw->bytes_per_second) / ticks_per_sec; + int64_t bytes = (ticks * hw->info.bytes_per_second) / ticks_per_sec; - if (bytes > INT_MAX) - samples = INT_MAX >> hw->shift; - else - samples = bytes >> hw->shift; + if (bytes > INT_MAX) { + samples = INT_MAX >> hw->info.shift; + } + else { + samples = bytes >> hw->info.shift; + } - live = pcm_hw_get_live (hw); - if (live <= 0) - return; + live = audio_pcm_hw_get_live_out (&no->hw); + if (!live) { + return 0; + } no->old_ticks = now; decr = audio_MIN (live, samples); - samples = decr; - rpos = hw->rpos; - while (samples) { - int left_till_end_samples = hw->samples - rpos; - int convert_samples = audio_MIN (samples, left_till_end_samples); - - src = advance (hw->mix_buf, rpos * sizeof (st_sample_t)); - memset (src, 0, convert_samples * sizeof (st_sample_t)); - - rpos = (rpos + convert_samples) % hw->samples; - samples -= convert_samples; - } - - pcm_hw_dec_live (hw, decr); - hw->rpos = rpos; + hw->rpos = (hw->rpos + decr) % hw->samples; + return decr; } -static int no_hw_write (SWVoice *sw, void *buf, int len) +static int no_write (SWVoiceOut *sw, void *buf, int len) { - return pcm_hw_write (sw, buf, len); + return audio_pcm_sw_write (sw, buf, len); } -static int no_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +static int no_init_out (HWVoiceOut *hw, int freq, + int nchannels, audfmt_e fmt) { - hw->freq = freq; - hw->nchannels = nchannels; - hw->fmt = fmt; + audio_pcm_init_info (&hw->info, freq, nchannels, fmt, 0); hw->bufsize = 4096; return 0; } -static void no_hw_fini (HWVoice *hw) +static void no_fini_out (HWVoiceOut *hw) { (void) hw; } -static int no_hw_ctl (HWVoice *hw, int cmd, ...) +static int no_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static int no_init_in (HWVoiceIn *hw, int freq, + int nchannels, audfmt_e fmt) +{ + audio_pcm_init_info (&hw->info, freq, nchannels, fmt, 0); + hw->bufsize = 4096; + return 0; +} + +static void no_fini_in (HWVoiceIn *hw) +{ + (void) hw; +} + +static int no_run_in (HWVoiceIn *hw) +{ + NoVoiceIn *no = (NoVoiceIn *) hw; + int64_t now = qemu_get_clock (vm_clock); + int64_t ticks = now - no->old_ticks; + int64_t bytes = (ticks * hw->info.bytes_per_second) / ticks_per_sec; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int samples; + + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> hw->info.shift; + samples = audio_MIN (samples, dead); + + return samples; +} + +static int no_read (SWVoiceIn *sw, void *buf, int size) +{ + int samples = size >> sw->info.shift; + int total = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + int to_clear = audio_MIN (samples, total); + audio_pcm_info_clear_buf (&sw->info, buf, to_clear); + return to_clear; +} + +static int no_ctl_in (HWVoiceIn *hw, int cmd, ...) { (void) hw; (void) cmd; @@ -107,22 +140,33 @@ static void *no_audio_init (void) static void no_audio_fini (void *opaque) { + (void) opaque; } -struct pcm_ops no_pcm_ops = { - no_hw_init, - no_hw_fini, - no_hw_run, - no_hw_write, - no_hw_ctl +static struct audio_pcm_ops no_pcm_ops = { + no_init_out, + no_fini_out, + no_run_out, + no_write, + no_ctl_out, + + no_init_in, + no_fini_in, + no_run_in, + no_read, + no_ctl_in }; -struct audio_output_driver no_output_driver = { - "none", - no_audio_init, - no_audio_fini, - &no_pcm_ops, - 1, - 1, - sizeof (NoVoice) +struct audio_driver no_audio_driver = { + INIT_FIELD (name = ) "none", + INIT_FIELD (descr = ) "Timer based audio emulation", + INIT_FIELD (options = ) NULL, + INIT_FIELD (init = ) no_audio_init, + INIT_FIELD (fini = ) no_audio_fini, + INIT_FIELD (pcm_ops = ) &no_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) INT_MAX, + INIT_FIELD (voice_size_out = ) sizeof (NoVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (NoVoiceIn) }; diff --git a/audio/ossaudio.c b/audio/ossaudio.c index 5246ebb785..ff1a034945 100644 --- a/audio/ossaudio.c +++ b/audio/ossaudio.c @@ -1,8 +1,8 @@ /* - * QEMU OSS audio output driver - * - * Copyright (c) 2003-2004 Vassili Karpov (malc) - * + * QEMU OSS audio driver + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * * 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 @@ -25,45 +25,42 @@ #include #include #include -#include #include "vl.h" -#include "audio/audio_int.h" +#define AUDIO_CAP "oss" +#include "audio_int.h" -typedef struct OSSVoice { - HWVoice hw; +typedef struct OSSVoiceOut { + HWVoiceOut hw; void *pcm_buf; int fd; int nfrags; int fragsize; int mmapped; int old_optr; -} OSSVoice; +} OSSVoiceOut; -#define dolog(...) AUD_log ("oss", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) -#endif - -#define QC_OSS_FRAGSIZE "QEMU_OSS_FRAGSIZE" -#define QC_OSS_NFRAGS "QEMU_OSS_NFRAGS" -#define QC_OSS_MMAP "QEMU_OSS_MMAP" -#define QC_OSS_DEV "QEMU_OSS_DEV" - -#define errstr() strerror (errno) +typedef struct OSSVoiceIn { + HWVoiceIn hw; + void *pcm_buf; + int fd; + int nfrags; + int fragsize; + int old_optr; +} OSSVoiceIn; static struct { int try_mmap; int nfrags; int fragsize; - const char *dspname; + const char *devpath_out; + const char *devpath_in; } conf = { .try_mmap = 0, .nfrags = 4, .fragsize = 4096, - .dspname = "/dev/dsp" + .devpath_out = "/dev/dsp", + .devpath_in = "/dev/dsp" }; struct oss_params { @@ -74,65 +71,141 @@ struct oss_params { int fragsize; }; -static int oss_hw_write (SWVoice *sw, void *buf, int len) +static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...) { - return pcm_hw_write (sw, buf, len); + va_list ap; + + AUD_vlog (AUDIO_CAP, fmt, ap); + + va_start (ap, fmt); + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); + va_end (ap); } -static int AUD_to_ossfmt (audfmt_e fmt) +static void GCC_FMT_ATTR (3, 4) oss_logerr2 ( + int err, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Can not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +static void oss_anal_close (int *fdp) +{ + int err = close (*fdp); + if (err) { + oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp); + } + *fdp = -1; +} + +static int oss_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int aud_to_ossfmt (audfmt_e fmt) { switch (fmt) { - case AUD_FMT_S8: return AFMT_S8; - case AUD_FMT_U8: return AFMT_U8; - case AUD_FMT_S16: return AFMT_S16_LE; - case AUD_FMT_U16: return AFMT_U16_LE; + case AUD_FMT_S8: + return AFMT_S8; + + case AUD_FMT_U8: + return AFMT_U8; + + case AUD_FMT_S16: + return AFMT_S16_LE; + + case AUD_FMT_U16: + return AFMT_U16_LE; + default: - dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); - exit (EXIT_FAILURE); + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return AFMT_U8; } } -static int oss_to_audfmt (int fmt) +static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness) { - switch (fmt) { - case AFMT_S8: return AUD_FMT_S8; - case AFMT_U8: return AUD_FMT_U8; - case AFMT_S16_LE: return AUD_FMT_S16; - case AFMT_U16_LE: return AUD_FMT_U16; + switch (ossfmt) { + case AFMT_S8: + *endianness =0; + *fmt = AUD_FMT_S8; + break; + + case AFMT_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case AFMT_S16_LE: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case AFMT_U16_LE: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case AFMT_S16_BE: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case AFMT_U16_BE: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + default: - dolog ("Internal logic error: Unrecognized OSS audio format %d\n" - "Aborting\n", - fmt); - exit (EXIT_FAILURE); + dolog ("Unrecognized audio format %d\n", ossfmt); + return -1; } + + return 0; } -#ifdef DEBUG_PCM -static void oss_dump_pcm_info (struct oss_params *req, struct oss_params *obt) +#ifdef DEBUG_MISMATCHES +static void oss_dump_info (struct oss_params *req, struct oss_params *obt) { dolog ("parameter | requested value | obtained value\n"); dolog ("format | %10d | %10d\n", req->fmt, obt->fmt); - dolog ("channels | %10d | %10d\n", req->nchannels, obt->nchannels); + dolog ("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); dolog ("nfrags | %10d | %10d\n", req->nfrags, obt->nfrags); - dolog ("fragsize | %10d | %10d\n", req->fragsize, obt->fragsize); + dolog ("fragsize | %10d | %10d\n", + req->fragsize, obt->fragsize); } #endif -static int oss_open (struct oss_params *req, struct oss_params *obt, int *pfd) +static int oss_open (int in, struct oss_params *req, + struct oss_params *obt, int *pfd) { int fd; int mmmmssss; audio_buf_info abinfo; int fmt, freq, nchannels; - const char *dspname = conf.dspname; + const char *dspname = in ? conf.devpath_in : conf.devpath_out; + const char *typ = in ? "ADC" : "DAC"; - fd = open (dspname, O_WRONLY | O_NONBLOCK); + fd = open (dspname, (in ? O_RDONLY : O_WRONLY) | O_NONBLOCK); if (-1 == fd) { - dolog ("Could not initialize audio hardware. Failed to open `%s':\n" - "Reason:%s\n", - dspname, - errstr ()); + oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname); return -1; } @@ -141,52 +214,35 @@ static int oss_open (struct oss_params *req, struct oss_params *obt, int *pfd) fmt = req->fmt; if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { - dolog ("Could not initialize audio hardware\n" - "Failed to set sample size\n" - "Reason: %s\n", - errstr ()); + oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); goto err; } if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) { - dolog ("Could not initialize audio hardware\n" - "Failed to set number of channels\n" - "Reason: %s\n", - errstr ()); + oss_logerr2 (errno, typ, "Failed to set number of channels %d\n", + req->nchannels); goto err; } if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) { - dolog ("Could not initialize audio hardware\n" - "Failed to set frequency\n" - "Reason: %s\n", - errstr ()); + oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq); goto err; } if (ioctl (fd, SNDCTL_DSP_NONBLOCK)) { - dolog ("Could not initialize audio hardware\n" - "Failed to set non-blocking mode\n" - "Reason: %s\n", - errstr ()); + oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n"); goto err; } mmmmssss = (req->nfrags << 16) | lsbindex (req->fragsize); if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) { - dolog ("Could not initialize audio hardware\n" - "Failed to set buffer length (%d, %d)\n" - "Reason:%s\n", - conf.nfrags, conf.fragsize, - errstr ()); + oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n", + req->nfrags, req->fragsize); goto err; } - if (ioctl (fd, SNDCTL_DSP_GETOSPACE, &abinfo)) { - dolog ("Could not initialize audio hardware\n" - "Failed to get buffer length\n" - "Reason:%s\n", - errstr ()); + if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) { + oss_logerr2 (errno, typ, "Failed to get buffer length\n"); goto err; } @@ -202,25 +258,25 @@ static int oss_open (struct oss_params *req, struct oss_params *obt, int *pfd) (req->freq != obt->freq) || (req->fragsize != obt->fragsize) || (req->nfrags != obt->nfrags)) { -#ifdef DEBUG_PCM +#ifdef DEBUG_MISMATCHES dolog ("Audio parameters mismatch\n"); - oss_dump_pcm_info (req, obt); + oss_dump_info (req, obt); #endif } -#ifdef DEBUG_PCM - oss_dump_pcm_info (req, obt); +#ifdef DEBUG + oss_dump_info (req, obt); #endif return 0; -err: - close (fd); + err: + oss_anal_close (&fd); return -1; } -static void oss_hw_run (HWVoice *hw) +static int oss_run_out (HWVoiceOut *hw) { - OSSVoice *oss = (OSSVoice *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; int err, rpos, live, decr; int samples; uint8_t *dst; @@ -228,23 +284,25 @@ static void oss_hw_run (HWVoice *hw) struct audio_buf_info abinfo; struct count_info cntinfo; - live = pcm_hw_get_live (hw); - if (live <= 0) - return; + live = audio_pcm_hw_get_live_out (hw); + if (!live) { + return 0; + } if (oss->mmapped) { int bytes; err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); if (err < 0) { - dolog ("SNDCTL_DSP_GETOPTR failed\nReason: %s\n", errstr ()); - return; + oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); + return 0; } if (cntinfo.ptr == oss->old_optr) { - if (abs (hw->samples - live) < 64) - dolog ("overrun\n"); - return; + if (abs (hw->samples - live) < 64) { + dolog ("warning: overrun\n"); + } + return 0; } if (cntinfo.ptr > oss->old_optr) { @@ -254,18 +312,25 @@ static void oss_hw_run (HWVoice *hw) bytes = hw->bufsize + cntinfo.ptr - oss->old_optr; } - decr = audio_MIN (bytes >> hw->shift, live); + decr = audio_MIN (bytes >> hw->info.shift, live); } else { err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo); if (err < 0) { - dolog ("SNDCTL_DSP_GETOSPACE failed\nReason: %s\n", errstr ()); - return; + oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); + return 0; } - decr = audio_MIN (abinfo.bytes >> hw->shift, live); - if (decr <= 0) - return; + if (abinfo.bytes < 0 || abinfo.bytes > hw->bufsize) { + ldebug ("warning: invalid available size, size=%d bufsize=%d\n", + abinfo.bytes, hw->bufsize); + return 0; + } + + decr = audio_MIN (abinfo.bytes >> hw->info.shift, live); + if (!decr) { + return 0; + } } samples = decr; @@ -274,33 +339,41 @@ static void oss_hw_run (HWVoice *hw) int left_till_end_samples = hw->samples - rpos; int convert_samples = audio_MIN (samples, left_till_end_samples); - src = advance (hw->mix_buf, rpos * sizeof (st_sample_t)); - dst = advance (oss->pcm_buf, rpos << hw->shift); + src = hw->mix_buf + rpos; + dst = advance (oss->pcm_buf, rpos << hw->info.shift); hw->clip (dst, src, convert_samples); if (!oss->mmapped) { int written; - written = write (oss->fd, dst, convert_samples << hw->shift); + written = write (oss->fd, dst, convert_samples << hw->info.shift); /* XXX: follow errno recommendations ? */ if (written == -1) { - dolog ("Failed to write audio\nReason: %s\n", errstr ()); + oss_logerr ( + errno, + "Failed to write %d bytes of audio data from %p\n", + convert_samples << hw->info.shift, + dst + ); continue; } - if (written != convert_samples << hw->shift) { - int wsamples = written >> hw->shift; - int wbytes = wsamples << hw->shift; + if (written != convert_samples << hw->info.shift) { + int wsamples = written >> hw->info.shift; + int wbytes = wsamples << hw->info.shift; if (wbytes != written) { - dolog ("Unaligned write %d, %d\n", wbytes, written); + dolog ("warning: misaligned write %d (requested %d), " + "alignment %d\n", + wbytes, written, hw->info.align + 1); } - memset (src, 0, wbytes); - decr -= samples; + mixeng_clear (src, wsamples); + decr -= wsamples; rpos = (rpos + wsamples) % hw->samples; break; } } - memset (src, 0, convert_samples * sizeof (st_sample_t)); + + mixeng_clear (src, convert_samples); rpos = (rpos + convert_samples) % hw->samples; samples -= convert_samples; @@ -309,28 +382,24 @@ static void oss_hw_run (HWVoice *hw) oss->old_optr = cntinfo.ptr; } - pcm_hw_dec_live (hw, decr); hw->rpos = rpos; + return decr; } -static void oss_hw_fini (HWVoice *hw) +static void oss_fini_out (HWVoiceOut *hw) { int err; - OSSVoice *oss = (OSSVoice *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; - ldebug ("oss_hw_fini\n"); - err = close (oss->fd); - if (err) { - dolog ("Failed to close OSS descriptor\nReason: %s\n", errstr ()); - } - oss->fd = -1; + ldebug ("oss_fini\n"); + oss_anal_close (&oss->fd); if (oss->pcm_buf) { if (oss->mmapped) { err = munmap (oss->pcm_buf, hw->bufsize); if (err) { - dolog ("Failed to unmap OSS buffer\nReason: %s\n", - errstr ()); + oss_logerr (errno, "Failed to unmap buffer %p, size %d\n", + oss->pcm_buf, hw->bufsize); } } else { @@ -340,25 +409,38 @@ static void oss_hw_fini (HWVoice *hw) } } -static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +static int oss_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) { - OSSVoice *oss = (OSSVoice *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; - assert (!oss->fd); - req.fmt = AUD_to_ossfmt (fmt); + req.fmt = aud_to_ossfmt (fmt); req.freq = freq; req.nchannels = nchannels; req.fragsize = conf.fragsize; req.nfrags = conf.nfrags; - if (oss_open (&req, &obt, &oss->fd)) + if (oss_open (0, &req, &obt, &fd)) { return -1; + } - hw->freq = obt.freq; - hw->fmt = oss_to_audfmt (obt.fmt); - hw->nchannels = obt.nchannels; + err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + oss_anal_close (&fd); + return -1; + } + audio_pcm_init_info ( + &hw->info, + obt.freq, + obt.nchannels, + effective_fmt, + audio_need_to_swap_endian (endianness) + ); oss->nfrags = obt.nfrags; oss->fragsize = obt.fragsize; hw->bufsize = obt.nfrags * obt.fragsize; @@ -366,22 +448,23 @@ static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) oss->mmapped = 0; if (conf.try_mmap) { oss->pcm_buf = mmap (0, hw->bufsize, PROT_READ | PROT_WRITE, - MAP_SHARED, oss->fd, 0); + MAP_SHARED, fd, 0); if (oss->pcm_buf == MAP_FAILED) { - dolog ("Failed to mmap OSS device\nReason: %s\n", - errstr ()); + oss_logerr (errno, "Failed to map %d bytes of DAC\n", + hw->bufsize); } else { int err; int trig = 0; - if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - dolog ("SNDCTL_DSP_SETTRIGGER 0 failed\nReason: %s\n", - errstr ()); + if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); } else { trig = PCM_ENABLE_OUTPUT; - if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - dolog ("SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" - "Reason: %s\n", errstr ()); + if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr ( + errno, + "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" + ); } else { oss->mmapped = 1; @@ -391,8 +474,8 @@ static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) if (!oss->mmapped) { err = munmap (oss->pcm_buf, hw->bufsize); if (err) { - dolog ("Failed to unmap OSS device\nReason: %s\n", - errstr ()); + oss_logerr (errno, "Failed to unmap buffer %p size %d\n", + oss->pcm_buf, hw->bufsize); } } } @@ -401,31 +484,34 @@ static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) if (!oss->mmapped) { oss->pcm_buf = qemu_mallocz (hw->bufsize); if (!oss->pcm_buf) { - close (oss->fd); - oss->fd = -1; + oss_anal_close (&fd); return -1; } } + oss->fd = fd; return 0; } -static int oss_hw_ctl (HWVoice *hw, int cmd, ...) +static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) { int trig; - OSSVoice *oss = (OSSVoice *) hw; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; - if (!oss->mmapped) + if (!oss->mmapped) { return 0; + } switch (cmd) { case VOICE_ENABLE: ldebug ("enabling voice\n"); - pcm_hw_clear (hw, oss->pcm_buf, hw->samples); + audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples); trig = PCM_ENABLE_OUTPUT; if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - dolog ("SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" - "Reason: %s\n", errstr ()); + oss_logerr ( + errno, + "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" + ); return -1; } break; @@ -434,8 +520,7 @@ static int oss_hw_ctl (HWVoice *hw, int cmd, ...) ldebug ("disabling voice\n"); trig = 0; if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { - dolog ("SNDCTL_DSP_SETTRIGGER 0 failed\nReason: %s\n", - errstr ()); + oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); return -1; } break; @@ -443,33 +528,194 @@ static int oss_hw_ctl (HWVoice *hw, int cmd, ...) return 0; } +static int oss_init_in (HWVoiceIn *hw, + int freq, int nchannels, audfmt_e fmt) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; + + req.fmt = aud_to_ossfmt (fmt); + req.freq = freq; + req.nchannels = nchannels; + req.fragsize = conf.fragsize; + req.nfrags = conf.nfrags; + if (oss_open (1, &req, &obt, &fd)) { + return -1; + } + + err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + oss_anal_close (&fd); + return -1; + } + + audio_pcm_init_info ( + &hw->info, + obt.freq, + obt.nchannels, + effective_fmt, + audio_need_to_swap_endian (endianness) + ); + oss->nfrags = obt.nfrags; + oss->fragsize = obt.fragsize; + hw->bufsize = obt.nfrags * obt.fragsize; + oss->pcm_buf = qemu_mallocz (hw->bufsize); + if (!oss->pcm_buf) { + oss_anal_close (&fd); + return -1; + } + + oss->fd = fd; + return 0; +} + +static void oss_fini_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + oss_anal_close (&oss->fd); + + if (oss->pcm_buf) { + qemu_free (oss->pcm_buf); + oss->pcm_buf = NULL; + } +} + +static int oss_run_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + int hwshift = hw->info.shift; + int i; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + size_t read_samples = 0; + struct { + int add; + int len; + } bufs[2] = { + { hw->wpos, 0 }, + { 0, 0 } + }; + + if (!dead) { + return 0; + } + + if (hw->wpos + dead > hw->samples) { + bufs[0].len = (hw->samples - hw->wpos) << hwshift; + bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift; + } + else { + bufs[0].len = dead << hwshift; + } + + + for (i = 0; i < 2; ++i) { + ssize_t nread; + + if (bufs[i].len) { + void *p = advance (oss->pcm_buf, bufs[i].add << hwshift); + nread = read (oss->fd, p, bufs[i].len); + + if (nread > 0) { + if (nread & hw->info.align) { + dolog ("warning: misaligned read %d (requested %d), " + "alignment %d\n", nread, bufs[i].add << hwshift, + hw->info.align + 1); + } + read_samples += nread >> hwshift; + hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift, + &nominal_volume); + } + + if (bufs[i].len - nread) { + if (nread == -1) { + switch (errno) { + case EINTR: + case EAGAIN: + break; + default: + oss_logerr ( + errno, + "Failed to read %d bytes of audio (to %p)\n", + bufs[i].len, p + ); + break; + } + } + break; + } + } + } + + hw->wpos = (hw->wpos + read_samples) % hw->samples; + return read_samples; +} + +static int oss_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + static void *oss_audio_init (void) { - conf.fragsize = audio_get_conf_int (QC_OSS_FRAGSIZE, conf.fragsize); - conf.nfrags = audio_get_conf_int (QC_OSS_NFRAGS, conf.nfrags); - conf.try_mmap = audio_get_conf_int (QC_OSS_MMAP, conf.try_mmap); - conf.dspname = audio_get_conf_str (QC_OSS_DEV, conf.dspname); return &conf; } static void oss_audio_fini (void *opaque) { + (void) opaque; } -struct pcm_ops oss_pcm_ops = { - oss_hw_init, - oss_hw_fini, - oss_hw_run, - oss_hw_write, - oss_hw_ctl +static struct audio_option oss_options[] = { + {"FRAGSIZE", AUD_OPT_INT, &conf.fragsize, + "Fragment size in bytes", NULL, 0}, + {"NFRAGS", AUD_OPT_INT, &conf.nfrags, + "Number of fragments", NULL, 0}, + {"MMAP", AUD_OPT_BOOL, &conf.try_mmap, + "Try using memory mapped access", NULL, 0}, + {"DAC_DEV", AUD_OPT_STR, &conf.devpath_out, + "Path to DAC device", NULL, 0}, + {"ADC_DEV", AUD_OPT_STR, &conf.devpath_in, + "Path to ADC device", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} }; -struct audio_output_driver oss_output_driver = { - "oss", - oss_audio_init, - oss_audio_fini, - &oss_pcm_ops, - 1, - INT_MAX, - sizeof (OSSVoice) +static struct audio_pcm_ops oss_pcm_ops = { + oss_init_out, + oss_fini_out, + oss_run_out, + oss_write, + oss_ctl_out, + + oss_init_in, + oss_fini_in, + oss_run_in, + oss_read, + oss_ctl_in +}; + +struct audio_driver oss_audio_driver = { + INIT_FIELD (name = ) "oss", + INIT_FIELD (descr = ) "OSS http://www.opensound.com", + INIT_FIELD (options = ) oss_options, + INIT_FIELD (init = ) oss_audio_init, + INIT_FIELD (fini = ) oss_audio_fini, + INIT_FIELD (pcm_ops = ) &oss_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) INT_MAX, + INIT_FIELD (max_voices_in = ) INT_MAX, + INIT_FIELD (voice_size_out = ) sizeof (OSSVoiceOut), + INIT_FIELD (voice_size_in = ) sizeof (OSSVoiceIn) }; diff --git a/audio/rate_template.h b/audio/rate_template.h new file mode 100644 index 0000000000..5cc95c829a --- /dev/null +++ b/audio/rate_template.h @@ -0,0 +1,111 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 Fabrice Bellard + * + * 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. + */ + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of samples processed. + */ +void NAME (void *opaque, st_sample_t *ibuf, st_sample_t *obuf, + int *isamp, int *osamp) +{ + rate_t rate = (rate_t) opaque; + st_sample_t *istart, *iend; + st_sample_t *ostart, *oend; + st_sample_t ilast, icur, out; +#ifdef FLOAT_MIXENG + real_t t; +#else + int64_t t; +#endif + + ilast = rate->ilast; + + istart = ibuf; + iend = ibuf + *isamp; + + ostart = obuf; + oend = obuf + *osamp; + + if (rate->opos_inc == (1ULL + UINT_MAX)) { + int i, n = *isamp > *osamp ? *osamp : *isamp; + for (i = 0; i < n; i++) { + OP (obuf[i].l, ibuf[i].r); + OP (obuf[i].r, ibuf[i].r); + } + *isamp = n; + *osamp = n; + return; + } + + while (obuf < oend) { + + /* Safety catch to make sure we have input samples. */ + if (ibuf >= iend) { + break; + } + + /* read as many input samples so that ipos > opos */ + + while (rate->ipos <= (rate->opos >> 32)) { + ilast = *ibuf++; + rate->ipos++; + /* See if we finished the input buffer yet */ + if (ibuf >= iend) { + goto the_end; + } + } + + icur = *ibuf; + + /* interpolate */ +#ifdef FLOAT_MIXENG +#ifdef RECIPROCAL + t = (rate->opos & UINT_MAX) * (1.f / UINT_MAX); +#else + t = (rate->opos & UINT_MAX) / (real_t) UINT_MAX; +#endif + out.l = (ilast.l * (1.0 - t)) + icur.l * t; + out.r = (ilast.r * (1.0 - t)) + icur.r * t; +#else + t = rate->opos & 0xffffffff; + out.l = (ilast.l * ((int64_t) UINT_MAX - t) + icur.l * t) >> 32; + out.r = (ilast.r * ((int64_t) UINT_MAX - t) + icur.r * t) >> 32; +#endif + + /* output sample & increment position */ + OP (obuf->l, out.l); + OP (obuf->r, out.r); + obuf += 1; + rate->opos += rate->opos_inc; + } + +the_end: + *isamp = ibuf - istart; + *osamp = obuf - ostart; + rate->ilast = ilast; +} + +#undef NAME +#undef OP diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c index 978686a071..673e2a1125 100644 --- a/audio/sdlaudio.c +++ b/audio/sdlaudio.c @@ -1,8 +1,8 @@ /* - * QEMU SDL audio output driver - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * QEMU SDL audio driver + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -25,22 +25,15 @@ #include #include "vl.h" -#include "audio/audio_int.h" +#define AUDIO_CAP "sdl" +#include "audio_int.h" -typedef struct SDLVoice { - HWVoice hw; -} SDLVoice; - -#define dolog(...) AUD_log ("sdl", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) -#endif - -#define QC_SDL_SAMPLES "QEMU_SDL_SAMPLES" - -#define errstr() SDL_GetError () +typedef struct SDLVoiceOut { + HWVoiceOut hw; + int live; + int rpos; + int decr; +} SDLVoiceOut; static struct { int nb_samples; @@ -56,91 +49,129 @@ struct SDLAudioState { } glob_sdl; typedef struct SDLAudioState SDLAudioState; -static void sdl_hw_run (HWVoice *hw) +static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) { - (void) hw; + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); } -static int sdl_lock (SDLAudioState *s) +static int sdl_lock (SDLAudioState *s, const char *forfn) { if (SDL_LockMutex (s->mutex)) { - dolog ("SDL_LockMutex failed\nReason: %s\n", errstr ()); + sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); return -1; } return 0; } -static int sdl_unlock (SDLAudioState *s) +static int sdl_unlock (SDLAudioState *s, const char *forfn) { if (SDL_UnlockMutex (s->mutex)) { - dolog ("SDL_UnlockMutex failed\nReason: %s\n", errstr ()); + sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); return -1; } return 0; } -static int sdl_post (SDLAudioState *s) +static int sdl_post (SDLAudioState *s, const char *forfn) { if (SDL_SemPost (s->sem)) { - dolog ("SDL_SemPost failed\nReason: %s\n", errstr ()); + sdl_logerr ("SDL_SemPost for %s failed\n", forfn); return -1; } return 0; } -static int sdl_wait (SDLAudioState *s) +static int sdl_wait (SDLAudioState *s, const char *forfn) { if (SDL_SemWait (s->sem)) { - dolog ("SDL_SemWait failed\nReason: %s\n", errstr ()); + sdl_logerr ("SDL_SemWait for %s failed\n", forfn); return -1; } return 0; } -static int sdl_unlock_and_post (SDLAudioState *s) +static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) { - if (sdl_unlock (s)) + if (sdl_unlock (s, forfn)) { return -1; + } - return sdl_post (s); + return sdl_post (s, forfn); } -static int sdl_hw_write (SWVoice *sw, void *buf, int len) +static int aud_to_sdlfmt (audfmt_e fmt, int *shift) { - int ret; - SDLAudioState *s = &glob_sdl; - sdl_lock (s); - ret = pcm_hw_write (sw, buf, len); - sdl_unlock_and_post (s); - return ret; -} - -static int AUD_to_sdlfmt (audfmt_e fmt, int *shift) -{ - *shift = 0; switch (fmt) { - case AUD_FMT_S8: return AUDIO_S8; - case AUD_FMT_U8: return AUDIO_U8; - case AUD_FMT_S16: *shift = 1; return AUDIO_S16LSB; - case AUD_FMT_U16: *shift = 1; return AUDIO_U16LSB; + case AUD_FMT_S8: + *shift = 0; + return AUDIO_S8; + + case AUD_FMT_U8: + *shift = 0; + return AUDIO_U8; + + case AUD_FMT_S16: + *shift = 1; + return AUDIO_S16LSB; + + case AUD_FMT_U16: + *shift = 1; + return AUDIO_U16LSB; + default: - dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); - exit (EXIT_FAILURE); + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return AUDIO_U8; } } -static int sdl_to_audfmt (int fmt) +static int sdl_to_audfmt (int sdlfmt, audfmt_e *fmt, int *endianess) { - switch (fmt) { - case AUDIO_S8: return AUD_FMT_S8; - case AUDIO_U8: return AUD_FMT_U8; - case AUDIO_S16LSB: return AUD_FMT_S16; - case AUDIO_U16LSB: return AUD_FMT_U16; + switch (sdlfmt) { + case AUDIO_S8: + *endianess = 0; + *fmt = AUD_FMT_S8; + break; + + case AUDIO_U8: + *endianess = 0; + *fmt = AUD_FMT_U8; + break; + + case AUDIO_S16LSB: + *endianess = 0; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16LSB: + *endianess = 0; + *fmt = AUD_FMT_U16; + break; + + case AUDIO_S16MSB: + *endianess = 1; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16MSB: + *endianess = 1; + *fmt = AUD_FMT_U16; + break; + default: - dolog ("Internal logic error: Unrecognized SDL audio format %d\n" - "Aborting\n", fmt); - exit (EXIT_FAILURE); + dolog ("Unrecognized SDL audio format %d\n", sdlfmt); + return -1; } + + return 0; } static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) @@ -149,7 +180,7 @@ static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) status = SDL_OpenAudio (req, obt); if (status) { - dolog ("SDL_OpenAudio failed\nReason: %s\n", errstr ()); + sdl_logerr ("SDL_OpenAudio failed\n"); } return status; } @@ -157,9 +188,9 @@ static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) static void sdl_close (SDLAudioState *s) { if (s->initialized) { - sdl_lock (s); + sdl_lock (s, "sdl_close"); s->exit = 1; - sdl_unlock_and_post (s); + sdl_unlock_and_post (s, "sdl_close"); SDL_PauseAudio (1); SDL_CloseAudio (); s->initialized = 0; @@ -168,31 +199,40 @@ static void sdl_close (SDLAudioState *s) static void sdl_callback (void *opaque, Uint8 *buf, int len) { - SDLVoice *sdl = opaque; + SDLVoiceOut *sdl = opaque; SDLAudioState *s = &glob_sdl; - HWVoice *hw = &sdl->hw; - int samples = len >> hw->shift; + HWVoiceOut *hw = &sdl->hw; + int samples = len >> hw->info.shift; if (s->exit) { return; } while (samples) { - int to_mix, live, decr; + int to_mix, decr; /* dolog ("in callback samples=%d\n", samples); */ - sdl_wait (s); + sdl_wait (s, "sdl_callback"); if (s->exit) { return; } - sdl_lock (s); - live = pcm_hw_get_live (hw); - if (live <= 0) + if (sdl_lock (s, "sdl_callback")) { + return; + } + + if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { + dolog ("sdl->live=%d hw->samples=%d\n", + sdl->live, hw->samples); + return; + } + + if (!sdl->live) { goto again; + } /* dolog ("in callback live=%d\n", live); */ - to_mix = audio_MIN (samples, live); + to_mix = audio_MIN (samples, sdl->live); decr = to_mix; while (to_mix) { int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); @@ -200,44 +240,86 @@ static void sdl_callback (void *opaque, Uint8 *buf, int len) /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ hw->clip (buf, src, chunk); - memset (src, 0, chunk * sizeof (st_sample_t)); - hw->rpos = (hw->rpos + chunk) % hw->samples; + mixeng_clear (src, chunk); + sdl->rpos = (sdl->rpos + chunk) % hw->samples; to_mix -= chunk; - buf += chunk << hw->shift; + buf += chunk << hw->info.shift; } samples -= decr; - pcm_hw_dec_live (hw, decr); + sdl->live -= decr; + sdl->decr += decr; again: - sdl_unlock (s); + if (sdl_unlock (s, "sdl_callback")) { + return; + } } /* dolog ("done len=%d\n", len); */ } -static void sdl_hw_fini (HWVoice *hw) +static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) { - ldebug ("sdl_hw_fini %d fixed=%d\n", - glob_sdl.initialized, audio_state.fixed_format); + return audio_pcm_sw_write (sw, buf, len); +} + +static int sdl_run_out (HWVoiceOut *hw) +{ + int decr, live; + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; + SDLAudioState *s = &glob_sdl; + + if (sdl_lock (s, "sdl_callback")) { + return 0; + } + + live = audio_pcm_hw_get_live_out (hw); + + if (sdl->decr > live) { + ldebug ("sdl->decr %d live %d sdl->live %d\n", + sdl->decr, + live, + sdl->live); + } + + decr = audio_MIN (sdl->decr, live); + sdl->decr -= decr; + + sdl->live = live - decr; + hw->rpos = sdl->rpos; + + if (sdl->live > 0) { + sdl_unlock_and_post (s, "sdl_callback"); + } + else { + sdl_unlock (s, "sdl_callback"); + } + return decr; +} + +static void sdl_fini_out (HWVoiceOut *hw) +{ + (void) hw; + sdl_close (&glob_sdl); } -static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +static int sdl_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) { - SDLVoice *sdl = (SDLVoice *) hw; + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; SDLAudioState *s = &glob_sdl; SDL_AudioSpec req, obt; int shift; - - ldebug ("sdl_hw_init %d freq=%d fixed=%d\n", - s->initialized, freq, audio_state.fixed_format); + int endianess; + int err; + audfmt_e effective_fmt; if (nchannels != 2) { - dolog ("Bogus channel count %d\n", nchannels); + dolog ("Can not init DAC. Bogus channel count %d\n", nchannels); return -1; } req.freq = freq; - req.format = AUD_to_sdlfmt (fmt, &shift); + req.format = aud_to_sdlfmt (fmt, &shift); req.channels = nchannels; req.samples = conf.nb_samples; shift <<= nchannels == 2; @@ -245,12 +327,23 @@ static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) req.callback = sdl_callback; req.userdata = sdl; - if (sdl_open (&req, &obt)) + if (sdl_open (&req, &obt)) { return -1; + } - hw->freq = obt.freq; - hw->fmt = sdl_to_audfmt (obt.format); - hw->nchannels = obt.channels; + err = sdl_to_audfmt (obt.format, &effective_fmt, &endianess); + if (err) { + sdl_close (s); + return -1; + } + + audio_pcm_init_info ( + &hw->info, + obt.freq, + obt.channels, + effective_fmt, + audio_need_to_swap_endian (endianess) + ); hw->bufsize = obt.samples << shift; s->initialized = 1; @@ -259,7 +352,7 @@ static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) return 0; } -static int sdl_hw_ctl (HWVoice *hw, int cmd, ...) +static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) { (void) hw; @@ -278,24 +371,22 @@ static int sdl_hw_ctl (HWVoice *hw, int cmd, ...) static void *sdl_audio_init (void) { SDLAudioState *s = &glob_sdl; - conf.nb_samples = audio_get_conf_int (QC_SDL_SAMPLES, conf.nb_samples); if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { - dolog ("SDL failed to initialize audio subsystem\nReason: %s\n", - errstr ()); + sdl_logerr ("SDL failed to initialize audio subsystem\n"); return NULL; } s->mutex = SDL_CreateMutex (); if (!s->mutex) { - dolog ("Failed to create SDL mutex\nReason: %s\n", errstr ()); + sdl_logerr ("Failed to create SDL mutex\n"); SDL_QuitSubSystem (SDL_INIT_AUDIO); return NULL; } s->sem = SDL_CreateSemaphore (0); if (!s->sem) { - dolog ("Failed to create SDL semaphore\nReason: %s\n", errstr ()); + sdl_logerr ("Failed to create SDL semaphore\n"); SDL_DestroyMutex (s->mutex); SDL_QuitSubSystem (SDL_INIT_AUDIO); return NULL; @@ -313,20 +404,36 @@ static void sdl_audio_fini (void *opaque) SDL_QuitSubSystem (SDL_INIT_AUDIO); } -struct pcm_ops sdl_pcm_ops = { - sdl_hw_init, - sdl_hw_fini, - sdl_hw_run, - sdl_hw_write, - sdl_hw_ctl +static struct audio_option sdl_options[] = { + {"SAMPLES", AUD_OPT_INT, &conf.nb_samples, + "Size of SDL buffer in samples", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} }; -struct audio_output_driver sdl_output_driver = { - "sdl", - sdl_audio_init, - sdl_audio_fini, - &sdl_pcm_ops, - 1, - 1, - sizeof (SDLVoice) +static struct audio_pcm_ops sdl_pcm_ops = { + sdl_init_out, + sdl_fini_out, + sdl_run_out, + sdl_write_out, + sdl_ctl_out, + + NULL, + NULL, + NULL, + NULL, + NULL +}; + +struct audio_driver sdl_audio_driver = { + INIT_FIELD (name = ) "sdl", + INIT_FIELD (descr = ) "SDL http://www.libsdl.org", + INIT_FIELD (options = ) sdl_options, + INIT_FIELD (init = ) sdl_audio_init, + INIT_FIELD (fini = ) sdl_audio_fini, + INIT_FIELD (pcm_ops = ) &sdl_pcm_ops, + INIT_FIELD (can_be_default = ) 1, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 0, + INIT_FIELD (voice_size_out = ) sizeof (SDLVoiceOut), + INIT_FIELD (voice_size_in = ) 0 }; diff --git a/audio/sys-queue.h b/audio/sys-queue.h new file mode 100644 index 0000000000..5b6e2a0a23 --- /dev/null +++ b/audio/sys-queue.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)queue.h 8.3 (Berkeley) 12/13/93 + */ + +#ifndef _SYS_QUEUE_H +#define _SYS_QUEUE_H 1 + +/* + * This file defines three types of data structures: lists, tail queues, + * and circular queues. + * + * A list is headed by a single forward pointer (or an array of forward + * pointers for a hash table header). The elements are doubly linked + * so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list after + * an existing element or at the head of the list. A list may only be + * traversed in the forward direction. + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list after + * an existing element, at the head of the list, or at the end of the + * list. A tail queue may only be traversed in the forward direction. + * + * A circle queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or after + * an existing element, at the head of the list, or at the end of the list. + * A circle queue may be traversed in either direction, but has a more + * complex end of list detection. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +/* + * List definitions. + */ +#define LIST_HEAD(name, type) \ +struct name { \ + struct type *lh_first; /* first element */ \ +} + +#define LIST_ENTRY(type) \ +struct { \ + struct type *le_next; /* next element */ \ + struct type **le_prev; /* address of previous next element */ \ +} + +/* + * List functions. + */ +#define LIST_INIT(head) { \ + (head)->lh_first = NULL; \ +} + +#define LIST_INSERT_AFTER(listelm, elm, field) { \ + if (((elm)->field.le_next = (listelm)->field.le_next) != NULL) \ + (listelm)->field.le_next->field.le_prev = \ + &(elm)->field.le_next; \ + (listelm)->field.le_next = (elm); \ + (elm)->field.le_prev = &(listelm)->field.le_next; \ +} + +#define LIST_INSERT_HEAD(head, elm, field) { \ + if (((elm)->field.le_next = (head)->lh_first) != NULL) \ + (head)->lh_first->field.le_prev = &(elm)->field.le_next;\ + (head)->lh_first = (elm); \ + (elm)->field.le_prev = &(head)->lh_first; \ +} + +#define LIST_REMOVE(elm, field) { \ + if ((elm)->field.le_next != NULL) \ + (elm)->field.le_next->field.le_prev = \ + (elm)->field.le_prev; \ + *(elm)->field.le_prev = (elm)->field.le_next; \ +} + +/* + * Tail queue definitions. + */ +#define TAILQ_HEAD(name, type) \ +struct name { \ + struct type *tqh_first; /* first element */ \ + struct type **tqh_last; /* addr of last next element */ \ +} + +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} + +/* + * Tail queue functions. + */ +#define TAILQ_INIT(head) { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} + +#define TAILQ_INSERT_HEAD(head, elm, field) { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} + +#define TAILQ_INSERT_TAIL(head, elm, field) { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} + +#define TAILQ_INSERT_AFTER(head, listelm, elm, field) { \ + if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\ + (elm)->field.tqe_next->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (listelm)->field.tqe_next = (elm); \ + (elm)->field.tqe_prev = &(listelm)->field.tqe_next; \ +} + +#define TAILQ_REMOVE(head, elm, field) { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} + +/* + * Circular queue definitions. + */ +#define CIRCLEQ_HEAD(name, type) \ +struct name { \ + struct type *cqh_first; /* first element */ \ + struct type *cqh_last; /* last element */ \ +} + +#define CIRCLEQ_ENTRY(type) \ +struct { \ + struct type *cqe_next; /* next element */ \ + struct type *cqe_prev; /* previous element */ \ +} + +/* + * Circular queue functions. + */ +#define CIRCLEQ_INIT(head) { \ + (head)->cqh_first = (void *)(head); \ + (head)->cqh_last = (void *)(head); \ +} + +#define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) { \ + (elm)->field.cqe_next = (listelm)->field.cqe_next; \ + (elm)->field.cqe_prev = (listelm); \ + if ((listelm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (listelm)->field.cqe_next->field.cqe_prev = (elm); \ + (listelm)->field.cqe_next = (elm); \ +} + +#define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) { \ + (elm)->field.cqe_next = (listelm); \ + (elm)->field.cqe_prev = (listelm)->field.cqe_prev; \ + if ((listelm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (listelm)->field.cqe_prev->field.cqe_next = (elm); \ + (listelm)->field.cqe_prev = (elm); \ +} + +#define CIRCLEQ_INSERT_HEAD(head, elm, field) { \ + (elm)->field.cqe_next = (head)->cqh_first; \ + (elm)->field.cqe_prev = (void *)(head); \ + if ((head)->cqh_last == (void *)(head)) \ + (head)->cqh_last = (elm); \ + else \ + (head)->cqh_first->field.cqe_prev = (elm); \ + (head)->cqh_first = (elm); \ +} + +#define CIRCLEQ_INSERT_TAIL(head, elm, field) { \ + (elm)->field.cqe_next = (void *)(head); \ + (elm)->field.cqe_prev = (head)->cqh_last; \ + if ((head)->cqh_first == (void *)(head)) \ + (head)->cqh_first = (elm); \ + else \ + (head)->cqh_last->field.cqe_next = (elm); \ + (head)->cqh_last = (elm); \ +} + +#define CIRCLEQ_REMOVE(head, elm, field) { \ + if ((elm)->field.cqe_next == (void *)(head)) \ + (head)->cqh_last = (elm)->field.cqe_prev; \ + else \ + (elm)->field.cqe_next->field.cqe_prev = \ + (elm)->field.cqe_prev; \ + if ((elm)->field.cqe_prev == (void *)(head)) \ + (head)->cqh_first = (elm)->field.cqe_next; \ + else \ + (elm)->field.cqe_prev->field.cqe_next = \ + (elm)->field.cqe_next; \ +} +#endif /* sys/queue.h */ diff --git a/audio/wavaudio.c b/audio/wavaudio.c index 5680161c72..e9bd87872f 100644 --- a/audio/wavaudio.c +++ b/audio/wavaudio.c @@ -1,8 +1,8 @@ /* - * QEMU WAV audio output driver - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * QEMU WAV audio driver + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -23,22 +23,16 @@ */ #include "vl.h" -#include "audio/audio_int.h" +#define AUDIO_CAP "wav" +#include "audio_int.h" -typedef struct WAVVoice { - HWVoice hw; +typedef struct WAVVoiceOut { + HWVoiceOut hw; QEMUFile *f; int64_t old_ticks; void *pcm_buf; int total_samples; -} WAVVoice; - -#define dolog(...) AUD_log ("wav", __VA_ARGS__) -#ifdef DEBUG -#define ldebug(...) dolog (__VA_ARGS__) -#else -#define ldebug(...) -#endif +} WAVVoiceOut; static struct { const char *wav_path; @@ -46,24 +40,27 @@ static struct { .wav_path = "qemu.wav" }; -static void wav_hw_run (HWVoice *hw) +static int wav_run_out (HWVoiceOut *hw) { - WAVVoice *wav = (WAVVoice *) hw; + WAVVoiceOut *wav = (WAVVoiceOut *) hw; int rpos, live, decr, samples; uint8_t *dst; st_sample_t *src; int64_t now = qemu_get_clock (vm_clock); int64_t ticks = now - wav->old_ticks; - int64_t bytes = (ticks * hw->bytes_per_second) / ticks_per_sec; + int64_t bytes = (ticks * hw->info.bytes_per_second) / ticks_per_sec; - if (bytes > INT_MAX) - samples = INT_MAX >> hw->shift; - else - samples = bytes >> hw->shift; + if (bytes > INT_MAX) { + samples = INT_MAX >> hw->info.shift; + } + else { + samples = bytes >> hw->info.shift; + } - live = pcm_hw_get_live (hw); - if (live <= 0) - return; + live = audio_pcm_hw_get_live_out (hw); + if (!live) { + return 0; + } wav->old_ticks = now; decr = audio_MIN (live, samples); @@ -73,25 +70,25 @@ static void wav_hw_run (HWVoice *hw) int left_till_end_samples = hw->samples - rpos; int convert_samples = audio_MIN (samples, left_till_end_samples); - src = advance (hw->mix_buf, rpos * sizeof (st_sample_t)); - dst = advance (wav->pcm_buf, rpos << hw->shift); + src = hw->mix_buf + rpos; + dst = advance (wav->pcm_buf, rpos << hw->info.shift); hw->clip (dst, src, convert_samples); - qemu_put_buffer (wav->f, dst, convert_samples << hw->shift); - memset (src, 0, convert_samples * sizeof (st_sample_t)); + qemu_put_buffer (wav->f, dst, convert_samples << hw->info.shift); + mixeng_clear (src, convert_samples); rpos = (rpos + convert_samples) % hw->samples; samples -= convert_samples; wav->total_samples += convert_samples; } - pcm_hw_dec_live (hw, decr); hw->rpos = rpos; + return decr; } -static int wav_hw_write (SWVoice *sw, void *buf, int len) +static int wav_write_out (SWVoiceOut *sw, void *buf, int len) { - return pcm_hw_write (sw, buf, len); + return audio_pcm_sw_write (sw, buf, len); } /* VICE code: Store number as little endian. */ @@ -104,10 +101,10 @@ static void le_store (uint8_t *buf, uint32_t val, int len) } } -static int wav_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) +static int wav_init_out (HWVoiceOut *hw, int freq, int nchannels, audfmt_e fmt) { - WAVVoice *wav = (WAVVoice *) hw; - int bits16 = 0, stereo = audio_state.fixed_channels == 2; + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int bits16; uint8_t hdr[] = { 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, @@ -115,34 +112,50 @@ static int wav_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 }; - switch (audio_state.fixed_fmt) { + freq = audio_state.fixed_freq_out; + fmt = audio_state.fixed_fmt_out; + nchannels = audio_state.fixed_channels_out; + + switch (fmt) { case AUD_FMT_S8: case AUD_FMT_U8: + bits16 = 0; break; case AUD_FMT_S16: case AUD_FMT_U16: bits16 = 1; break; + + default: + dolog ("Internal logic error bad format %d\n", fmt); + return -1; } hdr[34] = bits16 ? 0x10 : 0x08; - hw->freq = 44100; - hw->nchannels = stereo ? 2 : 1; - hw->fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; + audio_pcm_init_info ( + &hw->info, + freq, + nchannels, + bits16 ? AUD_FMT_S16 : AUD_FMT_U8, + audio_need_to_swap_endian (0) + ); hw->bufsize = 4096; wav->pcm_buf = qemu_mallocz (hw->bufsize); - if (!wav->pcm_buf) + if (!wav->pcm_buf) { + dolog ("Can not initialize WAV buffer of %d bytes\n", + hw->bufsize); return -1; + } - le_store (hdr + 22, hw->nchannels, 2); - le_store (hdr + 24, hw->freq, 4); - le_store (hdr + 28, hw->freq << (bits16 + stereo), 4); - le_store (hdr + 32, 1 << (bits16 + stereo), 2); + le_store (hdr + 22, hw->info.nchannels, 2); + le_store (hdr + 24, hw->info.freq, 4); + le_store (hdr + 28, hw->info.freq << (bits16 + (nchannels == 2)), 4); + le_store (hdr + 32, 1 << (bits16 + (nchannels == 2)), 2); wav->f = fopen (conf.wav_path, "wb"); if (!wav->f) { - dolog ("failed to open wave file `%s'\nReason: %s\n", + dolog ("Failed to open wave file `%s'\nReason: %s\n", conf.wav_path, strerror (errno)); qemu_free (wav->pcm_buf); wav->pcm_buf = NULL; @@ -153,17 +166,18 @@ static int wav_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) return 0; } -static void wav_hw_fini (HWVoice *hw) +static void wav_fini_out (HWVoiceOut *hw) { - WAVVoice *wav = (WAVVoice *) hw; - int stereo = hw->nchannels == 2; + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int stereo = hw->info.nchannels == 2; uint8_t rlen[4]; uint8_t dlen[4]; uint32_t rifflen = (wav->total_samples << stereo) + 36; uint32_t datalen = wav->total_samples << stereo; - if (!wav->f || !hw->active) + if (!wav->f || !hw->active) { return; + } le_store (rlen, rifflen, 4); le_store (dlen, datalen, 4); @@ -181,7 +195,7 @@ static void wav_hw_fini (HWVoice *hw) wav->pcm_buf = NULL; } -static int wav_hw_ctl (HWVoice *hw, int cmd, ...) +static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) { (void) hw; (void) cmd; @@ -195,23 +209,41 @@ static void *wav_audio_init (void) static void wav_audio_fini (void *opaque) { + (void) opaque; ldebug ("wav_fini"); } -struct pcm_ops wav_pcm_ops = { - wav_hw_init, - wav_hw_fini, - wav_hw_run, - wav_hw_write, - wav_hw_ctl +struct audio_option wav_options[] = { + {"PATH", AUD_OPT_STR, &conf.wav_path, + "Path to wave file", NULL, 0}, + {NULL, 0, NULL, NULL, NULL, 0} }; -struct audio_output_driver wav_output_driver = { - "wav", - wav_audio_init, - wav_audio_fini, - &wav_pcm_ops, - 1, - 1, - sizeof (WAVVoice) +struct audio_pcm_ops wav_pcm_ops = { + wav_init_out, + wav_fini_out, + wav_run_out, + wav_write_out, + wav_ctl_out, + + NULL, + NULL, + NULL, + NULL, + NULL +}; + +struct audio_driver wav_audio_driver = { + INIT_FIELD (name = ) "wav", + INIT_FIELD (descr = ) + "WAV renderer http://wikipedia.org/wiki/WAV", + INIT_FIELD (options = ) wav_options, + INIT_FIELD (init = ) wav_audio_init, + INIT_FIELD (fini = ) wav_audio_fini, + INIT_FIELD (pcm_ops = ) &wav_pcm_ops, + INIT_FIELD (can_be_default = ) 0, + INIT_FIELD (max_voices_out = ) 1, + INIT_FIELD (max_voices_in = ) 0, + INIT_FIELD (voice_size_out = ) sizeof (WAVVoiceOut), + INIT_FIELD (voice_size_in = ) 0 }; diff --git a/configure b/configure index 19a0ec900f..98ef82b6e1 100755 --- a/configure +++ b/configure @@ -77,6 +77,9 @@ gdbstub="yes" slirp="yes" adlib="no" oss="no" +dsound="no" +coreaudio="no" +alsa="no" fmod="no" fmod_lib="" fmod_inc="" @@ -115,7 +118,7 @@ Darwin) bsd="yes" darwin="yes" ;; -*) +*) oss="yes" linux="yes" if [ "$cpu" = "i386" -o "$cpu" = "x86_64" ] ; then @@ -131,7 +134,7 @@ if [ "$bsd" = "yes" ] ; then fi # find source path -# XXX: we assume an absolute path is given when launching configure, +# XXX: we assume an absolute path is given when launching configure, # except in './configure' case. source_path=${0%configure} source_path=${source_path%/} @@ -171,6 +174,12 @@ for opt do ;; --disable-sdl) sdl="no" ;; + --enable-coreaudio) coreaudio="yes" + ;; + --enable-alsa) alsa="yes" + ;; + --enable-dsound) dsound="yes" + ;; --enable-fmod) fmod="yes" ;; --fmod-lib=*) fmod_lib=${opt#--fmod-lib=} @@ -178,17 +187,17 @@ for opt do --fmod-inc=*) fmod_inc=${opt#--fmod-inc=} ;; --enable-mingw32) mingw32="yes" ; cross_prefix="i386-mingw32-" - ;; + ;; --disable-slirp) slirp="no" - ;; + ;; --enable-adlib) adlib="yes" - ;; + ;; --disable-kqemu) kqemu="no" - ;; + ;; --kernel-path=*) kernel_path=${opt#--kernel-path=} - ;; - --enable-cocoa) cocoa="yes" ; sdl="no" - ;; + ;; + --enable-cocoa) cocoa="yes" ; coreaudio="yes" ; sdl="no" + ;; --disable-gfx-check) check_gfx="no" ;; esac @@ -231,8 +240,8 @@ if test -z "$cross_prefix" ; then cat > $TMPC << EOF #include int main(int argc, char ** argv){ - volatile uint32_t i=0x01234567; - return (*((uint8_t*)(&i))) == 0x67; + volatile uint32_t i=0x01234567; + return (*((uint8_t*)(&i))) == 0x67; } EOF @@ -346,7 +355,10 @@ echo " --make=MAKE use specified make [$make]" echo " --static enable static build [$static]" echo " --enable-mingw32 enable Win32 cross compilation with mingw32" echo " --enable-adlib enable Adlib emulation" -echo " --enable-fmod enable FMOD audio output driver" +echo " --enable-coreaudio enable Coreaudio audio driver" +echo " --enable-alsa enable ALSA audio driver" +echo " --enable-fmod enable FMOD audio driver" +echo " --enabled-dsound enable DirectSound audio driver" echo " --fmod-lib path to FMOD library" echo " --fmod-inc path to FMOD includes" echo "" @@ -375,20 +387,20 @@ fi # kqemu support if test $kqemu = "yes" ; then # test if the source code is installed - if test '!' -f "kqemu/Makefile" ; then + if test '!' -f "kqemu/Makefile" ; then kqemu="no" fi fi - + # Linux specific kqemu configuration if test $kqemu = "yes" -a $linux = "yes" ; then # find the kernel path if test -z "$kernel_path" ; then kernel_version=`uname -r` kernel_path="/lib/modules/$kernel_version/build" -if test '!' -d "$kernel_path/include" ; then +if test '!' -d "$kernel_path/include" ; then kernel_path="/usr/src/linux" - if test '!' -d "$kernel_path/include" ; then + if test '!' -d "$kernel_path/include" ; then echo "Could not find kernel includes in /lib/modules or /usr/src/linux - cannot build the kqemu module" kqemu="no" fi @@ -401,7 +413,7 @@ if test $kqemu = "yes" ; then if test '!' -f "$kernel_path/Makefile" ; then echo "No Makefile file present in $kernel_path - kqemu cannot be built" kqemu="no" -fi +fi # find build system (2.6 or legacy) kbuild26="yes" @@ -439,8 +451,18 @@ if test "$sdl" != "no" ; then fi echo "mingw32 support $mingw32" echo "Adlib support $adlib" +echo "CoreAudio support $coreaudio" +echo "ALSA support $alsa" +echo "DSound support $dsound" echo -n "FMOD support $fmod" -if test $fmod = "yes"; then +if test "$fmod" = "yes"; then + if test -z $fmod_lib || test -z $fmod_inc; then + echo + echo "Error: You must specify path to FMOD library and headers" + echo "Example: --fmod-inc=/path/include/fmod --fmod-lib=/path/lib/libfmod-3.74.so" + echo + exit 1 + fi echo -n " (lib='$fmod_lib' include='$fmod_inc')" fi echo "" @@ -568,6 +590,18 @@ if test "$oss" = "yes" ; then echo "CONFIG_OSS=yes" >> $config_mak echo "#define CONFIG_OSS 1" >> $config_h fi +if test "$coreaudio" = "yes" ; then + echo "CONFIG_COREAUDIO=yes" >> $config_mak + echo "#define CONFIG_COREAUDIO 1" >> $config_h +fi +if test "$alsa" = "yes" ; then + echo "CONFIG_ALSA=yes" >> $config_mak + echo "#define CONFIG_ALSA 1" >> $config_h +fi +if test "$dsound" = "yes" ; then + echo "CONFIG_DSOUND=yes" >> $config_mak + echo "#define CONFIG_DSOUND 1" >> $config_h +fi if test "$fmod" = "yes" ; then echo "CONFIG_FMOD=yes" >> $config_mak echo "CONFIG_FMOD_LIB=$fmod_lib" >> $config_mak @@ -600,7 +634,7 @@ if [ "$bsd" = "yes" ] ; then echo "#define _BSD 1" >> $config_h fi -for target in $target_list; do +for target in $target_list; do target_dir="$target" config_mak=$target_dir/config.mak @@ -623,7 +657,7 @@ if expr $target : '.*-user' > /dev/null ; then fi if test "$target_user_only" = "no" -a "$check_gfx" = "yes" \ - -a "$sdl" = "no" -a "$cocoa" = "no" ; then + -a "$sdl" = "no" -a "$cocoa" = "no" ; then echo "ERROR: QEMU requires SDL or Cocoa for graphical output" echo "To build QEMU with graphical output configure with --disable-gfx-check" echo "Note that this will disable all output from the virtual graphics card." diff --git a/hw/adlib.c b/hw/adlib.c index 939a7ed036..70de4ffab1 100644 --- a/hw/adlib.c +++ b/hw/adlib.c @@ -1,8 +1,8 @@ /* - * QEMU Adlib emulation - * - * Copyright (c) 2004 Vassili Karpov (malc) - * + * QEMU Proxy for OPL2/3 emulation by MAME team + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * * 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 @@ -21,8 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include "vl.h" +#define ADLIB_KILL_TIMERS 1 + #define dolog(...) AUD_log ("adlib", __VA_ARGS__) #ifdef DEBUG #define ldebug(...) dolog (__VA_ARGS__) @@ -30,23 +33,15 @@ #define ldebug(...) #endif -#ifdef USE_YMF262 -#define HAS_YMF262 1 +#ifdef HAS_YMF262 #include "ymf262.h" -void YMF262UpdateOneQEMU(int which, INT16 *dst, int length); +void YMF262UpdateOneQEMU (int which, INT16 *dst, int length); #define SHIFT 2 #else #include "fmopl.h" #define SHIFT 1 #endif -#ifdef _WIN32 -#include -#define small_delay() Sleep (1) -#else -#define small_delay() usleep (1) -#endif - #define IO_READ_PROTO(name) \ uint32_t name (void *opaque, uint32_t nport) #define IO_WRITE_PROTO(name) \ @@ -58,35 +53,70 @@ static struct { } conf = {0x220, 44100}; typedef struct { + int ticking[2]; int enabled; int active; - int cparam; - int64_t ticks; int bufpos; +#ifdef DEBUG + int64_t exp[2]; +#endif int16_t *mixbuf; - double interval; - QEMUTimer *ts, *opl_ts; - SWVoice *voice; - int left, pos, samples, bytes_per_second, old_free; - int refcount; -#ifndef USE_YMF262 + uint64_t dexp[2]; + SWVoiceOut *voice; + int left, pos, samples; + QEMUAudioTimeStamp ats; +#ifndef HAS_YMF262 FM_OPL *opl; #endif } AdlibState; static AdlibState adlib; +static void adlib_stop_opl_timer (AdlibState *s, size_t n) +{ +#ifdef HAS_YMF262 + YMF262TimerOver (0, n); +#else + OPLTimerOver (s->opl, n); +#endif + s->ticking[n] = 0; +} + +static void adlib_kill_timers (AdlibState *s) +{ + size_t i; + + for (i = 0; i < 2; ++i) { + if (s->ticking[i]) { + uint64_t delta; + + delta = AUD_time_stamp_get_elapsed_usec_out (s->voice, &s->ats); + ldebug ( + "delta = %f dexp = %f expired => %d\n", + delta / 1000000.0, + s->dexp[i] / 1000000.0, + delta >= s->dexp[i] + ); + if (ADLIB_KILL_TIMERS || delta >= s->dexp[i]) { + adlib_stop_opl_timer (s, i); + AUD_init_time_stamp_out (s->voice, &s->ats); + } + } + } +} + static IO_WRITE_PROTO(adlib_write) { AdlibState *s = opaque; int a = nport & 3; int status; - s->ticks = qemu_get_clock (vm_clock); s->active = 1; - AUD_enable (s->voice, 1); + AUD_set_active_out (s->voice, 1); -#ifdef USE_YMF262 + adlib_kill_timers (s); + +#ifdef HAS_YMF262 status = YMF262Write (0, a, val); #else status = OPLWrite (s->opl, a, val); @@ -99,8 +129,9 @@ static IO_READ_PROTO(adlib_read) uint8_t data; int a = nport & 3; -#ifdef USE_YMF262 - (void) s; + adlib_kill_timers (s); + +#ifdef HAS_YMF262 data = YMF262Read (0, a); #else data = OPLRead (s->opl, a); @@ -108,119 +139,115 @@ static IO_READ_PROTO(adlib_read) return data; } -static void OPL_timer (void *opaque) -{ - AdlibState *s = opaque; -#ifdef USE_YMF262 - YMF262TimerOver (s->cparam >> 1, s->cparam & 1); -#else - OPLTimerOver (s->opl, s->cparam); -#endif - qemu_mod_timer (s->opl_ts, qemu_get_clock (vm_clock) + s->interval); -} - -static void YMF262TimerHandler (int c, double interval_Sec) +static void timer_handler (int c, double interval_Sec) { AdlibState *s = &adlib; + unsigned n = c & 1; +#ifdef DEBUG + double interval; +#endif + if (interval_Sec == 0.0) { - qemu_del_timer (s->opl_ts); + s->ticking[n] = 0; return; } - s->cparam = c; - s->interval = ticks_per_sec * interval_Sec; - qemu_mod_timer (s->opl_ts, qemu_get_clock (vm_clock) + s->interval); - small_delay (); + + s->ticking[n] = 1; +#ifdef DEBUG + interval = ticks_per_sec * interval_Sec; + exp = qemu_get_clock (vm_clock) + interval; + s->exp[n] = exp; +#endif + + s->dexp[n] = interval_Sec * 1000000.0; + AUD_init_time_stamp_out (s->voice, &s->ats); } static int write_audio (AdlibState *s, int samples) { int net = 0; - int ss = samples; + int pos = s->pos; + while (samples) { - int nbytes = samples << SHIFT; - int wbytes = AUD_write (s->voice, - s->mixbuf + (s->pos << (SHIFT - 1)), - nbytes); - int wsampl = wbytes >> SHIFT; - samples -= wsampl; - s->pos = (s->pos + wsampl) % s->samples; - net += wsampl; - if (!wbytes) + int nbytes, wbytes, wsampl; + + nbytes = samples << SHIFT; + wbytes = AUD_write ( + s->voice, + s->mixbuf + (pos << (SHIFT - 1)), + nbytes + ); + + if (wbytes) { + wsampl = wbytes >> SHIFT; + + samples -= wsampl; + pos = (pos + wsampl) % s->samples; + + net += wsampl; + } + else { break; + } } - if (net > ss) { - dolog ("WARNING: net > ss\n"); - } + return net; } -static void timer (void *opaque) +static void adlib_callback (void *opaque, int free) { AdlibState *s = opaque; - int elapsed, samples, net = 0; + int samples, net = 0, to_play, written; - if (s->refcount) - dolog ("refcount=%d\n", s->refcount); - - s->refcount += 1; - if (!(s->active && s->enabled)) - goto reset; - - AUD_run (); - - while (s->left) { - int written = write_audio (s, s->left); - net += written; - if (!written) - goto reset2; - s->left -= written; + samples = free >> SHIFT; + if (!(s->active && s->enabled) || !samples) { + return; } - s->pos = 0; - elapsed = AUD_calc_elapsed (s->voice); - if (!elapsed) - goto reset2; + to_play = audio_MIN (s->left, samples); + while (to_play) { + written = write_audio (s, to_play); - /* elapsed = AUD_get_free (s->voice); */ - samples = elapsed >> SHIFT; - if (!samples) - goto reset2; + if (written) { + s->left -= written; + samples -= written; + to_play -= written; + s->pos = (s->pos + written) % s->samples; + } + else { + return; + } + } samples = audio_MIN (samples, s->samples - s->pos); - if (s->left) - dolog ("left=%d samples=%d elapsed=%d free=%d\n", - s->left, samples, elapsed, AUD_get_free (s->voice)); + if (!samples) { + return; + } - if (!samples) - goto reset2; - -#ifdef USE_YMF262 +#ifdef HAS_YMF262 YMF262UpdateOneQEMU (0, s->mixbuf + s->pos * 2, samples); #else YM3812UpdateOne (s->opl, s->mixbuf + s->pos, samples); #endif while (samples) { - int written = write_audio (s, samples); - net += written; - if (!written) - break; - samples -= written; - } - if (!samples) - s->pos = 0; - s->left = samples; + written = write_audio (s, samples); -reset2: - AUD_adjust (s->voice, net << SHIFT); -reset: - qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + ticks_per_sec / 1024); - s->refcount -= 1; + if (written) { + net += written; + samples -= written; + s->pos = (s->pos + written) % s->samples; + } + else { + s->left = samples; + return; + } + } } static void Adlib_fini (AdlibState *s) { -#ifdef USE_YMF262 +#ifdef HAS_YMF262 YMF262Shutdown (); #else if (s->opl) { @@ -229,15 +256,9 @@ static void Adlib_fini (AdlibState *s) } #endif - if (s->opl_ts) - qemu_free_timer (s->opl_ts); - - if (s->ts) - qemu_free_timer (s->ts); - -#define maybe_free(p) if (p) qemu_free (p) - maybe_free (s->mixbuf); -#undef maybe_free + if (s->mixbuf) { + qemu_free (s->mixbuf); + } s->active = 0; s->enabled = 0; @@ -247,15 +268,13 @@ void Adlib_init (void) { AdlibState *s = &adlib; - memset (s, 0, sizeof (*s)); - -#ifdef USE_YMF262 +#ifdef HAS_YMF262 if (YMF262Init (1, 14318180, conf.freq)) { dolog ("YMF262Init %d failed\n", conf.freq); return; } else { - YMF262SetTimerHandler (0, YMF262TimerHandler, 0); + YMF262SetTimerHandler (0, timer_handler, 0); s->enabled = 1; } #else @@ -265,33 +284,26 @@ void Adlib_init (void) return; } else { - OPLSetTimerHandler (s->opl, YMF262TimerHandler, 0); + OPLSetTimerHandler (s->opl, timer_handler, 0); s->enabled = 1; } #endif - s->opl_ts = qemu_new_timer (vm_clock, OPL_timer, s); - if (!s->opl_ts) { - dolog ("Can not get timer for adlib emulation\n"); - Adlib_fini (s); - return; - } - - s->ts = qemu_new_timer (vm_clock, timer, s); - if (!s->opl_ts) { - dolog ("Can not get timer for adlib emulation\n"); - Adlib_fini (s); - return; - } - - s->voice = AUD_open (s->voice, "adlib", conf.freq, SHIFT, AUD_FMT_S16); + s->voice = AUD_open_out ( + s->voice, + "adlib", + s, + adlib_callback, + conf.freq, + SHIFT, + AUD_FMT_S16 + ); if (!s->voice) { Adlib_fini (s); return; } - s->bytes_per_second = conf.freq << SHIFT; - s->samples = AUD_get_buffer_size (s->voice) >> SHIFT; + s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT; s->mixbuf = qemu_mallocz (s->samples << SHIFT); if (!s->mixbuf) { @@ -300,6 +312,7 @@ void Adlib_init (void) Adlib_fini (s); return; } + register_ioport_read (0x388, 4, 1, adlib_read, s); register_ioport_write (0x388, 4, 1, adlib_write, s); @@ -308,6 +321,4 @@ void Adlib_init (void) register_ioport_read (conf.port + 8, 2, 1, adlib_read, s); register_ioport_write (conf.port + 8, 2, 1, adlib_write, s); - - qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + 1); } diff --git a/hw/es1370.c b/hw/es1370.c new file mode 100644 index 0000000000..0191f5fdf8 --- /dev/null +++ b/hw/es1370.c @@ -0,0 +1,1007 @@ +/* + * QEMU ES1370 emulation + * + * Copyright (c) 2005 Vassili Karpov (malc) + * + * 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 DEBUG_ES1370 */ +/* #define VERBOSE_ES1370 */ +#define SILENT_ES1370 + +#include "vl.h" + +/* Missing stuff: + SCTRL_P[12](END|ST)INC + SCTRL_P1SCTRLD + SCTRL_P2DACSEN + CTRL_DAC_SYNC + MIDI + non looped mode + surely more +*/ + +/* + Following macros and samplerate array were copied verbatim from + Linux kernel 2.4.30: drivers/sound/es1370.c + + Copyright (C) 1998-2001, 2003 Thomas Sailer (t.sailer@alumni.ethz.ch) +*/ + +/* Start blatant GPL violation */ + +#define ES1370_REG_CONTROL 0x00 +#define ES1370_REG_STATUS 0x04 +#define ES1370_REG_UART_DATA 0x08 +#define ES1370_REG_UART_STATUS 0x09 +#define ES1370_REG_UART_CONTROL 0x09 +#define ES1370_REG_UART_TEST 0x0a +#define ES1370_REG_MEMPAGE 0x0c +#define ES1370_REG_CODEC 0x10 +#define ES1370_REG_SERIAL_CONTROL 0x20 +#define ES1370_REG_DAC1_SCOUNT 0x24 +#define ES1370_REG_DAC2_SCOUNT 0x28 +#define ES1370_REG_ADC_SCOUNT 0x2c + +#define ES1370_REG_DAC1_FRAMEADR 0xc30 +#define ES1370_REG_DAC1_FRAMECNT 0xc34 +#define ES1370_REG_DAC2_FRAMEADR 0xc38 +#define ES1370_REG_DAC2_FRAMECNT 0xc3c +#define ES1370_REG_ADC_FRAMEADR 0xd30 +#define ES1370_REG_ADC_FRAMECNT 0xd34 +#define ES1370_REG_PHANTOM_FRAMEADR 0xd38 +#define ES1370_REG_PHANTOM_FRAMECNT 0xd3c + +static const unsigned dac1_samplerate[] = { 5512, 11025, 22050, 44100 }; + +#define DAC2_SRTODIV(x) (((1411200+(x)/2)/(x))-2) +#define DAC2_DIVTOSR(x) (1411200/((x)+2)) + +#define CTRL_ADC_STOP 0x80000000 /* 1 = ADC stopped */ +#define CTRL_XCTL1 0x40000000 /* electret mic bias */ +#define CTRL_OPEN 0x20000000 /* no function, can be read and written */ +#define CTRL_PCLKDIV 0x1fff0000 /* ADC/DAC2 clock divider */ +#define CTRL_SH_PCLKDIV 16 +#define CTRL_MSFMTSEL 0x00008000 /* MPEG serial data fmt: 0 = Sony, 1 = I2S */ +#define CTRL_M_SBB 0x00004000 /* DAC2 clock: 0 = PCLKDIV, 1 = MPEG */ +#define CTRL_WTSRSEL 0x00003000 /* DAC1 clock freq: 0=5512, 1=11025, 2=22050, 3=44100 */ +#define CTRL_SH_WTSRSEL 12 +#define CTRL_DAC_SYNC 0x00000800 /* 1 = DAC2 runs off DAC1 clock */ +#define CTRL_CCB_INTRM 0x00000400 /* 1 = CCB "voice" ints enabled */ +#define CTRL_M_CB 0x00000200 /* recording source: 0 = ADC, 1 = MPEG */ +#define CTRL_XCTL0 0x00000100 /* 0 = Line in, 1 = Line out */ +#define CTRL_BREQ 0x00000080 /* 1 = test mode (internal mem test) */ +#define CTRL_DAC1_EN 0x00000040 /* enable DAC1 */ +#define CTRL_DAC2_EN 0x00000020 /* enable DAC2 */ +#define CTRL_ADC_EN 0x00000010 /* enable ADC */ +#define CTRL_UART_EN 0x00000008 /* enable MIDI uart */ +#define CTRL_JYSTK_EN 0x00000004 /* enable Joystick port (presumably at address 0x200) */ +#define CTRL_CDC_EN 0x00000002 /* enable serial (CODEC) interface */ +#define CTRL_SERR_DIS 0x00000001 /* 1 = disable PCI SERR signal */ + +#define STAT_INTR 0x80000000 /* wired or of all interrupt bits */ +#define STAT_CSTAT 0x00000400 /* 1 = codec busy or codec write in progress */ +#define STAT_CBUSY 0x00000200 /* 1 = codec busy */ +#define STAT_CWRIP 0x00000100 /* 1 = codec write in progress */ +#define STAT_VC 0x00000060 /* CCB int source, 0=DAC1, 1=DAC2, 2=ADC, 3=undef */ +#define STAT_SH_VC 5 +#define STAT_MCCB 0x00000010 /* CCB int pending */ +#define STAT_UART 0x00000008 /* UART int pending */ +#define STAT_DAC1 0x00000004 /* DAC1 int pending */ +#define STAT_DAC2 0x00000002 /* DAC2 int pending */ +#define STAT_ADC 0x00000001 /* ADC int pending */ + +#define USTAT_RXINT 0x80 /* UART rx int pending */ +#define USTAT_TXINT 0x04 /* UART tx int pending */ +#define USTAT_TXRDY 0x02 /* UART tx ready */ +#define USTAT_RXRDY 0x01 /* UART rx ready */ + +#define UCTRL_RXINTEN 0x80 /* 1 = enable RX ints */ +#define UCTRL_TXINTEN 0x60 /* TX int enable field mask */ +#define UCTRL_ENA_TXINT 0x20 /* enable TX int */ +#define UCTRL_CNTRL 0x03 /* control field */ +#define UCTRL_CNTRL_SWR 0x03 /* software reset command */ + +#define SCTRL_P2ENDINC 0x00380000 /* */ +#define SCTRL_SH_P2ENDINC 19 +#define SCTRL_P2STINC 0x00070000 /* */ +#define SCTRL_SH_P2STINC 16 +#define SCTRL_R1LOOPSEL 0x00008000 /* 0 = loop mode */ +#define SCTRL_P2LOOPSEL 0x00004000 /* 0 = loop mode */ +#define SCTRL_P1LOOPSEL 0x00002000 /* 0 = loop mode */ +#define SCTRL_P2PAUSE 0x00001000 /* 1 = pause mode */ +#define SCTRL_P1PAUSE 0x00000800 /* 1 = pause mode */ +#define SCTRL_R1INTEN 0x00000400 /* enable interrupt */ +#define SCTRL_P2INTEN 0x00000200 /* enable interrupt */ +#define SCTRL_P1INTEN 0x00000100 /* enable interrupt */ +#define SCTRL_P1SCTRLD 0x00000080 /* reload sample count register for DAC1 */ +#define SCTRL_P2DACSEN 0x00000040 /* 1 = DAC2 play back last sample when disabled */ +#define SCTRL_R1SEB 0x00000020 /* 1 = 16bit */ +#define SCTRL_R1SMB 0x00000010 /* 1 = stereo */ +#define SCTRL_R1FMT 0x00000030 /* format mask */ +#define SCTRL_SH_R1FMT 4 +#define SCTRL_P2SEB 0x00000008 /* 1 = 16bit */ +#define SCTRL_P2SMB 0x00000004 /* 1 = stereo */ +#define SCTRL_P2FMT 0x0000000c /* format mask */ +#define SCTRL_SH_P2FMT 2 +#define SCTRL_P1SEB 0x00000002 /* 1 = 16bit */ +#define SCTRL_P1SMB 0x00000001 /* 1 = stereo */ +#define SCTRL_P1FMT 0x00000003 /* format mask */ +#define SCTRL_SH_P1FMT 0 + +/* End blatant GPL violation */ + +#define NB_CHANNELS 3 +#define DAC1_CHANNEL 0 +#define DAC2_CHANNEL 1 +#define ADC_CHANNEL 2 + +#define IO_READ_PROTO(n) \ +static uint32_t n (void *opaque, uint32_t addr) +#define IO_WRITE_PROTO(n) \ +static void n (void *opaque, uint32_t addr, uint32_t val) + +static void es1370_dac1_callback (void *opaque, int free); +static void es1370_dac2_callback (void *opaque, int free); +static void es1370_adc_callback (void *opaque, int avail); + +#ifdef DEBUG_ES1370 + +#define ldebug(...) AUD_log ("es1370", __VA_ARGS__) + +static void print_ctl (uint32_t val) +{ + char buf[1024]; + + buf[0] = '\0'; +#define a(n) if (val & CTRL_##n) strcat (buf, " "#n) + a (ADC_STOP); + a (XCTL1); + a (OPEN); + a (MSFMTSEL); + a (M_SBB); + a (DAC_SYNC); + a (CCB_INTRM); + a (M_CB); + a (XCTL0); + a (BREQ); + a (DAC1_EN); + a (DAC2_EN); + a (ADC_EN); + a (UART_EN); + a (JYSTK_EN); + a (CDC_EN); + a (SERR_DIS); +#undef a + AUD_log ("es1370", "ctl - PCLKDIV %d(DAC2 freq %d), freq %d,%s\n", + (val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV, + DAC2_DIVTOSR ((val & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV), + dac1_samplerate[(val & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL], + buf); +} + +static void print_sctl (uint32_t val) +{ + static const char *fmt_names[] = {"8M", "8S", "16M", "16S"}; + char buf[1024]; + + buf[0] = '\0'; + +#define a(n) if (val & SCTRL_##n) strcat (buf, " "#n) +#define b(n) if (!(val & SCTRL_##n)) strcat (buf, " "#n) + b (R1LOOPSEL); + b (P2LOOPSEL); + b (P1LOOPSEL); + a (P2PAUSE); + a (P1PAUSE); + a (R1INTEN); + a (P2INTEN); + a (P1INTEN); + a (P1SCTRLD); + a (P2DACSEN); + if (buf[0]) { + strcat (buf, "\n "); + } + else { + buf[0] = ' '; + buf[1] = '\0'; + } +#undef b +#undef a + AUD_log ("es1370", + "%s" + "p2_end_inc %d, p2_st_inc %d, r1_fmt %s, p2_fmt %s, p1_fmt %s\n", + buf, + (val & SCTRL_P2ENDINC) >> SCTRL_SH_P2ENDINC, + (val & SCTRL_P2STINC) >> SCTRL_SH_P2STINC, + fmt_names [(val >> SCTRL_SH_R1FMT) & 3], + fmt_names [(val >> SCTRL_SH_P2FMT) & 3], + fmt_names [(val >> SCTRL_SH_P1FMT) & 3] + ); +} +#else +#define ldebug(...) +#define print_ctl(...) +#define print_sctl(...) +#endif + +#ifdef VERBOSE_ES1370 +#define dolog(...) AUD_log ("es1370", __VA_ARGS__) +#else +#define dolog(...) +#endif + +#ifndef SILENT_ES1370 +#define lwarn(...) AUD_log ("es1370: warning:", __VA_ARGS__) +#else +#define lwarn(...) +#endif + +struct chan { + uint32_t shift; + uint32_t leftover; + uint32_t scount; + uint32_t frame_addr; + uint32_t frame_cnt; +}; + +typedef struct ES1370State { + PCIDevice *pci_dev; + + struct chan chan[NB_CHANNELS]; + SWVoiceOut *dac_voice[2]; + SWVoiceIn *adc_voice; + + uint32_t ctl; + uint32_t status; + uint32_t mempage; + uint32_t codec; + uint32_t sctl; +} ES1370State; + +typedef struct PCIES1370State { + PCIDevice dev; + ES1370State es1370; +} PCIES1370State; + +struct chan_bits { + uint32_t ctl_en; + uint32_t stat_int; + uint32_t sctl_pause; + uint32_t sctl_inten; + uint32_t sctl_fmt; + uint32_t sctl_sh_fmt; + uint32_t sctl_loopsel; + void (*calc_freq) (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq); +}; + +static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq); +static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, + uint32_t *new_freq); + +static const struct chan_bits es1370_chan_bits[] = { + {CTRL_DAC1_EN, STAT_DAC1, SCTRL_P1PAUSE, SCTRL_P1INTEN, + SCTRL_P1FMT, SCTRL_SH_P1FMT, SCTRL_P1LOOPSEL, + es1370_dac1_calc_freq}, + + {CTRL_DAC2_EN, STAT_DAC2, SCTRL_P2PAUSE, SCTRL_P2INTEN, + SCTRL_P2FMT, SCTRL_SH_P2FMT, SCTRL_P2LOOPSEL, + es1370_dac2_and_adc_calc_freq}, + + {CTRL_ADC_EN, STAT_ADC, 0, SCTRL_R1INTEN, + SCTRL_R1FMT, SCTRL_SH_R1FMT, SCTRL_R1LOOPSEL, + es1370_dac2_and_adc_calc_freq} +}; + +static void es1370_update_status (ES1370State *s, uint32_t new_status) +{ + uint32_t level = new_status & (STAT_DAC1 | STAT_DAC2 | STAT_ADC); + + if (level) { + s->status = new_status | STAT_INTR; + } + else { + s->status = new_status & ~STAT_INTR; + } + pci_set_irq (s->pci_dev, 0, !!level); +} + +static void es1370_reset (ES1370State *s) +{ + size_t i; + + s->ctl = 1; + s->status = 0x60; + s->mempage = 0; + s->codec = 0; + s->sctl = 0; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + d->scount = 0; + d->leftover = 0; + if (i == ADC_CHANNEL) { + AUD_close_in (s->adc_voice); + s->adc_voice = NULL; + } + else { + AUD_close_out (s->dac_voice[i]); + s->dac_voice[i] = NULL; + } + } + pci_set_irq (s->pci_dev, 0, 0); +} + +static void es1370_maybe_lower_irq (ES1370State *s, uint32_t sctl) +{ + uint32_t new_status = s->status; + + if (!(sctl & SCTRL_P1INTEN) && (s->sctl & SCTRL_P1INTEN)) { + new_status &= ~STAT_DAC1; + } + + if (!(sctl & SCTRL_P2INTEN) && (s->sctl & SCTRL_P2INTEN)) { + new_status &= ~STAT_DAC2; + } + + if (!(sctl & SCTRL_R1INTEN) && (s->sctl & SCTRL_R1INTEN)) { + new_status &= ~STAT_ADC; + } + + if (new_status != s->status) { + es1370_update_status (s, new_status); + } +} + +static void es1370_dac1_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, uint32_t *new_freq) + +{ + *old_freq = dac1_samplerate[(s->ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL]; + *new_freq = dac1_samplerate[(ctl & CTRL_WTSRSEL) >> CTRL_SH_WTSRSEL]; +} + +static void es1370_dac2_and_adc_calc_freq (ES1370State *s, uint32_t ctl, + uint32_t *old_freq, + uint32_t *new_freq) + +{ + uint32_t old_pclkdiv, new_pclkdiv; + + new_pclkdiv = (ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV; + old_pclkdiv = (s->ctl & CTRL_PCLKDIV) >> CTRL_SH_PCLKDIV; + *new_freq = DAC2_DIVTOSR (new_pclkdiv); + *old_freq = DAC2_DIVTOSR (old_pclkdiv); +} + +static void es1370_update_voices (ES1370State *s, uint32_t ctl, uint32_t sctl) +{ + size_t i; + uint32_t old_freq, new_freq, old_fmt, new_fmt; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + const struct chan_bits *b = &es1370_chan_bits[i]; + + new_fmt = (sctl & b->sctl_fmt) >> b->sctl_sh_fmt; + old_fmt = (s->sctl & b->sctl_fmt) >> b->sctl_sh_fmt; + + b->calc_freq (s, ctl, &old_freq, &new_freq); + + if ((old_fmt != new_fmt) || (old_freq != new_freq)) { + d->shift = (new_fmt & 1) + (new_fmt >> 1); + ldebug ("channel %d, freq = %d, nchannels %d, fmt %d, shift %d\n", + i, + new_freq, + 1 << (new_fmt & 1), + (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8, + d->shift); + if (new_freq) { + if (i == ADC_CHANNEL) { + s->adc_voice = + AUD_open_in ( + s->adc_voice, + "es1370.adc", + s, + es1370_adc_callback, + new_freq, + 1 << (new_fmt & 1), + (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8 + ); + } + else { + s->dac_voice[i] = + AUD_open_out ( + s->dac_voice[i], + i ? "es1370.dac2" : "es1370.dac1", + s, + i ? es1370_dac2_callback : es1370_dac1_callback, + new_freq, + 1 << (new_fmt & 1), + (new_fmt & 2) ? AUD_FMT_S16 : AUD_FMT_U8 + ); + } + } + } + + if (((ctl ^ s->ctl) & b->ctl_en) + || ((sctl ^ s->sctl) & b->sctl_pause)) { + int on = (ctl & b->ctl_en) && !(sctl & b->sctl_pause); + + if (i == ADC_CHANNEL) { + AUD_set_active_in (s->adc_voice, on); + } + else { + AUD_set_active_out (s->dac_voice[i], on); + } + } + } + + s->ctl = ctl; + s->sctl = sctl; +} + +static inline uint32_t es1370_fixup (ES1370State *s, uint32_t addr) +{ + addr &= 0xff; + if (addr >= 0x30 && addr <= 0x3f) + addr |= s->mempage << 8; + return addr; +} + +IO_WRITE_PROTO (es1370_writeb) +{ + ES1370State *s = opaque; + addr = es1370_fixup (s, addr); + uint32_t shift, mask; + + switch (addr) { + case ES1370_REG_CONTROL: + case ES1370_REG_CONTROL + 1: + case ES1370_REG_CONTROL + 2: + case ES1370_REG_CONTROL + 3: + shift = (addr - ES1370_REG_CONTROL) << 3; + mask = 0xff << shift; + val = (s->ctl & ~mask) | ((val & 0xff) << shift); + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + case ES1370_REG_MEMPAGE: + s->mempage = val; + break; + case ES1370_REG_SERIAL_CONTROL: + case ES1370_REG_SERIAL_CONTROL + 1: + case ES1370_REG_SERIAL_CONTROL + 2: + case ES1370_REG_SERIAL_CONTROL + 3: + shift = (addr - ES1370_REG_SERIAL_CONTROL) << 3; + mask = 0xff << shift; + val = (s->sctl & ~mask) | ((val & 0xff) << shift); + es1370_maybe_lower_irq (s, val); + es1370_update_voices (s, s->ctl, val); + print_sctl (val); + break; + default: + lwarn ("writeb %#x <- %#x\n", addr, val); + break; + } +} + +IO_WRITE_PROTO (es1370_writew) +{ + ES1370State *s = opaque; + addr = es1370_fixup (s, addr); + uint32_t shift, mask; + struct chan *d = &s->chan[0]; + + switch (addr) { + case ES1370_REG_CODEC: + dolog ("ignored codec write address %#x, data %#x\n", + (val >> 8) & 0xff, val & 0xff); + s->codec = val; + break; + + case ES1370_REG_CONTROL: + case ES1370_REG_CONTROL + 2: + shift = (addr != ES1370_REG_CONTROL) << 4; + mask = 0xffff << shift; + val = (s->ctl & ~mask) | ((val & 0xffff) << shift); + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + d->scount = (d->scount & ~0xffff) | (val & 0xffff); + break; + + default: + lwarn ("writew %#x <- %#x\n", addr, val); + break; + } +} + +IO_WRITE_PROTO (es1370_writel) +{ + ES1370State *s = opaque; + struct chan *d = &s->chan[0]; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_CONTROL: + es1370_update_voices (s, val, s->sctl); + print_ctl (val); + break; + + case ES1370_REG_MEMPAGE: + s->mempage = val & 0xf; + break; + + case ES1370_REG_SERIAL_CONTROL: + es1370_maybe_lower_irq (s, val); + es1370_update_voices (s, s->ctl, val); + print_sctl (val); + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + d->scount = (val & 0xffff) | (d->scount & ~0xffff); + ldebug ("chan %d CURR_SAMP_CT %d, SAMP_CT %d\n", + d - &s->chan[0], val >> 16, (val & 0xffff)); + break; + + case ES1370_REG_ADC_FRAMEADR: + d++; + case ES1370_REG_DAC2_FRAMEADR: + d++; + case ES1370_REG_DAC1_FRAMEADR: + d->frame_addr = val; + ldebug ("chan %d frame address %#x\n", d - &s->chan[0], val); + break; + + case ES1370_REG_ADC_FRAMECNT: + d++; + case ES1370_REG_DAC2_FRAMECNT: + d++; + case ES1370_REG_DAC1_FRAMECNT: + d->frame_cnt = val; + d->leftover = 0; + ldebug ("chan %d frame count %d, buffer size %d\n", + d - &s->chan[0], val >> 16, val & 0xffff); + break; + + default: + lwarn ("writel %#x <- %#x\n", addr, val); + break; + } +} + +IO_READ_PROTO (es1370_readb) +{ + ES1370State *s = opaque; + uint32_t val; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case 0x1b: /* Legacy */ + lwarn ("Attempt to read from legacy register\n"); + val = 5; + break; + case ES1370_REG_MEMPAGE: + val = s->mempage; + break; + case ES1370_REG_CONTROL + 0: + case ES1370_REG_CONTROL + 1: + case ES1370_REG_CONTROL + 2: + case ES1370_REG_CONTROL + 3: + val = s->ctl >> ((addr - ES1370_REG_CONTROL) << 3); + break; + case ES1370_REG_STATUS + 0: + case ES1370_REG_STATUS + 1: + case ES1370_REG_STATUS + 2: + case ES1370_REG_STATUS + 3: + val = s->status >> ((addr - ES1370_REG_STATUS) << 3); + break; + default: + val = ~0; + lwarn ("readb %#x -> %#x\n", addr, val); + break; + } + return val; +} + +IO_READ_PROTO (es1370_readw) +{ + ES1370State *s = opaque; + struct chan *d = &s->chan[0]; + uint32_t val; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_ADC_SCOUNT + 2: + d++; + case ES1370_REG_DAC2_SCOUNT + 2: + d++; + case ES1370_REG_DAC1_SCOUNT + 2: + val = d->scount >> 16; + break; + + default: + val = ~0; + lwarn ("readw %#x -> %#x\n", addr, val); + break; + } + + return val; +} + +IO_READ_PROTO (es1370_readl) +{ + ES1370State *s = opaque; + uint32_t val; + struct chan *d = &s->chan[0]; + + addr = es1370_fixup (s, addr); + + switch (addr) { + case ES1370_REG_CONTROL: + val = s->ctl; + break; + case ES1370_REG_STATUS: + val = s->status; + break; + case ES1370_REG_MEMPAGE: + val = s->mempage; + break; + case ES1370_REG_CODEC: + val = s->codec; + break; + case ES1370_REG_SERIAL_CONTROL: + val = s->sctl; + break; + + case ES1370_REG_ADC_SCOUNT: + d++; + case ES1370_REG_DAC2_SCOUNT: + d++; + case ES1370_REG_DAC1_SCOUNT: + val = d->scount; +#ifdef DEBUG_ES1370 + { + uint32_t curr_count = d->scount >> 16; + uint32_t count = d->scount & 0xffff; + + curr_count <<= d->shift; + count <<= d->shift; + dolog ("read scount curr %d, total %d\n", curr_count, count); + } +#endif + break; + + case ES1370_REG_ADC_FRAMECNT: + d++; + case ES1370_REG_DAC2_FRAMECNT: + d++; + case ES1370_REG_DAC1_FRAMECNT: + val = d->frame_cnt; +#ifdef DEBUG_ES1370 + { + uint32_t size = ((d->frame_cnt & 0xffff) + 1) << 2; + uint32_t curr = ((d->frame_cnt >> 16) + 1) << 2; + if (curr > size) + dolog ("read framecnt curr %d, size %d %d\n", curr, size, + curr > size); + } +#endif + break; + + case ES1370_REG_ADC_FRAMEADR: + d++; + case ES1370_REG_DAC2_FRAMEADR: + d++; + case ES1370_REG_DAC1_FRAMEADR: + val = d->frame_addr; + break; + + default: + val = ~0U; + lwarn ("readl %#x -> %#x\n", addr, val); + break; + } + return val; +} + + +static void es1370_transfer_audio (ES1370State *s, struct chan *d, int loop_sel, + int max, int *irq) +{ + uint8_t tmpbuf[4096]; + uint32_t addr = d->frame_addr; + int sc = d->scount & 0xffff; + int csc = d->scount >> 16; + int csc_bytes = (csc + 1) << d->shift; + int cnt = d->frame_cnt >> 16; + int size = d->frame_cnt & 0xffff; + int left = ((size - cnt + 1) << 2) + d->leftover; + int transfered = 0; + int temp = audio_MIN (max, audio_MIN (left, csc_bytes)); + int index = d - &s->chan[0]; + + addr += (cnt << 2) + d->leftover; + + if (index == ADC_CHANNEL) { + while (temp) { + int acquired, to_copy; + + to_copy = audio_MIN (temp, sizeof (tmpbuf)); + acquired = AUD_read (s->adc_voice, tmpbuf, to_copy); + if (!acquired) + break; + + cpu_physical_memory_write (addr, tmpbuf, acquired); + + temp -= acquired; + addr += acquired; + transfered += acquired; + } + } + else { + SWVoiceOut *voice = s->dac_voice[index]; + + while (temp) { + int copied, to_copy; + + to_copy = audio_MIN (temp, sizeof (tmpbuf)); + cpu_physical_memory_read (addr, tmpbuf, to_copy); + copied = AUD_write (voice, tmpbuf, to_copy); + if (!copied) + break; + temp -= copied; + addr += copied; + transfered += copied; + } + } + + if (csc_bytes == transfered) { + *irq = 1; + d->scount = sc | (sc << 16); + ldebug ("sc = %d, rate = %f\n", + (sc + 1) << d->shift, + (sc + 1) / (double) 44100); + } + else { + *irq = 0; + d->scount = sc | (((csc_bytes - transfered - 1) >> d->shift) << 16); + } + + cnt += (transfered + d->leftover) >> 2; + + if (s->sctl & loop_sel) { + /* Bah, how stupid is that having a 0 represent true value? + i just spent few hours on this shit */ + lwarn ("whoops non looping mode\n"); + } + else { + d->frame_cnt = size; + + if (cnt <= d->frame_cnt) + d->frame_cnt |= cnt << 16; + } + + d->leftover = (transfered + d->leftover) & 3; +} + +static void es1370_run_channel (ES1370State *s, size_t chan, int free_or_avail) +{ + uint32_t new_status = s->status; + int max_bytes, irq; + struct chan *d = &s->chan[chan]; + const struct chan_bits *b = &es1370_chan_bits[chan]; + + if (!(s->ctl & b->ctl_en) || (s->sctl & b->sctl_pause)) { + return; + } + + max_bytes = free_or_avail; + max_bytes &= ~((1 << d->shift) - 1); + if (!max_bytes) { + return; + } + + es1370_transfer_audio (s, d, b->sctl_loopsel, max_bytes, &irq); + + if (irq) { + if (s->sctl & b->sctl_inten) { + new_status |= b->stat_int; + } + } + + if (new_status != s->status) { + es1370_update_status (s, new_status); + } +} + +static void es1370_dac1_callback (void *opaque, int free) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, DAC1_CHANNEL, free); +} + +static void es1370_dac2_callback (void *opaque, int free) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, DAC2_CHANNEL, free); +} + +static void es1370_adc_callback (void *opaque, int avail) +{ + ES1370State *s = opaque; + + es1370_run_channel (s, ADC_CHANNEL, avail); +} + +static void es1370_map (PCIDevice *pci_dev, int region_num, + uint32_t addr, uint32_t size, int type) +{ + PCIES1370State *d = (PCIES1370State *) pci_dev; + ES1370State *s = &d->es1370; + + register_ioport_write (addr, 0x40 * 4, 1, es1370_writeb, s); + register_ioport_write (addr, 0x40 * 2, 2, es1370_writew, s); + register_ioport_write (addr, 0x40, 4, es1370_writel, s); + + register_ioport_read (addr, 0x40 * 4, 1, es1370_readb, s); + register_ioport_read (addr, 0x40 * 2, 2, es1370_readw, s); + register_ioport_read (addr, 0x40, 4, es1370_readl, s); +} + +static void es1370_save (QEMUFile *f, void *opaque) +{ + ES1370State *s = opaque; + size_t i; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + qemu_put_be32s (f, &d->shift); + qemu_put_be32s (f, &d->leftover); + qemu_put_be32s (f, &d->scount); + qemu_put_be32s (f, &d->frame_addr); + qemu_put_be32s (f, &d->frame_cnt); + } + qemu_put_be32s (f, &s->ctl); + qemu_put_be32s (f, &s->status); + qemu_put_be32s (f, &s->mempage); + qemu_put_be32s (f, &s->codec); + qemu_put_be32s (f, &s->sctl); +} + +static int es1370_load (QEMUFile *f, void *opaque, int version_id) +{ + uint32_t ctl, sctl; + ES1370State *s = opaque; + size_t i; + + if (version_id != 1) + return -EINVAL; + + for (i = 0; i < NB_CHANNELS; ++i) { + struct chan *d = &s->chan[i]; + qemu_get_be32s (f, &d->shift); + qemu_get_be32s (f, &d->leftover); + qemu_get_be32s (f, &d->scount); + qemu_get_be32s (f, &d->frame_addr); + qemu_get_be32s (f, &d->frame_cnt); + if (i == ADC_CHANNEL) { + if (s->adc_voice) { + AUD_close_in (s->adc_voice); + s->adc_voice = NULL; + } + } + else { + if (s->dac_voice[i]) { + AUD_close_out (s->dac_voice[i]); + s->dac_voice[i] = NULL; + } + } + } + + qemu_get_be32s (f, &ctl); + qemu_get_be32s (f, &s->status); + qemu_get_be32s (f, &s->mempage); + qemu_get_be32s (f, &s->codec); + qemu_get_be32s (f, &sctl); + + s->ctl = 0; + s->sctl = 0; + es1370_update_voices (s, ctl, sctl); + return 0; +} + +static void es1370_on_reset (void *opaque) +{ + ES1370State *s = opaque; + es1370_reset (s); +} + +int es1370_init (PCIBus *bus) +{ + PCIES1370State *d; + ES1370State *s; + uint8_t *c; + + d = (PCIES1370State *) pci_register_device (bus, "ES1370", + sizeof (PCIES1370State), + -1, NULL, NULL); + + if (!d) { + fprintf (stderr, "Failed to register PCI device for ES1370\n"); + return -1; + } + + c = d->dev.config; + c[0x00] = 0x74; + c[0x01] = 0x12; + c[0x02] = 0x00; + c[0x03] = 0x50; + c[0x07] = 2 << 1; + c[0x0a] = 0x01; + c[0x0b] = 0x04; + +#if 1 + c[0x2c] = 0x42; + c[0x2d] = 0x49; + c[0x2e] = 0x4c; + c[0x2f] = 0x4c; +#else + c[0x2c] = 0x74; + c[0x2d] = 0x12; + c[0x2e] = 0x71; + c[0x2f] = 0x13; + c[0x34] = 0xdc; + c[0x3c] = 10; + c[0xdc] = 0x00; +#endif + + c[0x3d] = 1; + c[0x3e] = 0x0c; + c[0x3f] = 0x80; + + s = &d->es1370; + s->pci_dev = &d->dev; + + pci_register_io_region (&d->dev, 0, 256, PCI_ADDRESS_SPACE_IO, es1370_map); + register_savevm ("es1370", 0, 1, es1370_save, es1370_load, s); + qemu_register_reset (es1370_on_reset, s); + es1370_reset (s); + return 0; +} diff --git a/hw/pc.c b/hw/pc.c index f0ae1b964f..ad3d5e032b 100644 --- a/hw/pc.c +++ b/hw/pc.c @@ -602,18 +602,18 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device, if (audio_enabled) { AUD_init(); -#ifdef USE_SB16 if (sb16_enabled) SB16_init (); -#endif #ifdef CONFIG_ADLIB if (adlib_enabled) Adlib_init (); #endif -#ifdef USE_GUS +#ifdef CONFIG_GUS if (gus_enabled) GUS_init (); #endif + if (pci_enabled && es1370_enabled) + es1370_init (pci_bus); } floppy_controller = fdctrl_init(6, 2, 0, 0x3f0, fd_table); diff --git a/hw/sb16.c b/hw/sb16.c index bca5795eac..e79d192789 100644 --- a/hw/sb16.c +++ b/hw/sb16.c @@ -1,8 +1,8 @@ /* * QEMU Soundblaster 16 emulation - * - * Copyright (c) 2003-2004 Vassili Karpov (malc) - * + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + * * 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 @@ -99,9 +99,10 @@ typedef struct SB16State { int dma_running; int bytes_per_second; int align; - SWVoice *voice; + int audio_free; + SWVoiceOut *voice; - QEMUTimer *ts, *aux_ts; + QEMUTimer *aux_ts; /* mixer state */ int mixer_nreg; uint8_t mixer_regs[256]; @@ -110,6 +111,8 @@ typedef struct SB16State { /* XXX: suppress that and use a context */ static struct SB16State dsp; +static void SB_audio_callback (void *opaque, int free); + static int magic_of_irq (int irq) { switch (irq) { @@ -174,11 +177,11 @@ static void control (SB16State *s, int hold) if (hold) { DMA_hold_DREQ (dma); - AUD_enable (s->voice, 1); + AUD_set_active_out (s->voice, 1); } else { DMA_release_DREQ (dma); - AUD_enable (s->voice, 0); + AUD_set_active_out (s->voice, 0); } } @@ -207,8 +210,9 @@ static void dma_cmd8 (SB16State *s, int mask, int dma_len) s->freq = (1000000 + (tmp / 2)) / tmp; } - if (dma_len != -1) + if (dma_len != -1) { s->block_size = dma_len << s->fmt_stereo; + } else { /* This is apparently the only way to make both Act1/PL and SecondReality/FC work @@ -227,17 +231,28 @@ static void dma_cmd8 (SB16State *s, int mask, int dma_len) s->dma_auto = (mask & DMA8_AUTO) != 0; s->align = (1 << s->fmt_stereo) - 1; - if (s->block_size & s->align) - dolog ("warning: unaligned buffer\n"); + if (s->block_size & s->align) { + dolog ("warning: misaligned block size %d, alignment %d\n", + s->block_size, s->align + 1); + } ldebug ("freq %d, stereo %d, sign %d, bits %d, " "dma %d, auto %d, fifo %d, high %d\n", s->freq, s->fmt_stereo, s->fmt_signed, s->fmt_bits, s->block_size, s->dma_auto, s->fifo, s->highspeed); - if (s->freq) - s->voice = AUD_open (s->voice, "sb16", s->freq, - 1 << s->fmt_stereo, s->fmt); + if (s->freq) { + s->audio_free = 0; + s->voice = AUD_open_out ( + s->voice, + "sb16", + s, + SB_audio_callback, + s->freq, + 1 << s->fmt_stereo, + s->fmt + ); + } control (s, 1); speaker (s, 1); @@ -309,12 +324,23 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len) s->bytes_per_second = (s->freq << s->fmt_stereo) << (s->fmt_bits == 16); s->highspeed = 0; s->align = (1 << (s->fmt_stereo + (s->fmt_bits == 16))) - 1; - if (s->block_size & s->align) - dolog ("warning: unaligned buffer\n"); + if (s->block_size & s->align) { + dolog ("warning: misaligned block size %d, alignment %d\n", + s->block_size, s->align + 1); + } - if (s->freq) - s->voice = AUD_open (s->voice, "sb16", s->freq, - 1 << s->fmt_stereo, s->fmt); + if (s->freq) { + s->audio_free = 0; + s->voice = AUD_open_out ( + s->voice, + "sb16", + s, + SB_audio_callback, + s->freq, + 1 << s->fmt_stereo, + s->fmt + ); + } control (s, 1); speaker (s, 1); @@ -323,14 +349,16 @@ static void dma_cmd (SB16State *s, uint8_t cmd, uint8_t d0, int dma_len) static inline void dsp_out_data (SB16State *s, uint8_t val) { ldebug ("outdata %#x\n", val); - if (s->out_data_len < sizeof (s->out_data)) + if (s->out_data_len < sizeof (s->out_data)) { s->out_data[s->out_data_len++] = val; + } } static inline uint8_t dsp_get_data (SB16State *s) { - if (s->in_index) + if (s->in_index) { return s->in2_data[--s->in_index]; + } else { dolog ("buffer underflow\n"); return 0; @@ -356,6 +384,8 @@ static void command (SB16State *s, uint8_t cmd) s->needed_bytes = 3; } else { + s->needed_bytes = 0; + switch (cmd) { case 0x03: dsp_out_data (s, 0x10); /* s->csp_param); */ @@ -403,7 +433,7 @@ static void command (SB16State *s, uint8_t cmd) goto warn; case 0x35: - dolog ("MIDI command(0x35) not implemented\n"); + dolog ("0x35 - MIDI command not implemented\n"); break; case 0x40: @@ -435,6 +465,38 @@ static void command (SB16State *s, uint8_t cmd) s->needed_bytes = 2; break; + case 0x74: + s->needed_bytes = 2; /* DMA DAC, 4-bit ADPCM */ + dolog ("0x75 - DMA DAC, 4-bit ADPCM not implemented\n"); + break; + + case 0x75: /* DMA DAC, 4-bit ADPCM Reference */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 4-bit ADPCM Reference not implemented\n"); + break; + + case 0x76: /* DMA DAC, 2.6-bit ADPCM */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 2.6-bit ADPCM not implemented\n"); + break; + + case 0x77: /* DMA DAC, 2.6-bit ADPCM Reference */ + s->needed_bytes = 2; + dolog ("0x74 - DMA DAC, 2.6-bit ADPCM Reference not implemented\n"); + break; + + case 0x7d: + dolog ("0x7d - Autio-Initialize DMA DAC, 4-bit ADPCM Reference\n"); + dolog ("not implemented\n"); + break; + + case 0x7f: + dolog ( + "0x7d - Autio-Initialize DMA DAC, 2.6-bit ADPCM Reference\n" + ); + dolog ("not implemented\n"); + break; + case 0x80: s->needed_bytes = 2; break; @@ -476,9 +538,9 @@ static void command (SB16State *s, uint8_t cmd) s->dma_auto = 0; break; - case 0xe0: + case 0xe0: /* DSP identification */ s->needed_bytes = 1; - goto warn; + break; case 0xe1: dsp_out_data (s, s->ver & 0xff); @@ -503,7 +565,7 @@ static void command (SB16State *s, uint8_t cmd) case 0xe7: dolog ("Attempt to probe for ESS (0xe7)?\n"); - return; + break; case 0xe8: /* read test reg */ dsp_out_data (s, s->test_reg); @@ -529,21 +591,29 @@ static void command (SB16State *s, uint8_t cmd) goto warn; default: - dolog ("unrecognized command %#x\n", cmd); - return; + dolog ("Unrecognized command %#x\n", cmd); + break; } } - s->cmd = cmd; - if (!s->needed_bytes) + if (!s->needed_bytes) { ldebug ("\n"); + } + + exit: + if (!s->needed_bytes) { + s->cmd = -1; + } + else { + s->cmd = cmd; + } return; warn: dolog ("warning: command %#x,%d is not truly understood yet\n", cmd, s->needed_bytes); - s->cmd = cmd; - return; + goto exit; + } static uint16_t dsp_get_lohi (SB16State *s) @@ -607,8 +677,9 @@ static void complete (SB16State *s) s->csp_reg83[s->csp_reg83r % 4] = d0; s->csp_reg83r += 1; } - else + else { s->csp_regs[d1] = d0; + } break; case 0x0f: @@ -622,8 +693,9 @@ static void complete (SB16State *s) dsp_out_data (s, s->csp_reg83[s->csp_reg83w % 4]); s->csp_reg83w += 1; } - else + else { dsp_out_data (s, s->csp_regs[d0]); + } break; case 0x10: @@ -641,8 +713,9 @@ static void complete (SB16State *s) break; case 0x42: /* FT2 sets output freq with this, go figure */ +#if 0 dolog ("cmd 0x42 might not do what it think it should\n"); - +#endif case 0x41: s->freq = dsp_get_hilo (s); ldebug ("set freq %d\n", s->freq); @@ -653,6 +726,13 @@ static void complete (SB16State *s) ldebug ("set dma block len %d\n", s->block_size); break; + case 0x74: + case 0x75: + case 0x76: + case 0x77: + /* ADPCM stuff, ignore */ + break; + case 0x80: { int freq, samples, bytes; @@ -662,10 +742,17 @@ static void complete (SB16State *s) samples = dsp_get_lohi (s) + 1; bytes = samples << s->fmt_stereo << (s->fmt_bits == 16); ticks = (bytes * ticks_per_sec) / freq; - if (ticks < ticks_per_sec / 1024) + if (ticks < ticks_per_sec / 1024) { pic_set_irq (s->irq, 1); - else - qemu_mod_timer (s->aux_ts, qemu_get_clock (vm_clock) + ticks); + } + else { + if (s->aux_ts) { + qemu_mod_timer ( + s->aux_ts, + qemu_get_clock (vm_clock) + ticks + ); + } + } ldebug ("mix silence %d %d %lld\n", samples, bytes, ticks); } break; @@ -674,7 +761,7 @@ static void complete (SB16State *s) d0 = dsp_get_data (s); s->out_data_len = 0; ldebug ("E0 data = %#x\n", d0); - dsp_out_data(s, ~d0); + dsp_out_data (s, ~d0); break; case 0xe2: @@ -737,6 +824,7 @@ static void reset (SB16State *s) s->nzero = 0; s->highspeed = 0; s->v2x6 = 0; + s->cmd = -1; dsp_out_data(s, 0xaa); speaker (s, 0); @@ -761,8 +849,9 @@ static IO_WRITE_PROTO (dsp_write) pic_set_irq (s->irq, 0); control (s, 0); } - else + else { reset (s); + } } s->v2x6 = 0; break; @@ -845,7 +934,10 @@ static IO_READ_PROTO (dsp_read) s->last_read_byte = retval; } else { - dolog ("empty output buffer\n"); + if (s->cmd != -1) { + dolog ("empty output buffer for command %#x\n", + s->cmd); + } retval = s->last_read_byte; /* goto error; */ } @@ -882,13 +974,14 @@ static IO_READ_PROTO (dsp_read) goto error; } - if (!ack) + if (!ack) { ldebug ("read %#x -> %#x\n", nport, retval); + } return retval; error: - dolog ("WARNING dsp_read %#x error\n", nport); + dolog ("warning: dsp_read %#x error\n", nport); return 0xff; } @@ -933,8 +1026,9 @@ static IO_WRITE_PROTO(mixer_write_datab) SB16State *s = opaque; ldebug ("mixer_write [%#x] <- %#x\n", s->mixer_nreg, val); - if (s->mixer_nreg > sizeof (s->mixer_regs)) + if (s->mixer_nreg > sizeof (s->mixer_regs)) { return; + } switch (s->mixer_nreg) { case 0x00: @@ -945,8 +1039,9 @@ static IO_WRITE_PROTO(mixer_write_datab) { int irq = irq_of_magic (val); ldebug ("setting irq to %d (val=%#x)\n", irq, val); - if (irq > 0) + if (irq > 0) { s->irq = irq; + } } break; @@ -956,8 +1051,12 @@ static IO_WRITE_PROTO(mixer_write_datab) dma = lsbindex (val & 0xf); hdma = lsbindex (val & 0xf0); - dolog ("attempt to set DMA register 8bit %d, 16bit %d (val=%#x)\n", - dma, hdma, val); + if (dma != s->dma || hdma != s->hdma) { + dolog ( + "attempt to change DMA " + "8bit %d(%d), 16bit %d(%d) (val=%#x)\n", + dma, s->dma, hdma, s->hdma, val); + } #if 0 s->dma = dma; s->hdma = hdma; @@ -971,8 +1070,9 @@ static IO_WRITE_PROTO(mixer_write_datab) return; default: - if (s->mixer_nreg >= 0x80) - dolog ("attempt to write mixer[%#x] <- %#x\n", s->mixer_nreg, val); + if (s->mixer_nreg >= 0x80) { + ldebug ("attempt to write mixer[%#x] <- %#x\n", s->mixer_nreg, val); + } break; } @@ -989,10 +1089,14 @@ static IO_READ_PROTO(mixer_read) { SB16State *s = opaque; #ifndef DEBUG_SB16_MOST - if (s->mixer_nreg != 0x82) -#endif + if (s->mixer_nreg != 0x82) { + ldebug ("mixer_read[%#x] -> %#x\n", + s->mixer_nreg, s->mixer_regs[s->mixer_nreg]); + } +#else ldebug ("mixer_read[%#x] -> %#x\n", s->mixer_nreg, s->mixer_regs[s->mixer_nreg]); +#endif return s->mixer_regs[s->mixer_nreg]; } @@ -1010,8 +1114,9 @@ static int write_audio (SB16State *s, int nchan, int dma_pos, int to_copy, copied; to_copy = audio_MIN (temp, left); - if (to_copy > sizeof(tmpbuf)) + if (to_copy > sizeof(tmpbuf)) { to_copy = sizeof(tmpbuf); + } copied = DMA_read_memory (nchan, tmpbuf, dma_pos, to_copy); copied = AUD_write (s->voice, tmpbuf, copied); @@ -1020,8 +1125,9 @@ static int write_audio (SB16State *s, int nchan, int dma_pos, dma_pos = (dma_pos + copied) % dma_len; net += copied; - if (!copied) + if (!copied) { break; + } } return net; @@ -1030,27 +1136,28 @@ static int write_audio (SB16State *s, int nchan, int dma_pos, static int SB_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len) { SB16State *s = opaque; - int free, rfree, till, copy, written, elapsed; + int till, copy, written, free; if (s->left_till_irq < 0) { s->left_till_irq = s->block_size; } - elapsed = AUD_calc_elapsed (s->voice); - free = elapsed;/* AUD_get_free (s->voice); */ - rfree = free; - free = audio_MIN (free, elapsed) & ~s->align; - - if ((free <= 0) || !dma_len) { - return dma_pos; + if (s->voice) { + free = s->audio_free & ~s->align; + if ((free <= 0) || !dma_len) { + return dma_pos; + } + } + else { + free = dma_len; } copy = free; till = s->left_till_irq; #ifdef DEBUG_SB16_MOST - dolog ("pos:%06d free:%d,%d till:%d len:%d\n", - dma_pos, free, AUD_get_free (s->voice), till, dma_len); + dolog ("pos:%06d %d till:%d len:%d\n", + dma_pos, free, till, dma_len); #endif if (till <= copy) { @@ -1082,15 +1189,13 @@ static int SB_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len) s->left_till_irq = s->block_size + s->left_till_irq; } - AUD_adjust (s->voice, written); return dma_pos; } -void SB_timer (void *opaque) +static void SB_audio_callback (void *opaque, int free) { SB16State *s = opaque; - AUD_run (); - qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + 1); + s->audio_free = free; } static void SB_save (QEMUFile *f, void *opaque) @@ -1150,8 +1255,9 @@ static int SB_load (QEMUFile *f, void *opaque, int version_id) { SB16State *s = opaque; - if (version_id != 1) + if (version_id != 1) { return -EINVAL; + } qemu_get_be32s (f, &s->irq); qemu_get_be32s (f, &s->dma); @@ -1202,14 +1308,23 @@ static int SB_load (QEMUFile *f, void *opaque, int version_id) qemu_get_buffer (f, s->mixer_regs, 256); if (s->voice) { - AUD_close (s->voice); + AUD_close_out (s->voice); s->voice = NULL; } if (s->dma_running) { - if (s->freq) - s->voice = AUD_open (s->voice, "sb16", s->freq, - 1 << s->fmt_stereo, s->fmt); + if (s->freq) { + s->audio_free = 0; + s->voice = AUD_open_out ( + s->voice, + "sb16", + s, + SB_audio_callback, + s->freq, + 1 << s->fmt_stereo, + s->fmt + ); + } control (s, 1); speaker (s, s->speaker); @@ -1224,10 +1339,7 @@ void SB16_init (void) static const uint8_t dsp_write_ports[] = {0x6, 0xc}; static const uint8_t dsp_read_ports[] = {0x6, 0xa, 0xc, 0xd, 0xe, 0xf}; - s->ts = qemu_new_timer (vm_clock, SB_timer, s); - if (!s->ts) - return; - + s->cmd = -1; s->irq = conf.irq; s->dma = conf.dma; s->hdma = conf.hdma; @@ -1243,8 +1355,9 @@ void SB16_init (void) reset_mixer (s); s->aux_ts = qemu_new_timer (vm_clock, aux_timer, s); - if (!s->aux_ts) - return; + if (!s->aux_ts) { + dolog ("Can not create auxiliary timer\n"); + } for (i = 0; i < LENOFA (dsp_write_ports); i++) { register_ioport_write (s->port + dsp_write_ports[i], 1, 1, dsp_write, s); @@ -1263,6 +1376,5 @@ void SB16_init (void) DMA_register_channel (s->dma, SB_read_DMA, s); s->can_write = 1; - qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + 1); register_savevm ("sb16", 0, 1, SB_save, SB_load, s); } diff --git a/qemu-doc.texi b/qemu-doc.texi index 526bd4b733..51875d7741 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -181,8 +181,23 @@ The default is @code{en-us}. @item -enable-audio -The SB16 emulation is disabled by default as it may give problems with -Windows. You can enable it manually with this option. +Will enable audio and all the sound hardware QEMU was built with. + +@item -audio-help + +Will show the audio subsystem help: list of drivers, tunable +parameters. + +@item -soundhw card1,card2,... + +Enable audio and selected sound hardware. Use ? to print all +available sound hardware. + +@example +qemu -soundhw sb16,adlib hda +qemu -soundhw es1370 hda +qemu -soundhw ? +@end example @item -localtime Set the real time clock to local time (the default is to UTC diff --git a/vl.c b/vl.c index e7cd9668f5..1ca0c347a9 100644 --- a/vl.c +++ b/vl.c @@ -127,10 +127,13 @@ int nb_nics; NetDriverState nd_table[MAX_NICS]; QEMUTimer *gui_timer; int vm_running; +#ifdef HAS_AUDIO int audio_enabled = 0; -int sb16_enabled = 1; -int adlib_enabled = 1; -int gus_enabled = 1; +int sb16_enabled = 0; +int adlib_enabled = 0; +int gus_enabled = 0; +int es1370_enabled = 0; +#endif int pci_enabled = 1; int prep_enabled = 0; int rtc_utc = 1; @@ -930,7 +933,7 @@ static void init_timers(void) #ifdef _WIN32 { int count=0; - timerID = timeSetEvent(10, // interval (ms) + timerID = timeSetEvent(1, // interval (ms) 0, // resolution host_alarm_handler, // function (DWORD)&count, // user parameter @@ -2837,7 +2840,12 @@ void help(void) #ifndef _WIN32 "-k language use keyboard layout (for example \"fr\" for French)\n" #endif +#ifdef HAS_AUDIO "-enable-audio enable audio support\n" + "-audio-help print list of audio drivers and their options\n" + "-soundhw c1,... comma separated list of sound card names\n" + " use -soundhw ? to get the list of supported sound cards\n" +#endif "-localtime set the real time clock to local time [default=utc]\n" "-full-screen start in full screen\n" #ifdef TARGET_I386 @@ -2935,7 +2943,11 @@ enum { QEMU_OPTION_snapshot, QEMU_OPTION_m, QEMU_OPTION_nographic, +#ifdef HAS_AUDIO QEMU_OPTION_enable_audio, + QEMU_OPTION_audio_help, + QEMU_OPTION_soundhw, +#endif QEMU_OPTION_nics, QEMU_OPTION_macaddr, @@ -2998,7 +3010,11 @@ const QEMUOption qemu_options[] = { { "m", HAS_ARG, QEMU_OPTION_m }, { "nographic", 0, QEMU_OPTION_nographic }, { "k", HAS_ARG, QEMU_OPTION_k }, +#ifdef HAS_AUDIO { "enable-audio", 0, QEMU_OPTION_enable_audio }, + { "audio-help", 0, QEMU_OPTION_audio_help }, + { "soundhw", HAS_ARG, QEMU_OPTION_soundhw }, +#endif { "nics", HAS_ARG, QEMU_OPTION_nics}, { "macaddr", HAS_ARG, QEMU_OPTION_macaddr}, @@ -3117,6 +3133,78 @@ void register_machines(void) #endif } +#ifdef HAS_AUDIO +static void select_soundhw (const char *optarg) +{ + if (*optarg == '?') { + show_valid_cards: + printf ("Valid sound card names (comma separated):\n"); + printf ("sb16 Creative Sound Blaster 16\n"); +#ifdef CONFIG_ADLIB +#ifdef HAS_YMF262 + printf ("adlib Ymaha YMF262 (OPL3)\n"); +#else + printf ("adlib Ymaha YM3812 (OPL2)\n"); +#endif +#endif +#ifdef CONFIG_GUS + printf ("gus Gravis Ultrasound GF1\n"); +#endif + printf ("es1370 ENSONIQ AudioPCI ES1370\n"); + exit (*optarg != '?'); + } + else { + struct { + char *name; + int *enabledp; + } soundhw_tab[] = { + { "sb16", &sb16_enabled }, +#ifdef CONFIG_ADLIB + { "adlib", &adlib_enabled }, +#endif +#ifdef CONFIG_GUS + { "gus", &gus_enabled }, +#endif + { "es1370", &es1370_enabled }, + }; + size_t tablen, l, i; + const char *p; + char *e; + int bad_card = 0; + + p = optarg; + tablen = sizeof (soundhw_tab) / sizeof (soundhw_tab[0]); + + while (*p) { + e = strchr (p, ','); + l = !e ? strlen (p) : (size_t) (e - p); + for (i = 0; i < tablen; ++i) { + if (!strncmp (soundhw_tab[i].name, p, l)) { + audio_enabled = 1; + *soundhw_tab[i].enabledp = 1; + break; + } + } + if (i == tablen) { + if (l > 80) { + fprintf (stderr, + "Unknown sound card name (too big to show)\n"); + } + else { + fprintf (stderr, "Unknown sound card name `%.*s'\n", + (int) l, p); + } + bad_card = 1; + } + p += l + (e != NULL); + } + + if (bad_card) + goto show_valid_cards; + } +} +#endif + #define NET_IF_TUN 0 #define NET_IF_USER 1 #define NET_IF_DUMMY 2 @@ -3401,9 +3489,22 @@ int main(int argc, char **argv) case QEMU_OPTION_dummy_net: net_if_type = NET_IF_DUMMY; break; +#ifdef HAS_AUDIO case QEMU_OPTION_enable_audio: audio_enabled = 1; + sb16_enabled = 1; + adlib_enabled = 1; + gus_enabled = 1; + es1370_enabled = 1; break; + case QEMU_OPTION_audio_help: + AUD_help (); + exit (0); + break; + case QEMU_OPTION_soundhw: + select_soundhw (optarg); + break; +#endif case QEMU_OPTION_h: help(); break; diff --git a/vl.h b/vl.h index 31d05b6b58..543d57972a 100644 --- a/vl.h +++ b/vl.h @@ -124,6 +124,7 @@ extern int audio_enabled; extern int sb16_enabled; extern int adlib_enabled; extern int gus_enabled; +extern int es1370_enabled; extern int ram_size; extern int bios_size; extern int rtc_utc; @@ -628,6 +629,9 @@ void pci_piix3_ide_init(PCIBus *bus, BlockDriverState **hd_table); int pmac_ide_init (BlockDriverState **hd_table, SetIRQFunc *set_irq, void *irq_opaque, int irq); +/* es1370.c */ +int es1370_init (PCIBus *bus); + /* sb16.c */ void SB16_init (void);