402 lines
9.0 KiB
C
402 lines
9.0 KiB
C
/*
|
|
* QEMU Audio subsystem header
|
|
*
|
|
* Copyright (c) 2005 Vassili Karpov (malc)
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#ifdef DAC
|
|
#define TYPE out
|
|
#define HW glue (HWVoice, Out)
|
|
#define SW glue (SWVoice, Out)
|
|
#else
|
|
#define TYPE in
|
|
#define HW glue (HWVoice, In)
|
|
#define SW glue (SWVoice, In)
|
|
#endif
|
|
|
|
static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw)
|
|
{
|
|
glue (audio_pcm_sw_free_resources_, TYPE) (sw);
|
|
if (sw->name) {
|
|
qemu_free (sw->name);
|
|
sw->name = NULL;
|
|
}
|
|
}
|
|
|
|
static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw)
|
|
{
|
|
LIST_INSERT_HEAD (&hw->sw_head, sw, entries);
|
|
}
|
|
|
|
static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw)
|
|
{
|
|
LIST_REMOVE (sw, entries);
|
|
}
|
|
|
|
static void glue (audio_pcm_hw_fini_, TYPE) (HW *hw)
|
|
{
|
|
if (hw->active) {
|
|
glue (audio_pcm_hw_free_resources_ ,TYPE) (hw);
|
|
glue (hw->pcm_ops->fini_, TYPE) (hw);
|
|
memset (hw, 0, glue (audio_state.drv->voice_size_, TYPE));
|
|
}
|
|
}
|
|
|
|
static void glue (audio_pcm_hw_gc_, TYPE) (HW *hw)
|
|
{
|
|
if (!hw->sw_head.lh_first) {
|
|
glue (audio_pcm_hw_fini_, TYPE) (hw);
|
|
}
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw)
|
|
{
|
|
return hw ? hw->entries.le_next : glue (hw_head_, TYPE).lh_first;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_find_any_active_, TYPE) (HW *hw)
|
|
{
|
|
while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
|
|
if (hw->active) {
|
|
return hw;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_find_any_active_enabled_, TYPE) (HW *hw)
|
|
{
|
|
while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
|
|
if (hw->active && hw->enabled) {
|
|
return hw;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_find_any_passive_, TYPE) (HW *hw)
|
|
{
|
|
while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) {
|
|
if (!hw->active) {
|
|
return hw;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_find_specific_, TYPE) (
|
|
HW *hw,
|
|
int freq,
|
|
int nchannels,
|
|
audfmt_e fmt
|
|
)
|
|
{
|
|
while ((hw = glue (audio_pcm_hw_find_any_active_, TYPE) (hw))) {
|
|
if (audio_pcm_info_eq (&hw->info, freq, nchannels, fmt)) {
|
|
return hw;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_add_new_, TYPE) (
|
|
int freq,
|
|
int nchannels,
|
|
audfmt_e fmt
|
|
)
|
|
{
|
|
HW *hw;
|
|
|
|
hw = glue (audio_pcm_hw_find_any_passive_, TYPE) (NULL);
|
|
if (hw) {
|
|
hw->pcm_ops = audio_state.drv->pcm_ops;
|
|
if (!hw->pcm_ops) {
|
|
return NULL;
|
|
}
|
|
|
|
if (glue (audio_pcm_hw_init_, TYPE) (hw, freq, nchannels, fmt)) {
|
|
glue (audio_pcm_hw_gc_, TYPE) (hw);
|
|
return NULL;
|
|
}
|
|
else {
|
|
return hw;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static HW *glue (audio_pcm_hw_add_, TYPE) (
|
|
int freq,
|
|
int nchannels,
|
|
audfmt_e fmt
|
|
)
|
|
{
|
|
HW *hw;
|
|
|
|
if (glue (audio_state.greedy_, TYPE)) {
|
|
hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt);
|
|
if (hw) {
|
|
return hw;
|
|
}
|
|
}
|
|
|
|
hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, freq, nchannels, fmt);
|
|
if (hw) {
|
|
return hw;
|
|
}
|
|
|
|
hw = glue (audio_pcm_hw_add_new_, TYPE) (freq, nchannels, fmt);
|
|
if (hw) {
|
|
return hw;
|
|
}
|
|
|
|
return glue (audio_pcm_hw_find_any_active_, TYPE) (NULL);
|
|
}
|
|
|
|
static SW *glue (audio_pcm_create_voice_pair_, TYPE) (
|
|
const char *name,
|
|
int freq,
|
|
int nchannels,
|
|
audfmt_e fmt
|
|
)
|
|
{
|
|
SW *sw;
|
|
HW *hw;
|
|
int hw_freq = freq;
|
|
int hw_nchannels = nchannels;
|
|
int hw_fmt = fmt;
|
|
|
|
if (glue (audio_state.fixed_settings_, TYPE)) {
|
|
hw_freq = glue (audio_state.fixed_freq_, TYPE);
|
|
hw_nchannels = glue (audio_state.fixed_channels_, TYPE);
|
|
hw_fmt = glue (audio_state.fixed_fmt_, TYPE);
|
|
}
|
|
|
|
sw = qemu_mallocz (sizeof (*sw));
|
|
if (!sw) {
|
|
goto err1;
|
|
}
|
|
|
|
hw = glue (audio_pcm_hw_add_, TYPE) (hw_freq, hw_nchannels, hw_fmt);
|
|
if (!hw) {
|
|
goto err2;
|
|
}
|
|
|
|
glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw);
|
|
|
|
if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, freq, nchannels, fmt)) {
|
|
goto err3;
|
|
}
|
|
|
|
return sw;
|
|
|
|
err3:
|
|
glue (audio_pcm_hw_del_sw_, TYPE) (sw);
|
|
glue (audio_pcm_hw_gc_, TYPE) (hw);
|
|
err2:
|
|
qemu_free (sw);
|
|
err1:
|
|
return NULL;
|
|
}
|
|
|
|
void glue (AUD_close_, TYPE) (SW *sw)
|
|
{
|
|
if (sw) {
|
|
glue (audio_pcm_sw_fini_, TYPE) (sw);
|
|
glue (audio_pcm_hw_del_sw_, TYPE) (sw);
|
|
glue (audio_pcm_hw_gc_, TYPE) (sw->hw);
|
|
qemu_free (sw);
|
|
}
|
|
}
|
|
|
|
SW *glue (AUD_open_, TYPE) (
|
|
SW *sw,
|
|
const char *name,
|
|
void *callback_opaque ,
|
|
audio_callback_fn_t callback_fn,
|
|
int freq,
|
|
int nchannels,
|
|
audfmt_e fmt
|
|
)
|
|
{
|
|
#ifdef DAC
|
|
int live = 0;
|
|
SW *old_sw = NULL;
|
|
#endif
|
|
|
|
if (!callback_fn) {
|
|
dolog ("No callback specifed for voice `%s'\n", name);
|
|
goto fail;
|
|
}
|
|
|
|
if (nchannels != 1 && nchannels != 2) {
|
|
dolog ("Bogus channel count %d for voice `%s'\n", nchannels, name);
|
|
goto fail;
|
|
}
|
|
|
|
if (!audio_state.drv) {
|
|
dolog ("No audio driver defined\n");
|
|
goto fail;
|
|
}
|
|
|
|
if (sw && audio_pcm_info_eq (&sw->info, freq, nchannels, fmt)) {
|
|
return sw;
|
|
}
|
|
|
|
#ifdef DAC
|
|
if (audio_state.plive && sw && (!sw->active && !sw->empty)) {
|
|
live = sw->total_hw_samples_mixed;
|
|
|
|
#ifdef DEBUG_PLIVE
|
|
dolog ("Replacing voice %s with %d live samples\n", sw->name, live);
|
|
dolog ("Old %s freq %d, bits %d, channels %d\n",
|
|
sw->name, sw->info.freq, sw->info.bits, sw->info.nchannels);
|
|
dolog ("New %s freq %d, bits %d, channels %d\n",
|
|
name, freq, (fmt == AUD_FMT_S16 || fmt == AUD_FMT_U16) ? 16 : 8,
|
|
nchannels);
|
|
#endif
|
|
|
|
if (live) {
|
|
old_sw = sw;
|
|
old_sw->callback.fn = NULL;
|
|
sw = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!glue (audio_state.fixed_settings_, TYPE) && sw) {
|
|
glue (AUD_close_, TYPE) (sw);
|
|
sw = NULL;
|
|
}
|
|
|
|
if (sw) {
|
|
HW *hw = sw->hw;
|
|
|
|
if (!hw) {
|
|
dolog ("Internal logic error voice %s has no hardware store\n",
|
|
name);
|
|
goto fail;
|
|
}
|
|
|
|
if (glue (audio_pcm_sw_init_, TYPE) (
|
|
sw,
|
|
hw,
|
|
name,
|
|
freq,
|
|
nchannels,
|
|
fmt
|
|
)) {
|
|
goto fail;
|
|
}
|
|
}
|
|
else {
|
|
sw = glue (audio_pcm_create_voice_pair_, TYPE) (
|
|
name,
|
|
freq,
|
|
nchannels,
|
|
fmt);
|
|
if (!sw) {
|
|
dolog ("Failed to create voice %s\n", name);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (sw) {
|
|
sw->vol = nominal_volume;
|
|
sw->callback.fn = callback_fn;
|
|
sw->callback.opaque = callback_opaque;
|
|
|
|
#ifdef DAC
|
|
if (live) {
|
|
int mixed =
|
|
(live << old_sw->info.shift)
|
|
* old_sw->info.bytes_per_second
|
|
/ sw->info.bytes_per_second;
|
|
|
|
#ifdef DEBUG_PLIVE
|
|
dolog ("Silence will be mixed %d\n", mixed);
|
|
#endif
|
|
sw->total_hw_samples_mixed += mixed;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG_AUDIO
|
|
dolog ("%s\n", name);
|
|
audio_pcm_print_info ("hw", &sw->hw->info);
|
|
audio_pcm_print_info ("sw", &sw->info);
|
|
#endif
|
|
}
|
|
|
|
return sw;
|
|
|
|
fail:
|
|
glue (AUD_close_, TYPE) (sw);
|
|
return NULL;
|
|
}
|
|
|
|
int glue (AUD_is_active_, TYPE) (SW *sw)
|
|
{
|
|
return sw ? sw->active : 0;
|
|
}
|
|
|
|
void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts)
|
|
{
|
|
if (!sw) {
|
|
return;
|
|
}
|
|
|
|
ts->old_ts = sw->hw->ts_helper;
|
|
}
|
|
|
|
uint64_t glue (AUD_time_stamp_get_elapsed_usec_, TYPE) (
|
|
SW *sw,
|
|
QEMUAudioTimeStamp *ts
|
|
)
|
|
{
|
|
uint64_t delta, cur_ts, old_ts;
|
|
|
|
if (!sw) {
|
|
return 0;
|
|
}
|
|
|
|
cur_ts = sw->hw->ts_helper;
|
|
old_ts = ts->old_ts;
|
|
/* dolog ("cur %lld old %lld\n", cur_ts, old_ts); */
|
|
|
|
if (cur_ts >= old_ts) {
|
|
delta = cur_ts - old_ts;
|
|
}
|
|
else {
|
|
delta = UINT64_MAX - old_ts + cur_ts;
|
|
}
|
|
|
|
if (!delta) {
|
|
return 0;
|
|
}
|
|
|
|
return (delta * sw->hw->info.freq) / 1000000;
|
|
}
|
|
|
|
#undef TYPE
|
|
#undef HW
|
|
#undef SW
|