Browse Source
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1125 c046a42c-6fe2-441c-8c8c-71466251a162stable-0.10
24 changed files with 6038 additions and 1674 deletions
@ -0,0 +1,935 @@ |
|||
/*
|
|||
* QEMU Audio subsystem |
|||
* |
|||
* Copyright (c) 2003-2004 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 <assert.h> |
|||
#include <limits.h> |
|||
#include "vl.h" |
|||
|
|||
#define AUDIO_CAP "audio" |
|||
#include "audio/audio.h" |
|||
|
|||
#define USE_SDL_AUDIO |
|||
#define USE_WAV_AUDIO |
|||
|
|||
#if defined __linux__ || (defined _BSD && !defined __APPLE__) |
|||
#define USE_OSS_AUDIO |
|||
#endif |
|||
|
|||
#ifdef USE_OSS_AUDIO |
|||
#include "audio/ossaudio.h" |
|||
#endif |
|||
|
|||
#ifdef USE_SDL_AUDIO |
|||
#include "audio/sdlaudio.h" |
|||
#endif |
|||
|
|||
#ifdef USE_WAV_AUDIO |
|||
#include "audio/wavaudio.h" |
|||
#endif |
|||
|
|||
#ifdef USE_FMOD_AUDIO |
|||
#include "audio/fmodaudio.h" |
|||
#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" |
|||
|
|||
extern void SB16_init (void); |
|||
|
|||
#ifdef USE_ADLIB |
|||
extern void Adlib_init (void); |
|||
#endif |
|||
|
|||
#ifdef USE_GUS |
|||
extern void GUS_init (void); |
|||
#endif |
|||
|
|||
static void (*hw_ctors[]) (void) = { |
|||
SB16_init, |
|||
#ifdef USE_ADLIB |
|||
Adlib_init, |
|||
#endif |
|||
#ifdef USE_GUS |
|||
GUS_init, |
|||
#endif |
|||
NULL |
|||
}; |
|||
|
|||
static HWVoice *hw_voice; |
|||
|
|||
AudioState audio_state = { |
|||
1, /* use fixed settings */ |
|||
44100, /* fixed frequency */ |
|||
2, /* fixed channels */ |
|||
AUD_FMT_S16, /* fixed format */ |
|||
1, /* number of hw voices */ |
|||
-1 /* voice size */ |
|||
}; |
|||
|
|||
/* http://www.df.lth.se/~john_e/gems/gem002d.html */ |
|||
/* http://www.multi-platforms.com/Tips/PopCount.htm */ |
|||
uint32_t popcount (uint32_t u) |
|||
{ |
|||
u = ((u&0x55555555) + ((u>>1)&0x55555555)); |
|||
u = ((u&0x33333333) + ((u>>2)&0x33333333)); |
|||
u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f)); |
|||
u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff)); |
|||
u = ( u&0x0000ffff) + (u>>16); |
|||
return u; |
|||
} |
|||
|
|||
inline uint32_t lsbindex (uint32_t u) |
|||
{ |
|||
return popcount ((u&-u)-1); |
|||
} |
|||
|
|||
int audio_get_conf_int (const char *key, int defval) |
|||
{ |
|||
int val = defval; |
|||
char *strval; |
|||
|
|||
strval = getenv (key); |
|||
if (strval) { |
|||
val = atoi (strval); |
|||
} |
|||
|
|||
return val; |
|||
} |
|||
|
|||
const char *audio_get_conf_str (const char *key, const char *defval) |
|||
{ |
|||
const char *val = getenv (key); |
|||
if (!val) |
|||
return defval; |
|||
else |
|||
return val; |
|||
} |
|||
|
|||
void audio_log (const char *fmt, ...) |
|||
{ |
|||
va_list ap; |
|||
va_start (ap, fmt); |
|||
vfprintf (stderr, fmt, ap); |
|||
va_end (ap); |
|||
} |
|||
|
|||
/*
|
|||
* Soft Voice |
|||
*/ |
|||
void pcm_sw_free_resources (SWVoice *sw) |
|||
{ |
|||
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; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
void pcm_sw_fini (SWVoice *sw) |
|||
{ |
|||
pcm_sw_free_resources (sw); |
|||
} |
|||
|
|||
int pcm_sw_init (SWVoice *sw, HWVoice *hw, 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; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
/* Hard voice */ |
|||
void pcm_hw_free_resources (HWVoice *hw) |
|||
{ |
|||
if (hw->mix_buf) |
|||
qemu_free (hw->mix_buf); |
|||
hw->mix_buf = NULL; |
|||
} |
|||
|
|||
int pcm_hw_alloc_resources (HWVoice *hw) |
|||
{ |
|||
hw->mix_buf = qemu_mallocz (hw->samples * sizeof (st_sample_t)); |
|||
if (!hw->mix_buf) |
|||
return -1; |
|||
return 0; |
|||
} |
|||
|
|||
|
|||
void pcm_hw_fini (HWVoice *hw) |
|||
{ |
|||
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); |
|||
} |
|||
} |
|||
|
|||
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 |
|||
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 |
|||
return -1; |
|||
} |
|||
|
|||
void pcm_hw_dec_live (HWVoice *hw, int decr) |
|||
{ |
|||
int i; |
|||
|
|||
for (i = 0; i < hw->nb_voices; i++) { |
|||
if (hw->pvoice[i]->live) { |
|||
hw->pvoice[i]->live -= decr; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void pcm_hw_clear (HWVoice *hw, void *buf, int len) |
|||
{ |
|||
if (!len) |
|||
return; |
|||
|
|||
switch (hw->fmt) { |
|||
case AUD_FMT_S16: |
|||
case AUD_FMT_S8: |
|||
memset (buf, len << hw->shift, 0x00); |
|||
break; |
|||
|
|||
case AUD_FMT_U8: |
|||
memset (buf, len << hw->shift, 0x80); |
|||
break; |
|||
|
|||
case AUD_FMT_U16: |
|||
{ |
|||
unsigned int i; |
|||
uint16_t *p = buf; |
|||
int shift = hw->nchannels - 1; |
|||
|
|||
for (i = 0; i < len << shift; i++) { |
|||
p[i] = INT16_MAX; |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
int pcm_hw_write (SWVoice *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; |
|||
|
|||
hwsamples = sw->hw->samples; |
|||
samples = size >> sw->shift; |
|||
|
|||
if (!sw->live) { |
|||
sw->wpos = sw->hw->rpos; |
|||
} |
|||
wpos = sw->wpos; |
|||
live = sw->live; |
|||
dead = hwsamples - live; |
|||
swlim = (dead * ((int64_t) INT_MAX)) / sw->ratio; |
|||
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) { |
|||
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); |
|||
ret += isamp; |
|||
swlim -= isamp; |
|||
pos += isamp; |
|||
live += osamp; |
|||
wpos = (wpos + osamp) % hwsamples; |
|||
} |
|||
|
|||
sw->wpos = wpos; |
|||
sw->live = live; |
|||
return ret << sw->shift; |
|||
} |
|||
|
|||
int pcm_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
|||
{ |
|||
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; |
|||
} |
|||
|
|||
static int dist (void *hw) |
|||
{ |
|||
if (hw) { |
|||
return (((uint8_t *) hw - (uint8_t *) hw_voice) |
|||
/ audio_state.voice_size) + 1; |
|||
} |
|||
else { |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
#define ADVANCE(hw) hw ? advance (hw, audio_state.voice_size) : hw_voice |
|||
|
|||
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; |
|||
} |
|||
|
|||
int AUD_write (SWVoice *sw, void *buf, int size) |
|||
{ |
|||
int bytes; |
|||
|
|||
if (!sw->hw->enabled) |
|||
dolog ("Writing to disabled voice %s\n", sw->name); |
|||
bytes = sw->hw->pcm_ops->write (sw, buf, size); |
|||
return bytes; |
|||
} |
|||
|
|||
void AUD_run (void) |
|||
{ |
|||
HWVoice *hw = NULL; |
|||
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
return sw->hw->bufsize; |
|||
} |
|||
|
|||
void AUD_adjust (SWVoice *sw, int bytes) |
|||
{ |
|||
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) { |
|||
if (on) { |
|||
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); |
|||
} |
|||
} |
|||
else { |
|||
if (hw->enabled && !hw->pending_disable) { |
|||
int nb_active = 0; |
|||
for (i = 0; i < hw->nb_voices; i++) { |
|||
nb_active += hw->pvoice[i]->active != 0; |
|||
} |
|||
|
|||
if (nb_active == 1) { |
|||
hw->pending_disable = 1; |
|||
} |
|||
} |
|||
} |
|||
sw->active = on; |
|||
} |
|||
} |
|||
|
|||
static struct audio_output_driver *drvtab[] = { |
|||
#ifdef USE_OSS_AUDIO |
|||
&oss_output_driver, |
|||
#endif |
|||
#ifdef USE_FMOD_AUDIO |
|||
&fmod_output_driver, |
|||
#endif |
|||
#ifdef USE_SDL_AUDIO |
|||
&sdl_output_driver, |
|||
#endif |
|||
#ifdef USE_WAV_AUDIO |
|||
&wav_output_driver, |
|||
#endif |
|||
}; |
|||
|
|||
static int voice_init (struct audio_output_driver *drv) |
|||
{ |
|||
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; |
|||
} |
|||
hw_voice = qemu_mallocz (audio_state.nb_hw_voices * drv->voice_size); |
|||
if (hw_voice) { |
|||
audio_state.drv = drv; |
|||
return 1; |
|||
} |
|||
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; |
|||
} |
|||
} |
|||
else { |
|||
dolog ("Could not init `%s' audio\n", drv->name); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
static void audio_vm_stop_handler (void *opaque, int reason) |
|||
{ |
|||
HWVoice *hw = NULL; |
|||
|
|||
while ((hw = pcm_hw_find_any (hw))) { |
|||
if (!hw->pcm_ops) |
|||
continue; |
|||
|
|||
hw->pcm_ops->ctl (hw, reason ? VOICE_ENABLE : VOICE_DISABLE); |
|||
} |
|||
} |
|||
|
|||
static void audio_atexit (void) |
|||
{ |
|||
HWVoice *hw = NULL; |
|||
|
|||
while ((hw = pcm_hw_find_any (hw))) { |
|||
if (!hw->pcm_ops) |
|||
continue; |
|||
|
|||
hw->pcm_ops->ctl (hw, VOICE_DISABLE); |
|||
hw->pcm_ops->fini (hw); |
|||
} |
|||
audio_state.drv->fini (audio_state.opaque); |
|||
} |
|||
|
|||
static void audio_save (QEMUFile *f, void *opaque) |
|||
{ |
|||
} |
|||
|
|||
static int audio_load (QEMUFile *f, void *opaque, int version_id) |
|||
{ |
|||
if (version_id != 1) |
|||
return -EINVAL; |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
void AUD_init (void) |
|||
{ |
|||
int i; |
|||
int done = 0; |
|||
const char *drvname; |
|||
|
|||
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); |
|||
|
|||
if (audio_state.nb_hw_voices <= 0) { |
|||
dolog ("Bogus number of voices %d, resetting to 1\n", |
|||
audio_state.nb_hw_voices); |
|||
} |
|||
|
|||
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]); |
|||
found = 1; |
|||
break; |
|||
} |
|||
} |
|||
if (!found) { |
|||
dolog ("Unknown audio driver `%s'\n", drvname); |
|||
} |
|||
} |
|||
|
|||
qemu_add_vm_stop_handler (audio_vm_stop_handler, NULL); |
|||
atexit (audio_atexit); |
|||
|
|||
if (!done) { |
|||
for (i = 0; !done && i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { |
|||
if (drvtab[i]->can_be_default) |
|||
done = voice_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"); |
|||
return; |
|||
} |
|||
|
|||
for (i = 0; hw_ctors[i]; i++) { |
|||
hw_ctors[i] (); |
|||
} |
|||
} |
|||
@ -0,0 +1,188 @@ |
|||
/*
|
|||
* QEMU Audio subsystem header |
|||
* |
|||
* Copyright (c) 2003-2004 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. |
|||
*/ |
|||
#ifndef QEMU_AUDIO_H |
|||
#define QEMU_AUDIO_H |
|||
|
|||
#include "mixeng.h" |
|||
|
|||
#define dolog(...) fprintf (stderr, AUDIO_CAP ": " __VA_ARGS__) |
|||
#ifdef DEBUG |
|||
#define ldebug(...) dolog (__VA_ARGS__) |
|||
#else |
|||
#define ldebug(...) |
|||
#endif |
|||
|
|||
typedef enum { |
|||
AUD_FMT_U8, |
|||
AUD_FMT_S8, |
|||
AUD_FMT_U16, |
|||
AUD_FMT_S16 |
|||
} audfmt_e; |
|||
|
|||
typedef struct HWVoice HWVoice; |
|||
struct audio_output_driver; |
|||
|
|||
typedef struct AudioState { |
|||
int fixed_format; |
|||
int fixed_freq; |
|||
int fixed_channels; |
|||
int fixed_fmt; |
|||
int nb_hw_voices; |
|||
int voice_size; |
|||
int64_t ticks_threshold; |
|||
int freq_threshold; |
|||
void *opaque; |
|||
struct audio_output_driver *drv; |
|||
} AudioState; |
|||
|
|||
extern AudioState audio_state; |
|||
|
|||
typedef struct SWVoice { |
|||
int freq; |
|||
audfmt_e fmt; |
|||
int nchannels; |
|||
|
|||
int shift; |
|||
int align; |
|||
|
|||
t_sample *conv; |
|||
|
|||
int left; |
|||
int pos; |
|||
int bytes_per_second; |
|||
int64_t ratio; |
|||
st_sample_t *buf; |
|||
void *rate; |
|||
|
|||
int wpos; |
|||
int live; |
|||
int active; |
|||
int64_t old_ticks; |
|||
HWVoice *hw; |
|||
char *name; |
|||
} SWVoice; |
|||
|
|||
#define VOICE_ENABLE 1 |
|||
#define VOICE_DISABLE 2 |
|||
|
|||
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 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; |
|||
}; |
|||
|
|||
struct HWVoice { |
|||
int active; |
|||
int enabled; |
|||
int pending_disable; |
|||
int valid; |
|||
int freq; |
|||
|
|||
f_sample *clip; |
|||
audfmt_e fmt; |
|||
int nchannels; |
|||
|
|||
int align; |
|||
int shift; |
|||
|
|||
int rpos; |
|||
int bufsize; |
|||
|
|||
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; |
|||
}; |
|||
|
|||
void audio_log (const char *fmt, ...); |
|||
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); |
|||
|
|||
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); |
|||
|
|||
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); |
|||
|
|||
int audio_get_conf_int (const char *key, int defval); |
|||
const char *audio_get_conf_str (const char *key, const char *defval); |
|||
|
|||
/* Public API */ |
|||
SWVoice * AUD_open (SWVoice *sw, const char *name, int freq, |
|||
int nchannels, audfmt_e fmt); |
|||
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); |
|||
|
|||
static inline void *advance (void *p, int incr) |
|||
{ |
|||
uint8_t *d = p; |
|||
return (d + incr); |
|||
} |
|||
|
|||
uint32_t popcount (uint32_t u); |
|||
inline uint32_t lsbindex (uint32_t u); |
|||
|
|||
#define audio_MIN(a, b) ((a)>(b)?(b):(a)) |
|||
#define audio_MAX(a, b) ((a)<(b)?(b):(a)) |
|||
|
|||
#endif /* audio.h */ |
|||
@ -0,0 +1,457 @@ |
|||
/*
|
|||
* QEMU FMOD audio output driver |
|||
* |
|||
* Copyright (c) 2004 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 <fmod.h> |
|||
#include <fmod_errors.h> |
|||
#include "vl.h" |
|||
|
|||
#define AUDIO_CAP "fmod" |
|||
#include "audio/audio.h" |
|||
#include "audio/fmodaudio.h" |
|||
|
|||
#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" |
|||
|
|||
static struct { |
|||
int nb_samples; |
|||
int freq; |
|||
int nb_channels; |
|||
int bufsize; |
|||
int threshold; |
|||
} conf = { |
|||
2048, |
|||
44100, |
|||
1, |
|||
0, |
|||
128 |
|||
}; |
|||
|
|||
#define errstr() FMOD_ErrorString (FSOUND_GetError ()) |
|||
|
|||
static int fmod_hw_write (SWVoice *sw, void *buf, int len) |
|||
{ |
|||
return pcm_hw_write (sw, buf, len); |
|||
} |
|||
|
|||
static void fmod_clear_sample (FMODVoice *fmd) |
|||
{ |
|||
HWVoice *hw = &fmd->hw; |
|||
int status; |
|||
void *p1 = 0, *p2 = 0; |
|||
unsigned int len1 = 0, len2 = 0; |
|||
|
|||
status = FSOUND_Sample_Lock ( |
|||
fmd->fmod_sample, |
|||
0, |
|||
hw->samples << hw->shift, |
|||
&p1, |
|||
&p2, |
|||
&len1, |
|||
&len2 |
|||
); |
|||
|
|||
if (!status) { |
|||
dolog ("Failed to lock sample\nReason: %s\n", errstr ()); |
|||
return; |
|||
} |
|||
|
|||
if ((len1 & hw->align) || (len2 & hw->align)) { |
|||
dolog ("Locking sample returned unaligned length %d, %d\n", |
|||
len1, len2); |
|||
goto fail; |
|||
} |
|||
|
|||
if (len1 + len2 != hw->samples << hw->shift) { |
|||
dolog ("Locking sample returned incomplete length %d, %d\n", |
|||
len1 + len2, hw->samples << hw->shift); |
|||
goto fail; |
|||
} |
|||
pcm_hw_clear (hw, 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 ()); |
|||
} |
|||
} |
|||
|
|||
static int fmod_write_sample (HWVoice *hw, uint8_t *dst, st_sample_t *src, |
|||
int src_size, int src_pos, 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; |
|||
|
|||
if (src_pos + dst_len > src_size) { |
|||
src_len1 = src_size - src_pos; |
|||
src2 = src; |
|||
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); |
|||
} |
|||
|
|||
if (src_len2) { |
|||
hw->clip (dst, src2, src_len2); |
|||
memset (src2, 0, src_len2 * sizeof (st_sample_t)); |
|||
} |
|||
return pos; |
|||
} |
|||
|
|||
static int fmod_unlock_sample (FMODVoice *fmd, void *p1, void *p2, |
|||
unsigned int blen1, unsigned int blen2) |
|||
{ |
|||
int status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, blen1, blen2); |
|||
if (!status) { |
|||
dolog ("Failed to unlock sample\nReason: %s\n", errstr ()); |
|||
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) |
|||
{ |
|||
HWVoice *hw = &fmd->hw; |
|||
int status; |
|||
|
|||
status = FSOUND_Sample_Lock ( |
|||
fmd->fmod_sample, |
|||
pos << hw->shift, |
|||
len << hw->shift, |
|||
p1, |
|||
p2, |
|||
blen1, |
|||
blen2 |
|||
); |
|||
|
|||
if (!status) { |
|||
dolog ("Failed to lock sample\nReason: %s\n", errstr ()); |
|||
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); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static void fmod_hw_run (HWVoice *hw) |
|||
{ |
|||
FMODVoice *fmd = (FMODVoice *) hw; |
|||
int rpos, live, decr; |
|||
void *p1 = 0, *p2 = 0; |
|||
unsigned int blen1 = 0, blen2 = 0; |
|||
unsigned int len1 = 0, len2 = 0; |
|||
int nb_active; |
|||
|
|||
live = pcm_hw_get_live2 (hw, &nb_active); |
|||
if (live <= 0) { |
|||
return; |
|||
} |
|||
|
|||
if (!hw->pending_disable |
|||
&& nb_active |
|||
&& conf.threshold |
|||
&& live <= conf.threshold) { |
|||
ldebug ("live=%d nb_active=%d\n", live, nb_active); |
|||
return; |
|||
} |
|||
|
|||
decr = live; |
|||
|
|||
#if 1 |
|||
if (fmd->channel >= 0) { |
|||
int pos2 = (fmd->old_pos + decr) % hw->samples; |
|||
int pos = FSOUND_GetCurrentPosition (fmd->channel); |
|||
|
|||
if (fmd->old_pos < pos && pos2 >= pos) { |
|||
decr = pos - fmd->old_pos - (pos2 == pos) - 1; |
|||
} |
|||
else if (fmd->old_pos > pos && pos2 >= pos && pos2 < fmd->old_pos) { |
|||
decr = (hw->samples - fmd->old_pos) + pos - (pos2 == pos) - 1; |
|||
} |
|||
/* ldebug ("pos=%d pos2=%d old=%d live=%d decr=%d\n", */ |
|||
/* pos, pos2, fmd->old_pos, live, decr); */ |
|||
} |
|||
#endif |
|||
|
|||
if (decr <= 0) { |
|||
return; |
|||
} |
|||
|
|||
if (fmod_lock_sample (fmd, fmd->old_pos, decr, &p1, &p2, &blen1, &blen2)) { |
|||
return; |
|||
} |
|||
|
|||
len1 = blen1 >> hw->shift; |
|||
len2 = blen2 >> hw->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 (len2) { |
|||
rpos = fmod_write_sample (hw, p2, hw->mix_buf, hw->samples, rpos, len2); |
|||
} |
|||
|
|||
fmod_unlock_sample (fmd, p1, p2, blen1, blen2); |
|||
|
|||
pcm_hw_dec_live (hw, decr); |
|||
hw->rpos = rpos % hw->samples; |
|||
fmd->old_pos = (fmd->old_pos + decr) % hw->samples; |
|||
} |
|||
|
|||
static int AUD_to_fmodfmt (audfmt_e fmt, int stereo) |
|||
{ |
|||
int mode = FSOUND_LOOP_NORMAL; |
|||
|
|||
switch (fmt) { |
|||
case AUD_FMT_S8: |
|||
mode |= FSOUND_SIGNED | FSOUND_8BITS; |
|||
break; |
|||
|
|||
case AUD_FMT_U8: |
|||
mode |= FSOUND_UNSIGNED | FSOUND_8BITS; |
|||
break; |
|||
|
|||
case AUD_FMT_S16: |
|||
mode |= FSOUND_SIGNED | FSOUND_16BITS; |
|||
break; |
|||
|
|||
case AUD_FMT_U16: |
|||
mode |= FSOUND_UNSIGNED | FSOUND_16BITS; |
|||
break; |
|||
|
|||
default: |
|||
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); |
|||
exit (EXIT_FAILURE); |
|||
} |
|||
mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; |
|||
return mode; |
|||
} |
|||
|
|||
static void fmod_hw_fini (HWVoice *hw) |
|||
{ |
|||
FMODVoice *fmd = (FMODVoice *) hw; |
|||
|
|||
if (fmd->fmod_sample) { |
|||
FSOUND_Sample_Free (fmd->fmod_sample); |
|||
fmd->fmod_sample = 0; |
|||
|
|||
if (fmd->channel >= 0) { |
|||
FSOUND_StopSound (fmd->channel); |
|||
} |
|||
} |
|||
} |
|||
|
|||
static int fmod_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
|||
{ |
|||
int bits16, mode, channel; |
|||
FMODVoice *fmd = (FMODVoice *) hw; |
|||
|
|||
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) { |
|||
dolog ("Failed to allocate FMOD sample\nReason: %s\n", errstr ()); |
|||
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 ()); |
|||
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; |
|||
hw->bufsize = conf.nb_samples << (nchannels == 2) << bits16; |
|||
return 0; |
|||
} |
|||
|
|||
static int fmod_hw_ctl (HWVoice *hw, int cmd, ...) |
|||
{ |
|||
int status; |
|||
FMODVoice *fmd = (FMODVoice *) 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 ()); |
|||
} |
|||
break; |
|||
|
|||
case VOICE_DISABLE: |
|||
status = FSOUND_SetPaused (fmd->channel, 1); |
|||
if (!status) { |
|||
dolog ("Failed to pause channel %d\nReason: %s\n", |
|||
fmd->channel, errstr ()); |
|||
} |
|||
break; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static struct { |
|||
const char *name; |
|||
int type; |
|||
} drvtab[] = { |
|||
{"none", FSOUND_OUTPUT_NOSOUND}, |
|||
#ifdef _WIN32 |
|||
{"winmm", FSOUND_OUTPUT_WINMM}, |
|||
{"dsound", FSOUND_OUTPUT_DSOUND}, |
|||
{"a3d", FSOUND_OUTPUT_A3D}, |
|||
{"asio", FSOUND_OUTPUT_ASIO}, |
|||
#endif |
|||
#ifdef __linux__ |
|||
{"oss", FSOUND_OUTPUT_OSS}, |
|||
{"alsa", FSOUND_OUTPUT_ALSA}, |
|||
{"esd", FSOUND_OUTPUT_ESD}, |
|||
#endif |
|||
#ifdef __APPLE__ |
|||
{"mac", FSOUND_OUTPUT_MAC}, |
|||
#endif |
|||
#if 0 |
|||
{"xbox", FSOUND_OUTPUT_XBOX}, |
|||
{"ps2", FSOUND_OUTPUT_PS2}, |
|||
{"gcube", FSOUND_OUTPUT_GC}, |
|||
#endif |
|||
{"nort", FSOUND_OUTPUT_NOSOUND_NONREALTIME} |
|||
}; |
|||
|
|||
static void *fmod_audio_init (void) |
|||
{ |
|||
int i; |
|||
double ver; |
|||
int status; |
|||
int output_type = -1; |
|||
const char *drv = audio_get_conf_str (QC_FMOD_DRV, NULL); |
|||
|
|||
ver = FSOUND_GetVersion (); |
|||
if (ver < FMOD_VERSION) { |
|||
dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION); |
|||
return NULL; |
|||
} |
|||
|
|||
if (drv) { |
|||
int found = 0; |
|||
for (i = 0; i < sizeof (drvtab) / sizeof (drvtab[0]); i++) { |
|||
if (!strcmp (drv, drvtab[i].name)) { |
|||
output_type = drvtab[i].type; |
|||
found = 1; |
|||
break; |
|||
} |
|||
} |
|||
if (!found) { |
|||
dolog ("Unknown FMOD output driver `%s'\n", drv); |
|||
} |
|||
} |
|||
|
|||
if (output_type != -1) { |
|||
status = FSOUND_SetOutput (output_type); |
|||
if (!status) { |
|||
dolog ("FSOUND_SetOutput(%d) failed\nReason: %s\n", |
|||
output_type, errstr ()); |
|||
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 ()); |
|||
} |
|||
} |
|||
|
|||
status = FSOUND_Init (conf.freq, conf.nb_channels, 0); |
|||
if (!status) { |
|||
dolog ("FSOUND_Init failed\nReason: %s\n", errstr ()); |
|||
return NULL; |
|||
} |
|||
|
|||
return &conf; |
|||
} |
|||
|
|||
static void fmod_audio_fini (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 |
|||
}; |
|||
|
|||
struct audio_output_driver fmod_output_driver = { |
|||
"fmod", |
|||
fmod_audio_init, |
|||
fmod_audio_fini, |
|||
&fmod_pcm_ops, |
|||
1, |
|||
INT_MAX, |
|||
sizeof (FMODVoice) |
|||
}; |
|||
@ -0,0 +1,39 @@ |
|||
/*
|
|||
* QEMU FMOD audio output driver header |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
#ifndef QEMU_FMODAUDIO_H |
|||
#define QEMU_FMODAUDIO_H |
|||
|
|||
#include <fmod.h> |
|||
|
|||
typedef struct FMODVoice { |
|||
struct HWVoice hw; |
|||
unsigned int old_pos; |
|||
FSOUND_SAMPLE *fmod_sample; |
|||
int channel; |
|||
} FMODVoice; |
|||
|
|||
extern struct pcm_ops fmod_pcm_ops; |
|||
extern struct audio_output_driver fmod_output_driver; |
|||
|
|||
#endif /* fmodaudio.h */ |
|||
@ -0,0 +1,255 @@ |
|||
/*
|
|||
* QEMU Mixing engine |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
#include "vl.h" |
|||
//#define DEBUG_FP
|
|||
#include "audio/mixeng.h" |
|||
|
|||
#define IN_T int8_t |
|||
#define IN_MIN CHAR_MIN |
|||
#define IN_MAX CHAR_MAX |
|||
#define SIGNED |
|||
#include "mixeng_template.h" |
|||
#undef SIGNED |
|||
#undef IN_MAX |
|||
#undef IN_MIN |
|||
#undef IN_T |
|||
|
|||
#define IN_T uint8_t |
|||
#define IN_MIN 0 |
|||
#define IN_MAX UCHAR_MAX |
|||
#include "mixeng_template.h" |
|||
#undef IN_MAX |
|||
#undef IN_MIN |
|||
#undef IN_T |
|||
|
|||
#define IN_T int16_t |
|||
#define IN_MIN SHRT_MIN |
|||
#define IN_MAX SHRT_MAX |
|||
#define SIGNED |
|||
#include "mixeng_template.h" |
|||
#undef SIGNED |
|||
#undef IN_MAX |
|||
#undef IN_MIN |
|||
#undef IN_T |
|||
|
|||
#define IN_T uint16_t |
|||
#define IN_MIN 0 |
|||
#define IN_MAX USHRT_MAX |
|||
#include "mixeng_template.h" |
|||
#undef IN_MAX |
|||
#undef IN_MIN |
|||
#undef IN_T |
|||
|
|||
t_sample *mixeng_conv[2][2][2] = { |
|||
{ |
|||
{ |
|||
conv_uint8_t_to_mono, |
|||
conv_uint16_t_to_mono |
|||
}, |
|||
{ |
|||
conv_int8_t_to_mono, |
|||
conv_int16_t_to_mono |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
conv_uint8_t_to_stereo, |
|||
conv_uint16_t_to_stereo |
|||
}, |
|||
{ |
|||
conv_int8_t_to_stereo, |
|||
conv_int16_t_to_stereo |
|||
} |
|||
} |
|||
}; |
|||
|
|||
f_sample *mixeng_clip[2][2][2] = { |
|||
{ |
|||
{ |
|||
clip_uint8_t_from_mono, |
|||
clip_uint16_t_from_mono |
|||
}, |
|||
{ |
|||
clip_int8_t_from_mono, |
|||
clip_int16_t_from_mono |
|||
} |
|||
}, |
|||
{ |
|||
{ |
|||
clip_uint8_t_from_stereo, |
|||
clip_uint16_t_from_stereo |
|||
}, |
|||
{ |
|||
clip_int8_t_from_stereo, |
|||
clip_int16_t_from_stereo |
|||
} |
|||
} |
|||
}; |
|||
|
|||
/*
|
|||
* August 21, 1998 |
|||
* Copyright 1998 Fabrice Bellard. |
|||
* |
|||
* [Rewrote completly the code of Lance Norskog And Sundry |
|||
* 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. |
|||
*/ |
|||
|
|||
/*
|
|||
* Sound Tools rate change effect file. |
|||
*/ |
|||
/*
|
|||
* Linear Interpolation. |
|||
* |
|||
* The use of fractional increment allows us to use no buffer. It |
|||
* avoid the problems at the end of the buffer we had with the old |
|||
* method which stored a possibly big buffer of size |
|||
* lcm(in_rate,out_rate). |
|||
* |
|||
* Limited to 16 bit samples and sampling frequency <= 65535 Hz. If |
|||
* the input & output frequencies are equal, a delay of one sample is |
|||
* introduced. Limited to processing 32-bit count worth of samples. |
|||
* |
|||
* 1 << FRAC_BITS evaluating to zero in several places. Changed with |
|||
* an (unsigned long) cast to make it safe. MarkMLl 2/1/99 |
|||
*/ |
|||
|
|||
/* Private data */ |
|||
typedef struct ratestuff { |
|||
uint64_t opos; |
|||
uint64_t opos_inc; |
|||
uint32_t ipos; /* position in the input stream (integer) */ |
|||
st_sample_t ilast; /* last sample in the input stream */ |
|||
} *rate_t; |
|||
|
|||
/*
|
|||
* Prepare processing. |
|||
*/ |
|||
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);
|
|||
} |
|||
|
|||
rate->opos = 0; |
|||
|
|||
/* increment */ |
|||
rate->opos_inc = (inrate * ((int64_t) UINT_MAX)) / outrate; |
|||
|
|||
rate->ipos = 0; |
|||
rate->ilast.l = 0; |
|||
rate->ilast.r = 0; |
|||
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; |
|||
|
|||
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; |
|||
} |
|||
|
|||
void st_rate_stop (void *opaque) |
|||
{ |
|||
qemu_free (opaque); |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/*
|
|||
* QEMU Mixing engine header |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
#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); |
|||
typedef struct { int64_t l; int64_t r; } st_sample_t; |
|||
|
|||
extern t_sample *mixeng_conv[2][2][2]; |
|||
extern f_sample *mixeng_clip[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_stop (void *opaque); |
|||
|
|||
#endif /* mixeng.h */ |
|||
@ -0,0 +1,111 @@ |
|||
/*
|
|||
* QEMU Mixing engine |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
|
|||
/*
|
|||
* Tusen tack till Mike Nordell |
|||
* dec++'ified by Dscho |
|||
*/ |
|||
|
|||
#ifdef SIGNED |
|||
#define HALFT IN_MAX |
|||
#define HALF IN_MAX |
|||
#else |
|||
#define HALFT ((IN_MAX)>>1) |
|||
#define HALF HALFT |
|||
#endif |
|||
|
|||
static int64_t inline glue(conv_,IN_T) (IN_T v) |
|||
{ |
|||
#ifdef SIGNED |
|||
return (INT_MAX*(int64_t)v)/HALF; |
|||
#else |
|||
return (INT_MAX*((int64_t)v-HALFT))/HALF; |
|||
#endif |
|||
} |
|||
|
|||
static IN_T inline glue(clip_,IN_T) (int64_t v) |
|||
{ |
|||
if (v >= INT_MAX) |
|||
return IN_MAX; |
|||
else if (v < -INT_MAX) |
|||
return IN_MIN; |
|||
|
|||
#ifdef SIGNED |
|||
return (IN_T) (v*HALF/INT_MAX); |
|||
#else |
|||
return (IN_T) (v+INT_MAX/2)*HALF/INT_MAX; |
|||
#endif |
|||
} |
|||
|
|||
static void glue(glue(conv_,IN_T),_to_stereo) (void *dst, const void *src, |
|||
int samples) |
|||
{ |
|||
st_sample_t *out = (st_sample_t *) dst; |
|||
IN_T *in = (IN_T *) src; |
|||
while (samples--) { |
|||
out->l = glue(conv_,IN_T) (*in++); |
|||
out->r = glue(conv_,IN_T) (*in++); |
|||
out += 1; |
|||
} |
|||
} |
|||
|
|||
static void glue(glue(conv_,IN_T),_to_mono) (void *dst, const void *src, |
|||
int samples) |
|||
{ |
|||
st_sample_t *out = (st_sample_t *) dst; |
|||
IN_T *in = (IN_T *) src; |
|||
while (samples--) { |
|||
out->l = glue(conv_,IN_T) (in[0]); |
|||
out->r = out->l; |
|||
out += 1; |
|||
in += 1; |
|||
} |
|||
} |
|||
|
|||
static void glue(glue(clip_,IN_T),_from_stereo) (void *dst, const void *src, |
|||
int samples) |
|||
{ |
|||
st_sample_t *in = (st_sample_t *) src; |
|||
IN_T *out = (IN_T *) dst; |
|||
while (samples--) { |
|||
*out++ = glue(clip_,IN_T) (in->l); |
|||
*out++ = glue(clip_,IN_T) (in->r); |
|||
in += 1; |
|||
} |
|||
} |
|||
|
|||
static void glue(glue(clip_,IN_T),_from_mono) (void *dst, const void *src, |
|||
int samples) |
|||
{ |
|||
st_sample_t *in = (st_sample_t *) src; |
|||
IN_T *out = (IN_T *) dst; |
|||
while (samples--) { |
|||
*out++ = glue(clip_,IN_T) (in->l + in->r); |
|||
in += 1; |
|||
} |
|||
} |
|||
|
|||
#undef HALF |
|||
#undef HALFT |
|||
|
|||
@ -0,0 +1,466 @@ |
|||
/*
|
|||
* QEMU OSS audio output driver |
|||
* |
|||
* Copyright (c) 2003-2004 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. |
|||
*/ |
|||
|
|||
/* Temporary kludge */ |
|||
#if defined __linux__ || (defined _BSD && !defined __APPLE__) |
|||
#include <assert.h> |
|||
#include "vl.h" |
|||
|
|||
#include <sys/mman.h> |
|||
#include <sys/types.h> |
|||
#include <sys/ioctl.h> |
|||
#include <sys/soundcard.h> |
|||
|
|||
#define AUDIO_CAP "oss" |
|||
#include "audio/audio.h" |
|||
#include "audio/ossaudio.h" |
|||
|
|||
#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) |
|||
|
|||
static struct { |
|||
int try_mmap; |
|||
int nfrags; |
|||
int fragsize; |
|||
const char *dspname; |
|||
} conf = { |
|||
.try_mmap = 0, |
|||
.nfrags = 4, |
|||
.fragsize = 4096, |
|||
.dspname = "/dev/dsp" |
|||
}; |
|||
|
|||
struct oss_params { |
|||
int freq; |
|||
audfmt_e fmt; |
|||
int nchannels; |
|||
int nfrags; |
|||
int fragsize; |
|||
}; |
|||
|
|||
static int oss_hw_write (SWVoice *sw, void *buf, int len) |
|||
{ |
|||
return pcm_hw_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; |
|||
default: |
|||
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); |
|||
exit (EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
static int oss_to_audfmt (int fmt) |
|||
{ |
|||
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; |
|||
default: |
|||
dolog ("Internal logic error: Unrecognized OSS audio format %d\n" |
|||
"Aborting\n", |
|||
fmt); |
|||
exit (EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
#ifdef DEBUG_PCM |
|||
static void oss_dump_pcm_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 ("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); |
|||
} |
|||
#endif |
|||
|
|||
static int oss_open (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; |
|||
|
|||
fd = open (dspname, O_RDWR | O_NONBLOCK); |
|||
if (-1 == fd) { |
|||
dolog ("Could not initialize audio hardware. Failed to open `%s':\n" |
|||
"Reason:%s\n", |
|||
dspname, |
|||
errstr ()); |
|||
return -1; |
|||
} |
|||
|
|||
freq = req->freq; |
|||
nchannels = req->nchannels; |
|||
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 ()); |
|||
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 ()); |
|||
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 ()); |
|||
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 ()); |
|||
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 ()); |
|||
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 ()); |
|||
goto err; |
|||
} |
|||
|
|||
obt->fmt = fmt; |
|||
obt->nchannels = nchannels; |
|||
obt->freq = freq; |
|||
obt->nfrags = abinfo.fragstotal; |
|||
obt->fragsize = abinfo.fragsize; |
|||
*pfd = fd; |
|||
|
|||
if ((req->fmt != obt->fmt) || |
|||
(req->nchannels != obt->nchannels) || |
|||
(req->freq != obt->freq) || |
|||
(req->fragsize != obt->fragsize) || |
|||
(req->nfrags != obt->nfrags)) { |
|||
#ifdef DEBUG_PCM |
|||
dolog ("Audio parameters mismatch\n"); |
|||
oss_dump_pcm_info (req, obt); |
|||
#endif |
|||
} |
|||
|
|||
#ifdef DEBUG_PCM |
|||
oss_dump_pcm_info (req, obt); |
|||
#endif |
|||
return 0; |
|||
|
|||
err: |
|||
close (fd); |
|||
return -1; |
|||
} |
|||
|
|||
static void oss_hw_run (HWVoice *hw) |
|||
{ |
|||
OSSVoice *oss = (OSSVoice *) hw; |
|||
int err, rpos, live, decr; |
|||
int samples; |
|||
uint8_t *dst; |
|||
st_sample_t *src; |
|||
struct audio_buf_info abinfo; |
|||
struct count_info cntinfo; |
|||
|
|||
live = pcm_hw_get_live (hw); |
|||
if (live <= 0) |
|||
return; |
|||
|
|||
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; |
|||
} |
|||
|
|||
if (cntinfo.ptr == oss->old_optr) { |
|||
if (abs (hw->samples - live) < 64) |
|||
dolog ("overrun\n"); |
|||
return; |
|||
} |
|||
|
|||
if (cntinfo.ptr > oss->old_optr) { |
|||
bytes = cntinfo.ptr - oss->old_optr; |
|||
} |
|||
else { |
|||
bytes = hw->bufsize + cntinfo.ptr - oss->old_optr; |
|||
} |
|||
|
|||
decr = audio_MIN (bytes >> hw->shift, live); |
|||
} |
|||
else { |
|||
err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo); |
|||
if (err < 0) { |
|||
dolog ("SNDCTL_DSP_GETOSPACE failed\nReason: %s\n", errstr ()); |
|||
return; |
|||
} |
|||
|
|||
decr = audio_MIN (abinfo.bytes >> hw->shift, live); |
|||
if (decr <= 0) |
|||
return; |
|||
} |
|||
|
|||
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)); |
|||
dst = advance (oss->pcm_buf, rpos << hw->shift); |
|||
|
|||
hw->clip (dst, src, convert_samples); |
|||
if (!oss->mmapped) { |
|||
int written; |
|||
|
|||
written = write (oss->fd, dst, convert_samples << hw->shift); |
|||
/* XXX: follow errno recommendations ? */ |
|||
if (written == -1) { |
|||
dolog ("Failed to write audio\nReason: %s\n", errstr ()); |
|||
continue; |
|||
} |
|||
|
|||
if (written != convert_samples << hw->shift) { |
|||
int wsamples = written >> hw->shift; |
|||
int wbytes = wsamples << hw->shift; |
|||
if (wbytes != written) { |
|||
dolog ("Unaligned write %d, %d\n", wbytes, written); |
|||
} |
|||
memset (src, 0, wbytes); |
|||
decr -= samples; |
|||
rpos = (rpos + wsamples) % hw->samples; |
|||
break; |
|||
} |
|||
} |
|||
memset (src, 0, convert_samples * sizeof (st_sample_t)); |
|||
|
|||
rpos = (rpos + convert_samples) % hw->samples; |
|||
samples -= convert_samples; |
|||
} |
|||
if (oss->mmapped) { |
|||
oss->old_optr = cntinfo.ptr; |
|||
} |
|||
|
|||
pcm_hw_dec_live (hw, decr); |
|||
hw->rpos = rpos; |
|||
} |
|||
|
|||
static void oss_hw_fini (HWVoice *hw) |
|||
{ |
|||
int err; |
|||
OSSVoice *oss = (OSSVoice *) 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; |
|||
|
|||
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 ()); |
|||
} |
|||
} |
|||
else { |
|||
qemu_free (oss->pcm_buf); |
|||
} |
|||
oss->pcm_buf = NULL; |
|||
} |
|||
} |
|||
|
|||
static int oss_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
|||
{ |
|||
OSSVoice *oss = (OSSVoice *) hw; |
|||
struct oss_params req, obt; |
|||
|
|||
assert (!oss->fd); |
|||
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)) |
|||
return -1; |
|||
|
|||
hw->freq = obt.freq; |
|||
hw->fmt = oss_to_audfmt (obt.fmt); |
|||
hw->nchannels = obt.nchannels; |
|||
|
|||
oss->nfrags = obt.nfrags; |
|||
oss->fragsize = obt.fragsize; |
|||
hw->bufsize = obt.nfrags * obt.fragsize; |
|||
|
|||
oss->mmapped = 0; |
|||
if (conf.try_mmap) { |
|||
oss->pcm_buf = mmap (0, hw->bufsize, PROT_READ | PROT_WRITE, |
|||
MAP_SHARED, oss->fd, 0); |
|||
if (oss->pcm_buf == MAP_FAILED) { |
|||
dolog ("Failed to mmap OSS device\nReason: %s\n", |
|||
errstr ()); |
|||
} |
|||
|
|||
for (;;) { |
|||
int err; |
|||
int trig = 0; |
|||
if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { |
|||
dolog ("SNDCTL_DSP_SETTRIGGER 0 failed\nReason: %s\n", |
|||
errstr ()); |
|||
goto fail; |
|||
} |
|||
|
|||
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 ()); |
|||
goto fail; |
|||
} |
|||
oss->mmapped = 1; |
|||
break; |
|||
|
|||
fail: |
|||
err = munmap (oss->pcm_buf, hw->bufsize); |
|||
if (err) { |
|||
dolog ("Failed to unmap OSS device\nReason: %s\n", |
|||
errstr ()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!oss->mmapped) { |
|||
oss->pcm_buf = qemu_mallocz (hw->bufsize); |
|||
if (!oss->pcm_buf) { |
|||
close (oss->fd); |
|||
oss->fd = -1; |
|||
return -1; |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
static int oss_hw_ctl (HWVoice *hw, int cmd, ...) |
|||
{ |
|||
int trig; |
|||
OSSVoice *oss = (OSSVoice *) hw; |
|||
|
|||
if (!oss->mmapped) |
|||
return 0; |
|||
|
|||
switch (cmd) { |
|||
case VOICE_ENABLE: |
|||
ldebug ("enabling voice\n"); |
|||
pcm_hw_clear (hw, 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 ()); |
|||
return -1; |
|||
} |
|||
break; |
|||
|
|||
case VOICE_DISABLE: |
|||
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 ()); |
|||
return -1; |
|||
} |
|||
break; |
|||
} |
|||
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) |
|||
{ |
|||
} |
|||
|
|||
struct pcm_ops oss_pcm_ops = { |
|||
oss_hw_init, |
|||
oss_hw_fini, |
|||
oss_hw_run, |
|||
oss_hw_write, |
|||
oss_hw_ctl |
|||
}; |
|||
|
|||
struct audio_output_driver oss_output_driver = { |
|||
"oss", |
|||
oss_audio_init, |
|||
oss_audio_fini, |
|||
&oss_pcm_ops, |
|||
1, |
|||
INT_MAX, |
|||
sizeof (OSSVoice) |
|||
}; |
|||
#endif |
|||
@ -0,0 +1,40 @@ |
|||
/*
|
|||
* QEMU OSS audio output driver header |
|||
* |
|||
* Copyright (c) 2003-2004 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. |
|||
*/ |
|||
#ifndef QEMU_OSSAUDIO_H |
|||
#define QEMU_OSSAUDIO_H |
|||
|
|||
typedef struct OSSVoice { |
|||
struct HWVoice hw; |
|||
void *pcm_buf; |
|||
int fd; |
|||
int nfrags; |
|||
int fragsize; |
|||
int mmapped; |
|||
int old_optr; |
|||
} OSSVoice; |
|||
|
|||
extern struct pcm_ops oss_pcm_ops; |
|||
extern struct audio_output_driver oss_output_driver; |
|||
|
|||
#endif /* ossaudio.h */ |
|||
@ -0,0 +1,323 @@ |
|||
/*
|
|||
* QEMU SDL audio output driver |
|||
* |
|||
* Copyright (c) 2004 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 <SDL/SDL.h> |
|||
#include <SDL/SDL_thread.h> |
|||
#include "vl.h" |
|||
|
|||
#define AUDIO_CAP "sdl" |
|||
#include "audio/audio.h" |
|||
#include "audio/sdlaudio.h" |
|||
|
|||
#define QC_SDL_SAMPLES "QEMU_SDL_SAMPLES" |
|||
|
|||
#define errstr() SDL_GetError () |
|||
|
|||
static struct { |
|||
int nb_samples; |
|||
} conf = { |
|||
1024 |
|||
}; |
|||
|
|||
struct SDLAudioState { |
|||
int exit; |
|||
SDL_mutex *mutex; |
|||
SDL_sem *sem; |
|||
int initialized; |
|||
} glob_sdl; |
|||
typedef struct SDLAudioState SDLAudioState; |
|||
|
|||
static void sdl_hw_run (HWVoice *hw) |
|||
{ |
|||
(void) hw; |
|||
} |
|||
|
|||
static int sdl_lock (SDLAudioState *s) |
|||
{ |
|||
if (SDL_LockMutex (s->mutex)) { |
|||
dolog ("SDL_LockMutex failed\nReason: %s\n", errstr ()); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int sdl_unlock (SDLAudioState *s) |
|||
{ |
|||
if (SDL_UnlockMutex (s->mutex)) { |
|||
dolog ("SDL_UnlockMutex failed\nReason: %s\n", errstr ()); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int sdl_post (SDLAudioState *s) |
|||
{ |
|||
if (SDL_SemPost (s->sem)) { |
|||
dolog ("SDL_SemPost failed\nReason: %s\n", errstr ()); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int sdl_wait (SDLAudioState *s) |
|||
{ |
|||
if (SDL_SemWait (s->sem)) { |
|||
dolog ("SDL_SemWait failed\nReason: %s\n", errstr ()); |
|||
return -1; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
static int sdl_unlock_and_post (SDLAudioState *s) |
|||
{ |
|||
if (sdl_unlock (s)) |
|||
return -1; |
|||
|
|||
return sdl_post (s); |
|||
} |
|||
|
|||
static int sdl_hw_write (SWVoice *sw, void *buf, int len) |
|||
{ |
|||
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; |
|||
default: |
|||
dolog ("Internal logic error: Bad audio format %d\nAborting\n", fmt); |
|||
exit (EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
static int sdl_to_audfmt (int fmt) |
|||
{ |
|||
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; |
|||
default: |
|||
dolog ("Internal logic error: Unrecognized SDL audio format %d\n" |
|||
"Aborting\n", fmt); |
|||
exit (EXIT_FAILURE); |
|||
} |
|||
} |
|||
|
|||
static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) |
|||
{ |
|||
int status; |
|||
|
|||
status = SDL_OpenAudio (req, obt); |
|||
if (status) { |
|||
dolog ("SDL_OpenAudio failed\nReason: %s\n", errstr ()); |
|||
} |
|||
return status; |
|||
} |
|||
|
|||
static void sdl_close (SDLAudioState *s) |
|||
{ |
|||
if (s->initialized) { |
|||
sdl_lock (s); |
|||
s->exit = 1; |
|||
sdl_unlock_and_post (s); |
|||
SDL_PauseAudio (1); |
|||
SDL_CloseAudio (); |
|||
s->initialized = 0; |
|||
} |
|||
} |
|||
|
|||
static void sdl_callback (void *opaque, Uint8 *buf, int len) |
|||
{ |
|||
SDLVoice *sdl = opaque; |
|||
SDLAudioState *s = &glob_sdl; |
|||
HWVoice *hw = &sdl->hw; |
|||
int samples = len >> hw->shift; |
|||
|
|||
if (s->exit) { |
|||
return; |
|||
} |
|||
|
|||
while (samples) { |
|||
int to_mix, live, decr; |
|||
|
|||
/* dolog ("in callback samples=%d\n", samples); */ |
|||
sdl_wait (s); |
|||
if (s->exit) { |
|||
return; |
|||
} |
|||
|
|||
sdl_lock (s); |
|||
live = pcm_hw_get_live (hw); |
|||
if (live <= 0) |
|||
goto again; |
|||
|
|||
/* dolog ("in callback live=%d\n", live); */ |
|||
to_mix = audio_MIN (samples, live); |
|||
decr = to_mix; |
|||
while (to_mix) { |
|||
int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); |
|||
st_sample_t *src = hw->mix_buf + hw->rpos; |
|||
|
|||
/* 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; |
|||
to_mix -= chunk; |
|||
buf += chunk << hw->shift; |
|||
} |
|||
samples -= decr; |
|||
pcm_hw_dec_live (hw, decr); |
|||
|
|||
again: |
|||
sdl_unlock (s); |
|||
} |
|||
/* dolog ("done len=%d\n", len); */ |
|||
} |
|||
|
|||
static void sdl_hw_fini (HWVoice *hw) |
|||
{ |
|||
ldebug ("sdl_hw_fini %d fixed=%d\n", |
|||
glob_sdl.initialized, audio_conf.fixed_format); |
|||
sdl_close (&glob_sdl); |
|||
} |
|||
|
|||
static int sdl_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
|||
{ |
|||
SDLVoice *sdl = (SDLVoice *) 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_conf.fixed_format); |
|||
|
|||
if (nchannels != 2) { |
|||
dolog ("Bogus channel count %d\n", nchannels); |
|||
return -1; |
|||
} |
|||
|
|||
req.freq = freq; |
|||
req.format = AUD_to_sdlfmt (fmt, &shift); |
|||
req.channels = nchannels; |
|||
req.samples = conf.nb_samples; |
|||
shift <<= nchannels == 2; |
|||
|
|||
req.callback = sdl_callback; |
|||
req.userdata = sdl; |
|||
|
|||
if (sdl_open (&req, &obt)) |
|||
return -1; |
|||
|
|||
hw->freq = obt.freq; |
|||
hw->fmt = sdl_to_audfmt (obt.format); |
|||
hw->nchannels = obt.channels; |
|||
hw->bufsize = obt.samples << shift; |
|||
|
|||
s->initialized = 1; |
|||
s->exit = 0; |
|||
SDL_PauseAudio (0); |
|||
return 0; |
|||
} |
|||
|
|||
static int sdl_hw_ctl (HWVoice *hw, int cmd, ...) |
|||
{ |
|||
(void) hw; |
|||
|
|||
switch (cmd) { |
|||
case VOICE_ENABLE: |
|||
SDL_PauseAudio (0); |
|||
break; |
|||
|
|||
case VOICE_DISABLE: |
|||
SDL_PauseAudio (1); |
|||
break; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
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 ()); |
|||
return NULL; |
|||
} |
|||
|
|||
s->mutex = SDL_CreateMutex (); |
|||
if (!s->mutex) { |
|||
dolog ("Failed to create SDL mutex\nReason: %s\n", errstr ()); |
|||
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_DestroyMutex (s->mutex); |
|||
SDL_QuitSubSystem (SDL_INIT_AUDIO); |
|||
return NULL; |
|||
} |
|||
|
|||
return s; |
|||
} |
|||
|
|||
static void sdl_audio_fini (void *opaque) |
|||
{ |
|||
SDLAudioState *s = opaque; |
|||
sdl_close (s); |
|||
SDL_DestroySemaphore (s->sem); |
|||
SDL_DestroyMutex (s->mutex); |
|||
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 |
|||
}; |
|||
|
|||
struct audio_output_driver sdl_output_driver = { |
|||
"sdl", |
|||
sdl_audio_init, |
|||
sdl_audio_fini, |
|||
&sdl_pcm_ops, |
|||
1, |
|||
1, |
|||
sizeof (SDLVoice) |
|||
}; |
|||
@ -0,0 +1,34 @@ |
|||
/*
|
|||
* QEMU SDL audio output driver header |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
#ifndef QEMU_SDLAUDIO_H |
|||
#define QEMU_SDLAUDIO_H |
|||
|
|||
typedef struct SDLVoice { |
|||
struct HWVoice hw; |
|||
} SDLVoice; |
|||
|
|||
extern struct pcm_ops sdl_pcm_ops; |
|||
extern struct audio_output_driver sdl_output_driver; |
|||
|
|||
#endif /* sdlaudio.h */ |
|||
@ -0,0 +1,200 @@ |
|||
/*
|
|||
* QEMU WAV audio output driver |
|||
* |
|||
* Copyright (c) 2004 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 "vl.h" |
|||
|
|||
#define AUDIO_CAP "wav" |
|||
#include "audio/audio.h" |
|||
#include "audio/wavaudio.h" |
|||
|
|||
static struct { |
|||
const char *wav_path; |
|||
} conf = { |
|||
.wav_path = "qemu.wav" |
|||
}; |
|||
|
|||
static void wav_hw_run (HWVoice *hw) |
|||
{ |
|||
WAVVoice *wav = (WAVVoice *) 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; |
|||
wav->old_ticks = now; |
|||
|
|||
if (bytes > INT_MAX) |
|||
samples = INT_MAX >> hw->shift; |
|||
else |
|||
samples = bytes >> hw->shift; |
|||
|
|||
live = pcm_hw_get_live (hw); |
|||
if (live <= 0) |
|||
return; |
|||
|
|||
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)); |
|||
dst = advance (wav->pcm_buf, rpos << hw->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)); |
|||
|
|||
rpos = (rpos + convert_samples) % hw->samples; |
|||
samples -= convert_samples; |
|||
wav->total_samples += convert_samples; |
|||
} |
|||
|
|||
pcm_hw_dec_live (hw, decr); |
|||
hw->rpos = rpos; |
|||
} |
|||
|
|||
static int wav_hw_write (SWVoice *sw, void *buf, int len) |
|||
{ |
|||
return pcm_hw_write (sw, buf, len); |
|||
} |
|||
|
|||
|
|||
/* VICE code: Store number as little endian. */ |
|||
static void le_store (uint8_t *buf, uint32_t val, int len) |
|||
{ |
|||
int i; |
|||
for (i = 0; i < len; i++) { |
|||
buf[i] = (uint8_t) (val & 0xff); |
|||
val >>= 8; |
|||
} |
|||
} |
|||
|
|||
static int wav_hw_init (HWVoice *hw, int freq, int nchannels, audfmt_e fmt) |
|||
{ |
|||
WAVVoice *wav = (WAVVoice *) hw; |
|||
int bits16 = 0, stereo = audio_state.fixed_channels == 2; |
|||
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, |
|||
0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, |
|||
0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 |
|||
}; |
|||
|
|||
switch (audio_state.fixed_fmt) { |
|||
case AUD_FMT_S8: |
|||
case AUD_FMT_U8: |
|||
break; |
|||
|
|||
case AUD_FMT_S16: |
|||
case AUD_FMT_U16: |
|||
bits16 = 1; |
|||
break; |
|||
} |
|||
|
|||
hdr[34] = bits16 ? 0x10 : 0x08; |
|||
hw->freq = 44100; |
|||
hw->nchannels = stereo ? 2 : 1; |
|||
hw->fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; |
|||
hw->bufsize = 4096; |
|||
wav->pcm_buf = qemu_mallocz (hw->bufsize); |
|||
if (!wav->pcm_buf) |
|||
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); |
|||
|
|||
wav->f = fopen (conf.wav_path, "wb"); |
|||
if (!wav->f) { |
|||
dolog ("failed to open wave file `%s'\nReason: %s\n", |
|||
conf.wav_path, strerror (errno)); |
|||
return -1; |
|||
} |
|||
|
|||
qemu_put_buffer (wav->f, hdr, sizeof (hdr)); |
|||
return 0; |
|||
} |
|||
|
|||
static void wav_hw_fini (HWVoice *hw) |
|||
{ |
|||
WAVVoice *wav = (WAVVoice *) hw; |
|||
int stereo = hw->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) |
|||
return; |
|||
|
|||
le_store (rlen, rifflen, 4); |
|||
le_store (dlen, datalen, 4); |
|||
|
|||
qemu_fseek (wav->f, 4, SEEK_SET); |
|||
qemu_put_buffer (wav->f, rlen, 4); |
|||
|
|||
qemu_fseek (wav->f, 32, SEEK_CUR); |
|||
qemu_put_buffer (wav->f, dlen, 4); |
|||
|
|||
fclose (wav->f); |
|||
wav->f = NULL; |
|||
} |
|||
|
|||
static int wav_hw_ctl (HWVoice *hw, int cmd, ...) |
|||
{ |
|||
(void) hw; |
|||
(void) cmd; |
|||
return 0; |
|||
} |
|||
|
|||
static void *wav_audio_init (void) |
|||
{ |
|||
return &conf; |
|||
} |
|||
|
|||
static void wav_audio_fini (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_output_driver wav_output_driver = { |
|||
"wav", |
|||
wav_audio_init, |
|||
wav_audio_fini, |
|||
&wav_pcm_ops, |
|||
1, |
|||
1, |
|||
sizeof (WAVVoice) |
|||
}; |
|||
@ -0,0 +1,38 @@ |
|||
/*
|
|||
* QEMU WAV audio output driver header |
|||
* |
|||
* Copyright (c) 2004 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. |
|||
*/ |
|||
#ifndef QEMU_WAVAUDIO_H |
|||
#define QEMU_WAVAUDIO_H |
|||
|
|||
typedef struct WAVVoice { |
|||
struct HWVoice hw; |
|||
QEMUFile *f; |
|||
int64_t old_ticks; |
|||
void *pcm_buf; |
|||
int total_samples; |
|||
} WAVVoice; |
|||
|
|||
extern struct pcm_ops wav_pcm_ops; |
|||
extern struct audio_output_driver wav_output_driver; |
|||
|
|||
#endif /* wavaudio.h */ |
|||
@ -0,0 +1,309 @@ |
|||
/*
|
|||
* QEMU Adlib emulation |
|||
* |
|||
* Copyright (c) 2004 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 "vl.h" |
|||
|
|||
#define AUDIO_CAP "adlib" |
|||
#include "audio/audio.h" |
|||
|
|||
#ifdef USE_YMF262 |
|||
#define HAS_YMF262 1 |
|||
#include "ymf262.h" |
|||
void YMF262UpdateOneQEMU(int which, INT16 *dst, int length); |
|||
#define SHIFT 2 |
|||
#else |
|||
#include "fmopl.h" |
|||
#define SHIFT 1 |
|||
#endif |
|||
|
|||
#ifdef _WIN32 |
|||
#include <windows.h> |
|||
#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) \ |
|||
void name (void *opaque, uint32_t nport, uint32_t val) |
|||
|
|||
static struct { |
|||
int port; |
|||
int freq; |
|||
} conf = {0x220, 44100}; |
|||
|
|||
typedef struct { |
|||
int enabled; |
|||
int active; |
|||
int cparam; |
|||
int64_t ticks; |
|||
int bufpos; |
|||
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 |
|||
FM_OPL *opl; |
|||
#endif |
|||
} AdlibState; |
|||
|
|||
static AdlibState adlib; |
|||
|
|||
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); |
|||
|
|||
#ifdef USE_YMF262 |
|||
status = YMF262Write (0, a, val); |
|||
#else |
|||
status = OPLWrite (s->opl, a, val); |
|||
#endif |
|||
} |
|||
|
|||
static IO_READ_PROTO(adlib_read) |
|||
{ |
|||
AdlibState *s = opaque; |
|||
uint8_t data; |
|||
int a = nport & 3; |
|||
|
|||
#ifdef USE_YMF262 |
|||
(void) s; |
|||
data = YMF262Read (0, a); |
|||
#else |
|||
data = OPLRead (s->opl, a); |
|||
#endif |
|||
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) |
|||
{ |
|||
AdlibState *s = &adlib; |
|||
if (interval_Sec == 0.0) { |
|||
qemu_del_timer (s->opl_ts); |
|||
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 (); |
|||
} |
|||
|
|||
static int write_audio (AdlibState *s, int samples) |
|||
{ |
|||
int net = 0; |
|||
int ss = samples; |
|||
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) |
|||
break; |
|||
} |
|||
if (net > ss) { |
|||
dolog ("WARNING: net > ss\n"); |
|||
} |
|||
return net; |
|||
} |
|||
|
|||
static void timer (void *opaque) |
|||
{ |
|||
AdlibState *s = opaque; |
|||
int elapsed, samples, net = 0; |
|||
|
|||
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; |
|||
} |
|||
s->pos = 0; |
|||
|
|||
elapsed = AUD_calc_elapsed (s->voice); |
|||
if (!elapsed) |
|||
goto reset2; |
|||
|
|||
/* elapsed = AUD_get_free (s->voice); */ |
|||
samples = elapsed >> SHIFT; |
|||
if (!samples) |
|||
goto reset2; |
|||
|
|||
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) |
|||
goto reset2; |
|||
|
|||
#ifdef USE_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; |
|||
|
|||
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; |
|||
} |
|||
|
|||
static void Adlib_fini (AdlibState *s) |
|||
{ |
|||
#ifdef USE_YMF262 |
|||
YMF262Shutdown (); |
|||
#else |
|||
if (s->opl) { |
|||
OPLDestroy (s->opl); |
|||
s->opl = NULL; |
|||
} |
|||
#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 |
|||
|
|||
s->active = 0; |
|||
s->enabled = 0; |
|||
} |
|||
|
|||
void Adlib_init (void) |
|||
{ |
|||
AdlibState *s = &adlib; |
|||
|
|||
memset (s, 0, sizeof (*s)); |
|||
|
|||
#ifdef USE_YMF262 |
|||
if (YMF262Init (1, 14318180, conf.freq)) { |
|||
dolog ("YMF262Init %d failed\n", conf.freq); |
|||
return; |
|||
} |
|||
else { |
|||
YMF262SetTimerHandler (0, YMF262TimerHandler, 0); |
|||
s->enabled = 1; |
|||
} |
|||
#else |
|||
s->opl = OPLCreate (OPL_TYPE_YM3812, 3579545, conf.freq); |
|||
if (!s->opl) { |
|||
dolog ("OPLCreate %d failed\n", conf.freq); |
|||
return; |
|||
} |
|||
else { |
|||
OPLSetTimerHandler (s->opl, YMF262TimerHandler, 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); |
|||
if (!s->voice) { |
|||
Adlib_fini (s); |
|||
return; |
|||
} |
|||
|
|||
s->bytes_per_second = conf.freq << SHIFT; |
|||
s->samples = AUD_get_buffer_size (s->voice) >> SHIFT; |
|||
s->mixbuf = qemu_mallocz (s->samples << SHIFT); |
|||
|
|||
if (!s->mixbuf) { |
|||
dolog ("not enough memory for adlib mixing buffer (%d)\n", |
|||
s->samples << SHIFT); |
|||
Adlib_fini (s); |
|||
return; |
|||
} |
|||
register_ioport_read (0x388, 4, 1, adlib_read, s); |
|||
register_ioport_write (0x388, 4, 1, adlib_write, s); |
|||
|
|||
register_ioport_read (conf.port, 4, 1, adlib_read, s); |
|||
register_ioport_write (conf.port, 4, 1, adlib_write, s); |
|||
|
|||
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); |
|||
} |
|||
File diff suppressed because it is too large
@ -0,0 +1,174 @@ |
|||
#ifndef __FMOPL_H_ |
|||
#define __FMOPL_H_ |
|||
|
|||
/* --- select emulation chips --- */ |
|||
#define BUILD_YM3812 (HAS_YM3812) |
|||
//#define BUILD_YM3526 (HAS_YM3526)
|
|||
//#define BUILD_Y8950 (HAS_Y8950)
|
|||
|
|||
/* --- system optimize --- */ |
|||
/* select bit size of output : 8 or 16 */ |
|||
#define OPL_OUTPUT_BIT 16 |
|||
|
|||
/* compiler dependence */ |
|||
#ifndef OSD_CPU_H |
|||
#define OSD_CPU_H |
|||
typedef unsigned char UINT8; /* unsigned 8bit */ |
|||
typedef unsigned short UINT16; /* unsigned 16bit */ |
|||
typedef unsigned int UINT32; /* unsigned 32bit */ |
|||
typedef signed char INT8; /* signed 8bit */ |
|||
typedef signed short INT16; /* signed 16bit */ |
|||
typedef signed int INT32; /* signed 32bit */ |
|||
#endif |
|||
|
|||
#if (OPL_OUTPUT_BIT==16) |
|||
typedef INT16 OPLSAMPLE; |
|||
#endif |
|||
#if (OPL_OUTPUT_BIT==8) |
|||
typedef unsigned char OPLSAMPLE; |
|||
#endif |
|||
|
|||
|
|||
#if BUILD_Y8950 |
|||
#include "ymdeltat.h" |
|||
#endif |
|||
|
|||
typedef void (*OPL_TIMERHANDLER)(int channel,double interval_Sec); |
|||
typedef void (*OPL_IRQHANDLER)(int param,int irq); |
|||
typedef void (*OPL_UPDATEHANDLER)(int param,int min_interval_us); |
|||
typedef void (*OPL_PORTHANDLER_W)(int param,unsigned char data); |
|||
typedef unsigned char (*OPL_PORTHANDLER_R)(int param); |
|||
|
|||
/* !!!!! here is private section , do not access there member direct !!!!! */ |
|||
|
|||
#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ |
|||
#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ |
|||
#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ |
|||
#define OPL_TYPE_IO 0x08 /* I/O port */ |
|||
|
|||
/* Saving is necessary for member of the 'R' mark for suspend/resume */ |
|||
/* ---------- OPL one of slot ---------- */ |
|||
typedef struct fm_opl_slot { |
|||
INT32 TL; /* total level :TL << 8 */ |
|||
INT32 TLL; /* adjusted now TL */ |
|||
UINT8 KSR; /* key scale rate :(shift down bit) */ |
|||
INT32 *AR; /* attack rate :&AR_TABLE[AR<<2] */ |
|||
INT32 *DR; /* decay rate :&DR_TALBE[DR<<2] */ |
|||
INT32 SL; /* sustin level :SL_TALBE[SL] */ |
|||
INT32 *RR; /* release rate :&DR_TABLE[RR<<2] */ |
|||
UINT8 ksl; /* keyscale level :(shift down bits) */ |
|||
UINT8 ksr; /* key scale rate :kcode>>KSR */ |
|||
UINT32 mul; /* multiple :ML_TABLE[ML] */ |
|||
UINT32 Cnt; /* frequency count : */ |
|||
UINT32 Incr; /* frequency step : */ |
|||
/* envelope generator state */ |
|||
UINT8 eg_typ; /* envelope type flag */ |
|||
UINT8 evm; /* envelope phase */ |
|||
INT32 evc; /* envelope counter */ |
|||
INT32 eve; /* envelope counter end point */ |
|||
INT32 evs; /* envelope counter step */ |
|||
INT32 evsa; /* envelope step for AR :AR[ksr] */ |
|||
INT32 evsd; /* envelope step for DR :DR[ksr] */ |
|||
INT32 evsr; /* envelope step for RR :RR[ksr] */ |
|||
/* LFO */ |
|||
UINT8 ams; /* ams flag */ |
|||
UINT8 vib; /* vibrate flag */ |
|||
/* wave selector */ |
|||
INT32 **wavetable; |
|||
}OPL_SLOT; |
|||
|
|||
/* ---------- OPL one of channel ---------- */ |
|||
typedef struct fm_opl_channel { |
|||
OPL_SLOT SLOT[2]; |
|||
UINT8 CON; /* connection type */ |
|||
UINT8 FB; /* feed back :(shift down bit) */ |
|||
INT32 *connect1; /* slot1 output pointer */ |
|||
INT32 *connect2; /* slot2 output pointer */ |
|||
INT32 op1_out[2]; /* slot1 output for selfeedback */ |
|||
/* phase generator state */ |
|||
UINT32 block_fnum; /* block+fnum : */ |
|||
UINT8 kcode; /* key code : KeyScaleCode */ |
|||
UINT32 fc; /* Freq. Increment base */ |
|||
UINT32 ksl_base; /* KeyScaleLevel Base step */ |
|||
UINT8 keyon; /* key on/off flag */ |
|||
} OPL_CH; |
|||
|
|||
/* OPL state */ |
|||
typedef struct fm_opl_f { |
|||
UINT8 type; /* chip type */ |
|||
int clock; /* master clock (Hz) */ |
|||
int rate; /* sampling rate (Hz) */ |
|||
double freqbase; /* frequency base */ |
|||
double TimerBase; /* Timer base time (==sampling time) */ |
|||
UINT8 address; /* address register */ |
|||
UINT8 status; /* status flag */ |
|||
UINT8 statusmask; /* status mask */ |
|||
UINT32 mode; /* Reg.08 : CSM , notesel,etc. */ |
|||
/* Timer */ |
|||
int T[2]; /* timer counter */ |
|||
UINT8 st[2]; /* timer enable */ |
|||
/* FM channel slots */ |
|||
OPL_CH *P_CH; /* pointer of CH */ |
|||
int max_ch; /* maximum channel */ |
|||
/* Rythm sention */ |
|||
UINT8 rythm; /* Rythm mode , key flag */ |
|||
#if BUILD_Y8950 |
|||
/* Delta-T ADPCM unit (Y8950) */ |
|||
YM_DELTAT *deltat; /* DELTA-T ADPCM */ |
|||
#endif |
|||
/* Keyboard / I/O interface unit (Y8950) */ |
|||
UINT8 portDirection; |
|||
UINT8 portLatch; |
|||
OPL_PORTHANDLER_R porthandler_r; |
|||
OPL_PORTHANDLER_W porthandler_w; |
|||
int port_param; |
|||
OPL_PORTHANDLER_R keyboardhandler_r; |
|||
OPL_PORTHANDLER_W keyboardhandler_w; |
|||
int keyboard_param; |
|||
/* time tables */ |
|||
INT32 AR_TABLE[75]; /* atttack rate tables */ |
|||
INT32 DR_TABLE[75]; /* decay rate tables */ |
|||
UINT32 FN_TABLE[1024]; /* fnumber -> increment counter */ |
|||
/* LFO */ |
|||
INT32 *ams_table; |
|||
INT32 *vib_table; |
|||
INT32 amsCnt; |
|||
INT32 amsIncr; |
|||
INT32 vibCnt; |
|||
INT32 vibIncr; |
|||
/* wave selector enable flag */ |
|||
UINT8 wavesel; |
|||
/* external event callback handler */ |
|||
OPL_TIMERHANDLER TimerHandler; /* TIMER handler */ |
|||
int TimerParam; /* TIMER parameter */ |
|||
OPL_IRQHANDLER IRQHandler; /* IRQ handler */ |
|||
int IRQParam; /* IRQ parameter */ |
|||
OPL_UPDATEHANDLER UpdateHandler; /* stream update handler */ |
|||
int UpdateParam; /* stream update parameter */ |
|||
} FM_OPL; |
|||
|
|||
/* ---------- Generic interface section ---------- */ |
|||
#define OPL_TYPE_YM3526 (0) |
|||
#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) |
|||
#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) |
|||
|
|||
FM_OPL *OPLCreate(int type, int clock, int rate); |
|||
void OPLDestroy(FM_OPL *OPL); |
|||
void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER TimerHandler,int channelOffset); |
|||
void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,int param); |
|||
void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,int param); |
|||
/* Y8950 port handlers */ |
|||
void OPLSetPortHandler(FM_OPL *OPL,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,int param); |
|||
void OPLSetKeyboardHandler(FM_OPL *OPL,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,int param); |
|||
|
|||
void OPLResetChip(FM_OPL *OPL); |
|||
int OPLWrite(FM_OPL *OPL,int a,int v); |
|||
unsigned char OPLRead(FM_OPL *OPL,int a); |
|||
int OPLTimerOver(FM_OPL *OPL,int c); |
|||
|
|||
/* YM3626/YM3812 local section */ |
|||
void YM3812UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); |
|||
|
|||
void Y8950UpdateOne(FM_OPL *OPL, INT16 *buffer, int length); |
|||
|
|||
#endif |
|||
File diff suppressed because it is too large
@ -1,978 +0,0 @@ |
|||
/*
|
|||
* QEMU OSS Audio output driver |
|||
* |
|||
* Copyright (c) 2003 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 "vl.h" |
|||
|
|||
#include <stdio.h> |
|||
#include <limits.h> |
|||
#include <stdlib.h> |
|||
#include <string.h> |
|||
|
|||
/* TODO: Graceful error handling */ |
|||
|
|||
#if defined(_WIN32) |
|||
#define USE_SDL_AUDIO |
|||
#endif |
|||
|
|||
#define MIN(a, b) ((a)>(b)?(b):(a)) |
|||
#define MAX(a, b) ((a)<(b)?(b):(a)) |
|||
|
|||
#define DEREF(x) (void)x |
|||
#define dolog(...) fprintf (stderr, "audio: " __VA_ARGS__) |
|||
#define ERRFail(...) do { \ |
|||
int _errno = errno; \ |
|||
fprintf (stderr, "audio: " __VA_ARGS__); \ |
|||
fprintf (stderr, "\nsystem error: %s\n", strerror (_errno)); \ |
|||
abort (); \ |
|||
} while (0) |
|||
#define Fail(...) do { \ |
|||
fprintf (stderr, "audio: " __VA_ARGS__); \ |
|||
fprintf (stderr, "\n"); \ |
|||
abort (); \ |
|||
} while (0) |
|||
|
|||
#ifdef DEBUG_AUDIO |
|||
#define lwarn(...) fprintf (stderr, "audio: " __VA_ARGS__) |
|||
#define linfo(...) fprintf (stderr, "audio: " __VA_ARGS__) |
|||
#define ldebug(...) fprintf (stderr, "audio: " __VA_ARGS__) |
|||
#else |
|||
#define lwarn(...) |
|||
#define linfo(...) |
|||
#define ldebug(...) |
|||
#endif |
|||
|
|||
static int get_conf_val (const char *key, int defval) |
|||
{ |
|||
int val = defval; |
|||
char *strval; |
|||
|
|||
strval = getenv (key); |
|||
if (strval) { |
|||
val = atoi (strval); |
|||
} |
|||
|
|||
return val; |
|||
} |
|||
|
|||
static void copy_no_conversion (void *dst, void *src, int size) |
|||
{ |
|||
memcpy (dst, src, size); |
|||
} |
|||
|
|||
static void copy_u16_to_s16 (void *dst, void *src, int size) |
|||
{ |
|||
int i; |
|||
uint16_t *out, *in; |
|||
|
|||
out = dst; |
|||
in = src; |
|||
|
|||
for (i = 0; i < size / 2; i++) { |
|||
out[i] = in[i] + 0x8000; |
|||
} |
|||
} |
|||
|
|||
#ifdef USE_SDL_AUDIO |
|||
#include <SDL/SDL.h> |
|||
#include <SDL/SDL_thread.h> |
|||
|
|||
static struct { |
|||
int samples; |
|||
} conf = { |
|||
.samples = 4096 |
|||
}; |
|||
|
|||
typedef struct AudioState { |
|||
int freq; |
|||
int bits16; |
|||
int nchannels; |
|||
int rpos; |
|||
int wpos; |
|||
volatile int live; |
|||
volatile int exit; |
|||
int bytes_per_second; |
|||
Uint8 *buf; |
|||
int bufsize; |
|||
int leftover; |
|||
uint64_t old_ticks; |
|||
SDL_AudioSpec spec; |
|||
SDL_mutex *mutex; |
|||
SDL_sem *sem; |
|||
void (*copy_fn)(void *, void *, int); |
|||
} AudioState; |
|||
|
|||
static AudioState sdl_audio; |
|||
|
|||
void AUD_run (void) |
|||
{ |
|||
} |
|||
|
|||
static void own (AudioState *s) |
|||
{ |
|||
/* SDL_LockAudio (); */ |
|||
if (SDL_mutexP (s->mutex)) |
|||
dolog ("SDL_mutexP: %s\n", SDL_GetError ()); |
|||
} |
|||
|
|||
static void disown (AudioState *s) |
|||
{ |
|||
/* SDL_UnlockAudio (); */ |
|||
if (SDL_mutexV (s->mutex)) |
|||
dolog ("SDL_mutexV: %s\n", SDL_GetError ()); |
|||
} |
|||
|
|||
static void sem_wait (AudioState *s) |
|||
{ |
|||
if (SDL_SemWait (s->sem)) |
|||
dolog ("SDL_SemWait: %s\n", SDL_GetError ()); |
|||
} |
|||
|
|||
static void sem_post (AudioState *s) |
|||
{ |
|||
if (SDL_SemPost (s->sem)) |
|||
dolog ("SDL_SemPost: %s\n", SDL_GetError ()); |
|||
} |
|||
|
|||
static void audio_callback (void *data, Uint8 *stream, int len) |
|||
{ |
|||
int to_mix; |
|||
AudioState *s = data; |
|||
|
|||
if (s->exit) return; |
|||
while (len) { |
|||
sem_wait (s); |
|||
if (s->exit) return; |
|||
own (s); |
|||
to_mix = MIN (len, s->live); |
|||
len -= to_mix; |
|||
/* printf ("to_mix=%d len=%d live=%d\n", to_mix, len, s->live); */ |
|||
while (to_mix) { |
|||
int chunk = MIN (to_mix, s->bufsize - s->rpos); |
|||
/* SDL_MixAudio (stream, buf, chunk, SDL_MIX_MAXVOLUME); */ |
|||
memcpy (stream, s->buf + s->rpos, chunk); |
|||
|
|||
s->rpos += chunk; |
|||
s->live -= chunk; |
|||
|
|||
stream += chunk; |
|||
to_mix -= chunk; |
|||
|
|||
if (s->rpos == s->bufsize) s->rpos = 0; |
|||
} |
|||
disown (s); |
|||
} |
|||
} |
|||
|
|||
static void sem_zero (AudioState *s) |
|||
{ |
|||
int res; |
|||
|
|||
do { |
|||
res = SDL_SemTryWait (s->sem); |
|||
if (res < 0) { |
|||
dolog ("SDL_SemTryWait: %s\n", SDL_GetError ()); |
|||
return; |
|||
} |
|||
} while (res != SDL_MUTEX_TIMEDOUT); |
|||
} |
|||
|
|||
static void do_open (AudioState *s) |
|||
{ |
|||
int status; |
|||
SDL_AudioSpec obtained; |
|||
|
|||
SDL_PauseAudio (1); |
|||
if (s->buf) { |
|||
s->exit = 1; |
|||
sem_post (s); |
|||
SDL_CloseAudio (); |
|||
s->exit = 0; |
|||
qemu_free (s->buf); |
|||
s->buf = NULL; |
|||
sem_zero (s); |
|||
} |
|||
|
|||
s->bytes_per_second = (s->spec.freq << (s->spec.channels >> 1)) << s->bits16; |
|||
s->spec.samples = conf.samples; |
|||
s->spec.userdata = s; |
|||
s->spec.callback = audio_callback; |
|||
|
|||
status = SDL_OpenAudio (&s->spec, &obtained); |
|||
if (status < 0) { |
|||
dolog ("SDL_OpenAudio: %s\n", SDL_GetError ()); |
|||
goto exit; |
|||
} |
|||
|
|||
if (obtained.freq != s->spec.freq || |
|||
obtained.channels != s->spec.channels || |
|||
obtained.format != s->spec.format) { |
|||
dolog ("Audio spec mismatch requested obtained\n" |
|||
"freq %5d %5d\n" |
|||
"channels %5d %5d\n" |
|||
"fmt %5d %5d\n", |
|||
s->spec.freq, obtained.freq, |
|||
s->spec.channels, obtained.channels, |
|||
s->spec.format, obtained.format |
|||
); |
|||
} |
|||
|
|||
s->bufsize = obtained.size; |
|||
s->buf = qemu_mallocz (s->bufsize); |
|||
if (!s->buf) { |
|||
dolog ("qemu_mallocz(%d)\n", s->bufsize); |
|||
goto exit; |
|||
} |
|||
SDL_PauseAudio (0); |
|||
|
|||
exit: |
|||
s->rpos = 0; |
|||
s->wpos = 0; |
|||
s->live = 0; |
|||
} |
|||
|
|||
int AUD_write (void *in_buf, int size) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
int to_copy, temp; |
|||
uint8_t *in, *out; |
|||
|
|||
own (s); |
|||
to_copy = MIN (s->bufsize - s->live, size); |
|||
|
|||
temp = to_copy; |
|||
|
|||
in = in_buf; |
|||
out = s->buf; |
|||
|
|||
while (temp) { |
|||
int copy; |
|||
|
|||
copy = MIN (temp, s->bufsize - s->wpos); |
|||
s->copy_fn (out + s->wpos, in, copy); |
|||
|
|||
s->wpos += copy; |
|||
if (s->wpos == s->bufsize) { |
|||
s->wpos = 0; |
|||
} |
|||
|
|||
temp -= copy; |
|||
in += copy; |
|||
s->live += copy; |
|||
} |
|||
|
|||
disown (s); |
|||
sem_post (s); |
|||
return to_copy; |
|||
} |
|||
|
|||
static void maybe_open (AudioState *s, int req_freq, int req_nchannels, |
|||
audfmt_e req_fmt, int force_open) |
|||
{ |
|||
int sdl_fmt, bits16; |
|||
|
|||
switch (req_fmt) { |
|||
case AUD_FMT_U8: |
|||
bits16 = 0; |
|||
sdl_fmt = AUDIO_U8; |
|||
s->copy_fn = copy_no_conversion; |
|||
break; |
|||
|
|||
case AUD_FMT_S8: |
|||
fprintf (stderr, "audio: can not play 8bit signed\n"); |
|||
return; |
|||
|
|||
case AUD_FMT_S16: |
|||
bits16 = 1; |
|||
sdl_fmt = AUDIO_S16; |
|||
s->copy_fn = copy_no_conversion; |
|||
break; |
|||
|
|||
case AUD_FMT_U16: |
|||
bits16 = 1; |
|||
sdl_fmt = AUDIO_S16; |
|||
s->copy_fn = copy_u16_to_s16; |
|||
break; |
|||
|
|||
default: |
|||
abort (); |
|||
} |
|||
|
|||
if (force_open |
|||
|| (NULL == s->buf) |
|||
|| (sdl_fmt != s->spec.format) |
|||
|| (req_nchannels != s->spec.channels) |
|||
|| (req_freq != s->spec.freq) |
|||
|| (bits16 != s->bits16)) { |
|||
|
|||
s->spec.format = sdl_fmt; |
|||
s->spec.channels = req_nchannels; |
|||
s->spec.freq = req_freq; |
|||
s->bits16 = bits16; |
|||
do_open (s); |
|||
} |
|||
} |
|||
|
|||
void AUD_reset (int req_freq, int req_nchannels, audfmt_e req_fmt) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
own (s); |
|||
maybe_open (s, req_freq, req_nchannels, req_fmt, 0); |
|||
disown (s); |
|||
} |
|||
|
|||
void AUD_open (int req_freq, int req_nchannels, audfmt_e req_fmt) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
own (s); |
|||
maybe_open (s, req_freq, req_nchannels, req_fmt, 1); |
|||
disown (s); |
|||
} |
|||
|
|||
void AUD_adjust_estimate (int leftover) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
own (s); |
|||
s->leftover = leftover; |
|||
disown (s); |
|||
} |
|||
|
|||
int AUD_get_free (void) |
|||
{ |
|||
int free, elapsed; |
|||
uint64_t ticks, delta; |
|||
uint64_t ua_elapsed; |
|||
uint64_t al_elapsed; |
|||
AudioState *s = &sdl_audio; |
|||
|
|||
own (s); |
|||
free = s->bufsize - s->live; |
|||
|
|||
if (0 == free) { |
|||
disown (s); |
|||
return 0; |
|||
} |
|||
|
|||
elapsed = free; |
|||
ticks = qemu_get_clock(rt_clock); |
|||
delta = ticks - s->old_ticks; |
|||
s->old_ticks = ticks; |
|||
|
|||
ua_elapsed = (delta * s->bytes_per_second) / 1000; |
|||
al_elapsed = ua_elapsed & ~3ULL; |
|||
|
|||
ldebug ("tid elapsed %llu bytes\n", ua_elapsed); |
|||
|
|||
if (al_elapsed > (uint64_t) INT_MAX) |
|||
elapsed = INT_MAX; |
|||
else |
|||
elapsed = al_elapsed; |
|||
|
|||
elapsed += s->leftover; |
|||
disown (s); |
|||
|
|||
if (elapsed > free) { |
|||
lwarn ("audio can not keep up elapsed %d free %d\n", elapsed, free); |
|||
return free; |
|||
} |
|||
else { |
|||
return elapsed; |
|||
} |
|||
} |
|||
|
|||
int AUD_get_live (void) |
|||
{ |
|||
int live; |
|||
AudioState *s = &sdl_audio; |
|||
|
|||
own (s); |
|||
live = s->live; |
|||
disown (s); |
|||
return live; |
|||
} |
|||
|
|||
int AUD_get_buffer_size (void) |
|||
{ |
|||
int bufsize; |
|||
AudioState *s = &sdl_audio; |
|||
|
|||
own (s); |
|||
bufsize = s->bufsize; |
|||
disown (s); |
|||
return bufsize; |
|||
} |
|||
|
|||
#define QC_SDL_NSAMPLES "QEMU_SDL_NSAMPLES" |
|||
|
|||
static void cleanup (void) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
own (s); |
|||
s->exit = 1; |
|||
sem_post (s); |
|||
disown (s); |
|||
} |
|||
|
|||
void AUD_init (void) |
|||
{ |
|||
AudioState *s = &sdl_audio; |
|||
|
|||
atexit (cleanup); |
|||
SDL_InitSubSystem (SDL_INIT_AUDIO); |
|||
s->mutex = SDL_CreateMutex (); |
|||
if (!s->mutex) { |
|||
dolog ("SDL_CreateMutex: %s\n", SDL_GetError ()); |
|||
return; |
|||
} |
|||
|
|||
s->sem = SDL_CreateSemaphore (0); |
|||
if (!s->sem) { |
|||
dolog ("SDL_CreateSemaphore: %s\n", SDL_GetError ()); |
|||
return; |
|||
} |
|||
|
|||
conf.samples = get_conf_val (QC_SDL_NSAMPLES, conf.samples); |
|||
} |
|||
|
|||
#elif !defined(_WIN32) && !defined(__APPLE__) |
|||
|
|||
#include <fcntl.h> |
|||
#include <errno.h> |
|||
#include <unistd.h> |
|||
#include <sys/mman.h> |
|||
#include <sys/types.h> |
|||
#include <sys/ioctl.h> |
|||
#include <sys/soundcard.h> |
|||
|
|||
/* http://www.df.lth.se/~john_e/gems/gem002d.html */ |
|||
/* http://www.multi-platforms.com/Tips/PopCount.htm */ |
|||
static inline uint32_t popcount (uint32_t u) |
|||
{ |
|||
u = ((u&0x55555555) + ((u>>1)&0x55555555)); |
|||
u = ((u&0x33333333) + ((u>>2)&0x33333333)); |
|||
u = ((u&0x0f0f0f0f) + ((u>>4)&0x0f0f0f0f)); |
|||
u = ((u&0x00ff00ff) + ((u>>8)&0x00ff00ff)); |
|||
u = ( u&0x0000ffff) + (u>>16); |
|||
return u; |
|||
} |
|||
|
|||
static inline uint32_t lsbindex (uint32_t u) |
|||
{ |
|||
return popcount ((u&-u)-1); |
|||
} |
|||
|
|||
#define IOCTL(args) do { \ |
|||
int ret = ioctl args; \ |
|||
if (-1 == ret) { \ |
|||
ERRFail (#args); \ |
|||
} \ |
|||
ldebug ("ioctl " #args " = %d\n", ret); \ |
|||
} while (0) |
|||
|
|||
typedef struct AudioState { |
|||
int fd; |
|||
int freq; |
|||
int bits16; |
|||
int nchannels; |
|||
int rpos; |
|||
int wpos; |
|||
int live; |
|||
int oss_fmt; |
|||
int bytes_per_second; |
|||
int is_mapped; |
|||
void *buf; |
|||
int bufsize; |
|||
int nfrags; |
|||
int fragsize; |
|||
int old_optr; |
|||
int leftover; |
|||
uint64_t old_ticks; |
|||
void (*copy_fn)(void *, void *, int); |
|||
} AudioState; |
|||
|
|||
static AudioState oss_audio = { .fd = -1 }; |
|||
|
|||
static struct { |
|||
int try_mmap; |
|||
int nfrags; |
|||
int fragsize; |
|||
} conf = { |
|||
.try_mmap = 0, |
|||
.nfrags = 4, |
|||
.fragsize = 4096 |
|||
}; |
|||
|
|||
static enum {DONT, DSP, TID} est = DONT; |
|||
|
|||
static void pab (AudioState *s, struct audio_buf_info *abinfo) |
|||
{ |
|||
DEREF (abinfo); |
|||
|
|||
ldebug ("fragments %d, fragstotal %d, fragsize %d, bytes %d\n" |
|||
"rpos %d, wpos %d, live %d\n", |
|||
abinfo->fragments, |
|||
abinfo->fragstotal, |
|||
abinfo->fragsize, |
|||
abinfo->bytes, |
|||
s->rpos, s->wpos, s->live); |
|||
} |
|||
|
|||
static void do_open (AudioState *s) |
|||
{ |
|||
int mmmmssss; |
|||
audio_buf_info abinfo; |
|||
int fmt, freq, nchannels; |
|||
|
|||
if (s->buf) { |
|||
if (s->is_mapped) { |
|||
if (-1 == munmap (s->buf, s->bufsize)) { |
|||
ERRFail ("failed to unmap audio buffer %p %d", |
|||
s->buf, s->bufsize); |
|||
} |
|||
} |
|||
else { |
|||
qemu_free (s->buf); |
|||
} |
|||
s->buf = NULL; |
|||
} |
|||
|
|||
if (-1 != s->fd) |
|||
close (s->fd); |
|||
|
|||
s->fd = open ("/dev/dsp", O_RDWR | O_NONBLOCK); |
|||
if (-1 == s->fd) { |
|||
ERRFail ("can not open /dev/dsp"); |
|||
} |
|||
|
|||
fmt = s->oss_fmt; |
|||
freq = s->freq; |
|||
nchannels = s->nchannels; |
|||
|
|||
IOCTL ((s->fd, SNDCTL_DSP_RESET, 1)); |
|||
IOCTL ((s->fd, SNDCTL_DSP_SAMPLESIZE, &fmt)); |
|||
IOCTL ((s->fd, SNDCTL_DSP_CHANNELS, &nchannels)); |
|||
IOCTL ((s->fd, SNDCTL_DSP_SPEED, &freq)); |
|||
IOCTL ((s->fd, SNDCTL_DSP_NONBLOCK)); |
|||
|
|||
mmmmssss = (conf.nfrags << 16) | conf.fragsize; |
|||
IOCTL ((s->fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)); |
|||
|
|||
if ((s->oss_fmt != fmt) |
|||
|| (s->nchannels != nchannels) |
|||
|| (s->freq != freq)) { |
|||
Fail ("failed to set audio parameters\n" |
|||
"parameter | requested value | obtained value\n" |
|||
"format | %10d | %10d\n" |
|||
"channels | %10d | %10d\n" |
|||
"frequency | %10d | %10d\n", |
|||
s->oss_fmt, fmt, |
|||
s->nchannels, nchannels, |
|||
s->freq, freq); |
|||
} |
|||
|
|||
IOCTL ((s->fd, SNDCTL_DSP_GETOSPACE, &abinfo)); |
|||
|
|||
s->nfrags = abinfo.fragstotal; |
|||
s->fragsize = abinfo.fragsize; |
|||
s->bufsize = s->nfrags * s->fragsize; |
|||
s->old_optr = 0; |
|||
|
|||
s->bytes_per_second = (freq << (nchannels >> 1)) << s->bits16; |
|||
|
|||
linfo ("bytes per second %d\n", s->bytes_per_second); |
|||
|
|||
linfo ("fragments %d, fragstotal %d, fragsize %d, bytes %d, bufsize %d\n", |
|||
abinfo.fragments, |
|||
abinfo.fragstotal, |
|||
abinfo.fragsize, |
|||
abinfo.bytes, |
|||
s->bufsize); |
|||
|
|||
s->buf = MAP_FAILED; |
|||
s->is_mapped = 0; |
|||
|
|||
if (conf.try_mmap) { |
|||
s->buf = mmap (NULL, s->bufsize, PROT_WRITE, MAP_SHARED, s->fd, 0); |
|||
if (MAP_FAILED == s->buf) { |
|||
int err; |
|||
|
|||
err = errno; |
|||
dolog ("failed to mmap audio, size %d, fd %d\n" |
|||
"syserr: %s\n", |
|||
s->bufsize, s->fd, strerror (err)); |
|||
} |
|||
else { |
|||
est = TID; |
|||
s->is_mapped = 1; |
|||
} |
|||
} |
|||
|
|||
if (MAP_FAILED == s->buf) { |
|||
est = TID; |
|||
s->buf = qemu_mallocz (s->bufsize); |
|||
if (!s->buf) { |
|||
ERRFail ("audio buf malloc failed, size %d", s->bufsize); |
|||
} |
|||
} |
|||
|
|||
s->rpos = 0; |
|||
s->wpos = 0; |
|||
s->live = 0; |
|||
|
|||
if (s->is_mapped) { |
|||
int trig; |
|||
|
|||
trig = 0; |
|||
IOCTL ((s->fd, SNDCTL_DSP_SETTRIGGER, &trig)); |
|||
trig = PCM_ENABLE_OUTPUT; |
|||
IOCTL ((s->fd, SNDCTL_DSP_SETTRIGGER, &trig)); |
|||
} |
|||
} |
|||
|
|||
static void maybe_open (AudioState *s, int req_freq, int req_nchannels, |
|||
audfmt_e req_fmt, int force_open) |
|||
{ |
|||
int oss_fmt, bits16; |
|||
|
|||
switch (req_fmt) { |
|||
case AUD_FMT_U8: |
|||
bits16 = 0; |
|||
oss_fmt = AFMT_U8; |
|||
s->copy_fn = copy_no_conversion; |
|||
break; |
|||
|
|||
case AUD_FMT_S8: |
|||
Fail ("can not play 8bit signed"); |
|||
|
|||
case AUD_FMT_S16: |
|||
bits16 = 1; |
|||
oss_fmt = AFMT_S16_LE; |
|||
s->copy_fn = copy_no_conversion; |
|||
break; |
|||
|
|||
case AUD_FMT_U16: |
|||
bits16 = 1; |
|||
oss_fmt = AFMT_S16_LE; |
|||
s->copy_fn = copy_u16_to_s16; |
|||
break; |
|||
|
|||
default: |
|||
abort (); |
|||
} |
|||
|
|||
if (force_open |
|||
|| (-1 == s->fd) |
|||
|| (oss_fmt != s->oss_fmt) |
|||
|| (req_nchannels != s->nchannels) |
|||
|| (req_freq != s->freq) |
|||
|| (bits16 != s->bits16)) { |
|||
s->oss_fmt = oss_fmt; |
|||
s->nchannels = req_nchannels; |
|||
s->freq = req_freq; |
|||
s->bits16 = bits16; |
|||
do_open (s); |
|||
} |
|||
} |
|||
|
|||
void AUD_reset (int req_freq, int req_nchannels, audfmt_e req_fmt) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
maybe_open (s, req_freq, req_nchannels, req_fmt, 0); |
|||
} |
|||
|
|||
void AUD_open (int req_freq, int req_nchannels, audfmt_e req_fmt) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
maybe_open (s, req_freq, req_nchannels, req_fmt, 1); |
|||
} |
|||
|
|||
int AUD_write (void *in_buf, int size) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
int to_copy, temp; |
|||
uint8_t *in, *out; |
|||
|
|||
to_copy = MIN (s->bufsize - s->live, size); |
|||
|
|||
temp = to_copy; |
|||
|
|||
in = in_buf; |
|||
out = s->buf; |
|||
|
|||
while (temp) { |
|||
int copy; |
|||
|
|||
copy = MIN (temp, s->bufsize - s->wpos); |
|||
s->copy_fn (out + s->wpos, in, copy); |
|||
|
|||
s->wpos += copy; |
|||
if (s->wpos == s->bufsize) { |
|||
s->wpos = 0; |
|||
} |
|||
|
|||
temp -= copy; |
|||
in += copy; |
|||
s->live += copy; |
|||
} |
|||
|
|||
return to_copy; |
|||
} |
|||
|
|||
void AUD_run (void) |
|||
{ |
|||
int res; |
|||
int bytes; |
|||
struct audio_buf_info abinfo; |
|||
AudioState *s = &oss_audio; |
|||
|
|||
if (0 == s->live) |
|||
return; |
|||
|
|||
if (s->is_mapped) { |
|||
count_info info; |
|||
|
|||
res = ioctl (s->fd, SNDCTL_DSP_GETOPTR, &info); |
|||
if (res < 0) { |
|||
int err; |
|||
|
|||
err = errno; |
|||
lwarn ("SNDCTL_DSP_GETOPTR failed with %s\n", strerror (err)); |
|||
return; |
|||
} |
|||
|
|||
if (info.ptr > s->old_optr) { |
|||
bytes = info.ptr - s->old_optr; |
|||
} |
|||
else { |
|||
bytes = s->bufsize + info.ptr - s->old_optr; |
|||
} |
|||
|
|||
s->old_optr = info.ptr; |
|||
s->live -= bytes; |
|||
return; |
|||
} |
|||
|
|||
res = ioctl (s->fd, SNDCTL_DSP_GETOSPACE, &abinfo); |
|||
|
|||
if (res < 0) { |
|||
int err; |
|||
|
|||
err = errno; |
|||
lwarn ("SNDCTL_DSP_GETOSPACE failed with %s\n", strerror (err)); |
|||
return; |
|||
} |
|||
|
|||
bytes = abinfo.bytes; |
|||
bytes = MIN (s->live, bytes); |
|||
#if 0 |
|||
bytes = (bytes / fragsize) * fragsize; |
|||
#endif |
|||
|
|||
while (bytes) { |
|||
int left, play, written; |
|||
|
|||
left = s->bufsize - s->rpos; |
|||
play = MIN (left, bytes); |
|||
written = write (s->fd, (uint8_t *)s->buf + s->rpos, play); |
|||
|
|||
if (-1 == written) { |
|||
if (EAGAIN == errno || EINTR == errno) { |
|||
return; |
|||
} |
|||
else { |
|||
ERRFail ("write audio"); |
|||
} |
|||
} |
|||
|
|||
play = written; |
|||
s->live -= play; |
|||
s->rpos += play; |
|||
bytes -= play; |
|||
|
|||
if (s->rpos == s->bufsize) { |
|||
s->rpos = 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static int get_dsp_bytes (void) |
|||
{ |
|||
int res; |
|||
struct count_info info; |
|||
AudioState *s = &oss_audio; |
|||
|
|||
res = ioctl (s->fd, SNDCTL_DSP_GETOPTR, &info); |
|||
if (-1 == res) { |
|||
int err; |
|||
|
|||
err = errno; |
|||
lwarn ("SNDCTL_DSP_GETOPTR failed with %s\n", strerror (err)); |
|||
return -1; |
|||
} |
|||
else { |
|||
ldebug ("bytes %d\n", info.bytes); |
|||
return info.bytes; |
|||
} |
|||
} |
|||
|
|||
void AUD_adjust_estimate (int leftover) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
s->leftover = leftover; |
|||
} |
|||
|
|||
int AUD_get_free (void) |
|||
{ |
|||
int free, elapsed; |
|||
AudioState *s = &oss_audio; |
|||
|
|||
free = s->bufsize - s->live; |
|||
|
|||
if (free <= 0) |
|||
return 0; |
|||
|
|||
elapsed = free; |
|||
switch (est) { |
|||
case DONT: |
|||
break; |
|||
|
|||
case DSP: |
|||
{ |
|||
static int old_bytes; |
|||
int bytes; |
|||
|
|||
bytes = get_dsp_bytes (); |
|||
if (bytes <= 0) |
|||
return free; |
|||
|
|||
elapsed = bytes - old_bytes; |
|||
old_bytes = bytes; |
|||
ldebug ("dsp elapsed %d bytes\n", elapsed); |
|||
break; |
|||
} |
|||
|
|||
case TID: |
|||
{ |
|||
uint64_t ticks, delta; |
|||
uint64_t ua_elapsed; |
|||
uint64_t al_elapsed; |
|||
|
|||
ticks = qemu_get_clock(rt_clock); |
|||
delta = ticks - s->old_ticks; |
|||
s->old_ticks = ticks; |
|||
|
|||
ua_elapsed = (delta * s->bytes_per_second) / 1000; |
|||
al_elapsed = ua_elapsed & ~3ULL; |
|||
|
|||
ldebug ("tid elapsed %llu bytes\n", ua_elapsed); |
|||
|
|||
if (al_elapsed > (uint64_t) INT_MAX) |
|||
elapsed = INT_MAX; |
|||
else |
|||
elapsed = al_elapsed; |
|||
|
|||
elapsed += s->leftover; |
|||
} |
|||
} |
|||
|
|||
if (elapsed > free) { |
|||
lwarn ("audio can not keep up elapsed %d free %d\n", elapsed, free); |
|||
return free; |
|||
} |
|||
else { |
|||
return elapsed; |
|||
} |
|||
} |
|||
|
|||
int AUD_get_live (void) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
return s->live; |
|||
} |
|||
|
|||
int AUD_get_buffer_size (void) |
|||
{ |
|||
AudioState *s = &oss_audio; |
|||
return s->bufsize; |
|||
} |
|||
|
|||
#define QC_OSS_FRAGSIZE "QEMU_OSS_FRAGSIZE" |
|||
#define QC_OSS_NFRAGS "QEMU_OSS_NFRAGS" |
|||
#define QC_OSS_MMAP "QEMU_OSS_MMAP" |
|||
|
|||
void AUD_init (void) |
|||
{ |
|||
int fsp; |
|||
|
|||
DEREF (pab); |
|||
|
|||
conf.fragsize = get_conf_val (QC_OSS_FRAGSIZE, conf.fragsize); |
|||
conf.nfrags = get_conf_val (QC_OSS_NFRAGS, conf.nfrags); |
|||
conf.try_mmap = get_conf_val (QC_OSS_MMAP, conf.try_mmap); |
|||
|
|||
fsp = conf.fragsize; |
|||
if (0 != (fsp & (fsp - 1))) { |
|||
Fail ("fragment size %d is not power of 2", fsp); |
|||
} |
|||
|
|||
conf.fragsize = lsbindex (fsp); |
|||
} |
|||
|
|||
#else |
|||
|
|||
void AUD_run (void) |
|||
{ |
|||
} |
|||
|
|||
int AUD_write (void *in_buf, int size) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
void AUD_reset (int rfreq, int rnchannels, audfmt_e rfmt) |
|||
{ |
|||
} |
|||
|
|||
void AUD_adjust_estimate (int _leftover) |
|||
{ |
|||
} |
|||
|
|||
int AUD_get_free (void) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int AUD_get_live (void) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
int AUD_get_buffer_size (void) |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
void AUD_init (void) |
|||
{ |
|||
} |
|||
|
|||
#endif |
|||
Loading…
Reference in new issue