Newer
Older
*
* Copyright (c) 2003-2005 Vassili Karpov (malc)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* 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 "monitor/monitor.h"
#include "qapi/error.h"
#include "qapi/clone-visitor.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qapi-visit-audio.h"
#include "qapi/qapi-commands-audio.h"
#include "qapi/qmp/qdict.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/help_option.h"
#include "sysemu/sysemu.h"
#include "sysemu/replay.h"
#include "sysemu/runstate.h"
#define AUDIO_CAP "audio"
#include "audio_int.h"
/* #define DEBUG_LIVE */
/* #define DEBUG_OUT */
/* #define DEBUG_CAPTURE */
#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown"
/* Order of CONFIG_AUDIO_DRIVERS is import.
The 1st one is the one used by default, that is the reason
that we generate the list.
*/
const char *audio_prio_list[] = {
static QLIST_HEAD(, audio_driver) audio_drivers;
static AudiodevListHead audiodevs =
QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
static AudiodevListHead default_audiodevs =
QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
void audio_driver_register(audio_driver *drv)
{
QLIST_INSERT_HEAD(&audio_drivers, drv, next);
}
static audio_driver *audio_driver_lookup(const char *name)
Error *local_err = NULL;
int rv;
QLIST_FOREACH(d, &audio_drivers, next) {
if (strcmp(name, d->name) == 0) {
return d;
}
}
rv = audio_module_load(name, &local_err);
if (rv > 0) {
QLIST_FOREACH(d, &audio_drivers, next) {
if (strcmp(name, d->name) == 0) {
return d;
}
} else if (rv < 0) {
error_report_err(local_err);
static QTAILQ_HEAD(AudioStateHead, AudioState) audio_states =
QTAILQ_HEAD_INITIALIZER(audio_states);
static AudioState *default_audio_state;
const struct mixeng_volume nominal_volume = {
.r = 1ULL << 32,
.l = 1ULL << 32,
int audio_bug (const char *funcname, int cond)
if (cond) {
static int shown;
AUD_log (NULL, "A bug was just triggered in %s\n", funcname);
if (!shown) {
shown = 1;
AUD_log (NULL, "Save all your work and restart without audio\n");
AUD_log (NULL, "I am sorry\n");
}
AUD_log (NULL, "Context:\n");
static inline int audio_bits_to_index (int bits)
{
switch (bits) {
case 8:
return 0;
case 16:
return 1;
case 32:
return 2;
default:
audio_bug ("bits_to_index", 1);
AUD_log (NULL, "invalid bits %d\n", bits);
}
}
void AUD_vlog (const char *cap, const char *fmt, va_list ap)
if (cap) {
fprintf(stderr, "%s: ", cap);
vfprintf(stderr, fmt, ap);
void AUD_log (const char *cap, const char *fmt, ...)
va_list ap;
va_start (ap, fmt);
AUD_vlog (cap, fmt, ap);
va_end (ap);
static void audio_print_settings (struct audsettings *as)
{
dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels);
switch (as->fmt) {
case AUDIO_FORMAT_F32:
AUD_log (NULL, "F32");
break;
default:
AUD_log (NULL, "invalid(%d)", as->fmt);
break;
}
switch (as->endianness) {
case 0:
AUD_log (NULL, "little");
break;
case 1:
AUD_log (NULL, "big");
break;
default:
AUD_log (NULL, "invalid");
break;
}
static int audio_validate_settings (struct audsettings *as)
invalid = as->nchannels < 1;
invalid |= as->endianness != 0 && as->endianness != 1;
case AUDIO_FORMAT_S8:
case AUDIO_FORMAT_U8:
case AUDIO_FORMAT_S16:
case AUDIO_FORMAT_U16:
case AUDIO_FORMAT_S32:
case AUDIO_FORMAT_U32:
case AUDIO_FORMAT_F32:
break;
default:
invalid = 1;
break;
}
invalid |= as->freq <= 0;
static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *as)
int bits = 8;
bool is_signed = false, is_float = false;
case AUDIO_FORMAT_F32:
is_float = true;
/* fall through */
bits = 32;
break;
default:
abort();
return info->freq == as->freq
&& info->nchannels == as->nchannels
&& info->is_signed == is_signed
&& info->is_float == is_float
&& info->bits == bits
&& info->swap_endianness == (as->endianness != AUDIO_HOST_ENDIANNESS);
void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as)
int bits = 8, mul;
bool is_signed = false, is_float = false;
/* fall through */
case AUDIO_FORMAT_F32:
is_float = true;
/* fall through */
default:
abort();
info->is_signed = is_signed;
info->is_float = is_float;
info->bytes_per_frame = as->nchannels * mul;
info->bytes_per_second = info->freq * info->bytes_per_frame;
info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS);
void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)
if (!len) {
return;
}
if (info->is_signed || info->is_float) {
memset(buf, 0x00, len * info->bytes_per_frame);
switch (info->bits) {
case 8:
memset(buf, 0x80, len * info->bytes_per_frame);
case 16:
{
int i;
uint16_t *p = buf;
short s = INT16_MAX;
if (info->swap_endianness) {
s = bswap16 (s);
}
for (i = 0; i < len * info->nchannels; i++) {
p[i] = s;
}
break;
case 32:
{
int i;
uint32_t *p = buf;
int32_t s = INT32_MAX;
if (info->swap_endianness) {
s = bswap32 (s);
}
for (i = 0; i < len * info->nchannels; i++) {
p[i] = s;
}
break;
default:
AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n",
info->bits);
break;
/*
* Capture
*/
static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioState *s,
struct audsettings *as)
{
CaptureVoiceOut *cap;
for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
if (audio_pcm_info_eq (&cap->hw.info, as)) {
return cap;
}
}
return NULL;
}
static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)
struct capture_callback *cb;
#ifdef DEBUG_CAPTURE
dolog ("notification %d sent\n", cmd);
#endif
for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {
cb->ops.notify (cb->opaque, cmd);
}
}
static void audio_capture_maybe_changed (CaptureVoiceOut *cap, int enabled)
{
if (cap->hw.enabled != enabled) {
audcnotification_e cmd;
cap->hw.enabled = enabled;
cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;
audio_notify_capture (cap, cmd);
}
}
static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)
{
HWVoiceOut *hw = &cap->hw;
SWVoiceOut *sw;
int enabled = 0;
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
if (sw->active) {
enabled = 1;
break;
}
}
audio_capture_maybe_changed (cap, enabled);
}
static void audio_detach_capture (HWVoiceOut *hw)
{
SWVoiceCap *sc = hw->cap_head.lh_first;
while (sc) {
SWVoiceCap *sc1 = sc->entries.le_next;
SWVoiceOut *sw = &sc->sw;
CaptureVoiceOut *cap = sc->cap;
int was_active = sw->active;
if (sw->rate) {
st_rate_stop (sw->rate);
sw->rate = NULL;
}
QLIST_REMOVE (sw, entries);
QLIST_REMOVE (sc, entries);
if (was_active) {
/* We have removed soft voice from the capture:
this might have changed the overall status of the capture
since this might have been the only active voice */
audio_recalc_and_notify_capture (cap);
}
sc = sc1;
static int audio_attach_capture (HWVoiceOut *hw)
CaptureVoiceOut *cap;
audio_detach_capture (hw);
for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {
sc = g_malloc0(sizeof(*sc));
sw->empty = 1;
sw->active = hw->enabled;
sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);
QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);
QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);
sw->name = g_strdup_printf ("for %p %d,%d,%d",
hw, sw->info.freq, sw->info.bits,
sw->info.nchannels);
dolog ("Added %s active = %d\n", sw->name, sw->active);
#endif
audio_capture_maybe_changed (cap, 1);
}
}
return 0;
}
/*
* Hard voice (capture)
*/
static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)
size_t m = hw->total_samples_captured;
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
if (sw->active) {
m = MIN (m, sw->total_hw_samples_acquired);
static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)
size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);
if (audio_bug(__func__, live > hw->conv_buf.size)) {
dolog("live=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)
{
size_t conv = 0;
STSampleBuffer *conv_buf = &hw->conv_buf;
while (samples) {
uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);
size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);
hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);
conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;
samples -= proc;
conv += proc;
}
return conv;
}
/*
* Soft voice (capture)
*/
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
static void audio_pcm_sw_resample_in(SWVoiceIn *sw,
size_t frames_in_max, size_t frames_out_max,
size_t *total_in, size_t *total_out)
{
HWVoiceIn *hw = sw->hw;
struct st_sample *src, *dst;
size_t live, rpos, frames_in, frames_out;
live = hw->total_samples_captured - sw->total_hw_samples_acquired;
rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);
/* resample conv_buf from rpos to end of buffer */
src = hw->conv_buf.buffer + rpos;
frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);
dst = sw->resample_buf.buffer;
frames_out = frames_out_max;
st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
rpos += frames_in;
*total_in = frames_in;
*total_out = frames_out;
/* resample conv_buf from start of buffer if there are input frames left */
if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {
src = hw->conv_buf.buffer;
frames_in = frames_in_max - frames_in;
dst += frames_out;
frames_out = frames_out_max - frames_out;
st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);
*total_in += frames_in;
*total_out += frames_out;
}
}
static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)
size_t live, frames_out_max, total_in, total_out;
live = hw->total_samples_captured - sw->total_hw_samples_acquired;
if (!live) {
return 0;
}
if (audio_bug(__func__, live > hw->conv_buf.size)) {
dolog("live_in=%zu hw->conv_buf.size=%zu\n", live, hw->conv_buf.size);
frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,
sw->resample_buf.size);
audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);
mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);
sw->clip(buf, sw->resample_buf.buffer, total_out);
sw->total_hw_samples_acquired += total_in;
return total_out * sw->info.bytes_per_frame;
/*
* Hard voice (playback)
*/
static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)
for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {
if (sw->active || !sw->empty) {
m = MIN (m, sw->total_hw_samples_mixed);
static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)
smin = audio_pcm_hw_find_min_out (hw, &nb_live1);
if (nb_live) {
*nb_live = nb_live1;
if (audio_bug(__func__, live > hw->mix_buf.size)) {
dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)
{
return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :
INT_MAX) / hw->info.bytes_per_frame;
}
static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)
{
size_t clipped = 0;
size_t pos = hw->mix_buf.pos;
st_sample *src = hw->mix_buf.buffer + pos;
uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);
size_t samples_till_end_of_buf = hw->mix_buf.size - pos;
size_t samples_to_clip = MIN(len, samples_till_end_of_buf);
hw->clip(dst, src, samples_to_clip);
pos = (pos + samples_to_clip) % hw->mix_buf.size;
len -= samples_to_clip;
clipped += samples_to_clip;
}
}
/*
* Soft voice (playback)
*/
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
static void audio_pcm_sw_resample_out(SWVoiceOut *sw,
size_t frames_in_max, size_t frames_out_max,
size_t *total_in, size_t *total_out)
{
HWVoiceOut *hw = sw->hw;
struct st_sample *src, *dst;
size_t live, wpos, frames_in, frames_out;
live = sw->total_hw_samples_mixed;
wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;
/* write to mix_buf from wpos to end of buffer */
src = sw->resample_buf.buffer;
frames_in = frames_in_max;
dst = hw->mix_buf.buffer + wpos;
frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);
st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
wpos += frames_out;
*total_in = frames_in;
*total_out = frames_out;
/* write to mix_buf from start of buffer if there are input frames left */
if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {
src += frames_in;
frames_in = frames_in_max - frames_in;
dst = hw->mix_buf.buffer;
frames_out = frames_out_max - frames_out;
st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);
*total_in += frames_in;
*total_out += frames_out;
}
}
static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)
HWVoiceOut *hw = sw->hw;
size_t live, dead, hw_free, sw_max, fe_max;
size_t frames_in_max, frames_out_max, total_in, total_out;
live = sw->total_hw_samples_mixed;
if (audio_bug(__func__, live > hw->mix_buf.size)) {
dolog("live=%zu hw->mix_buf.size=%zu\n", live, hw->mix_buf.size);
if (live == hw->mix_buf.size) {
dolog ("%s is full %zu\n", sw->name, live);
dead = hw->mix_buf.size - live;
hw_free = audio_pcm_hw_get_free(hw);
hw_free = hw_free > live ? hw_free - live : 0;
frames_out_max = MIN(dead, hw_free);
sw_max = st_rate_frames_in(sw->rate, frames_out_max);
fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,
sw->resample_buf.size);
frames_in_max = MIN(sw_max, fe_max);
if (!frames_in_max) {
return 0;
if (frames_in_max > sw->resample_buf.pos) {
sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,
buf, frames_in_max - sw->resample_buf.pos);
if (!sw->hw->pcm_ops->volume_out) {
mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,
frames_in_max - sw->resample_buf.pos, &sw->vol);
}
}
audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,
&total_in, &total_out);
sw->total_hw_samples_mixed += total_out;
sw->empty = sw->total_hw_samples_mixed == 0;
/*
* Upsampling may leave one audio frame in the resample buffer. Decrement
* total_in by one if there was a leftover frame from the previous resample
* pass in the resample buffer. Increment total_in by one if the current
* resample pass left one frame in the resample buffer.
*/
if (frames_in_max - total_in == 1) {
/* copy one leftover audio frame to the beginning of the buffer */
*sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);
total_in += 1 - sw->resample_buf.pos;
sw->resample_buf.pos = 1;
} else if (total_in >= sw->resample_buf.pos) {
total_in -= sw->resample_buf.pos;
sw->resample_buf.pos = 0;
}
#ifdef DEBUG_OUT
dolog (
"%s: write size %zu written %zu total mixed %zu\n",
SW_NAME(sw),
buf_len / sw->info.bytes_per_frame,
total_in,
return total_in * sw->info.bytes_per_frame;
#ifdef DEBUG_AUDIO
static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)
dolog("%s: bits %d, sign %d, float %d, freq %d, nchan %d\n",
cap, info->bits, info->is_signed, info->is_float, info->freq,
info->nchannels);
#define DAC
#include "audio_template.h"
#undef DAC
#include "audio_template.h"
static int audio_is_timer_needed(AudioState *s)
{
HWVoiceIn *hwi = NULL;
HWVoiceOut *hwo = NULL;
while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {
if (!hwo->poll_mode) {
return 1;
}
while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {
if (!hwi->poll_mode) {
return 1;
}
static void audio_reset_timer (AudioState *s)
timer_mod_anticipate_ns(s->ts,
qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);
if (!s->timer_running) {
s->timer_running = true;
s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
trace_audio_timer_start(s->period_ticks / SCALE_MS);
}
} else {
timer_del(s->ts);
if (s->timer_running) {
s->timer_running = false;
static void audio_timer (void *opaque)
{
AudioState *s = opaque;
now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
if (diff > s->period_ticks * 3 / 2) {
trace_audio_timer_delayed(diff / SCALE_MS);
}
audio_reset_timer(s);
size_t AUD_write(SWVoiceOut *sw, void *buf, size_t size)
if (!sw) {
/* XXX: Consider options */
return size;
}
dolog ("Writing to disabled voice %s\n", SW_NAME (sw));
if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {
return audio_pcm_sw_write(sw, buf, size);
} else {
return hw->pcm_ops->write(hw, buf, size);
}
size_t AUD_read(SWVoiceIn *sw, void *buf, size_t size)
if (!sw) {
/* XXX: Consider options */
return size;
dolog ("Reading from disabled voice %s\n", SW_NAME (sw));
if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {
return audio_pcm_sw_read(sw, buf, size);
} else {
return hw->pcm_ops->read(hw, buf, size);
}
int AUD_get_buffer_size_out(SWVoiceOut *sw)
return sw->hw->samples * sw->hw->info.bytes_per_frame;
}
void AUD_set_active_out (SWVoiceOut *sw, int on)
{
HWVoiceOut *hw;
if (on) {
hw->pending_disable = 0;
if (!hw->enabled) {
hw->enabled = 1;
if (hw->pcm_ops->enable_out) {
hw->pcm_ops->enable_out(hw, true);
}
if (hw->enabled) {
int nb_active = 0;
for (temp_sw = hw->sw_head.lh_first; temp_sw;
temp_sw = temp_sw->entries.le_next) {
nb_active += temp_sw->active != 0;
}
hw->pending_disable = nb_active == 1;
}
for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {
sc->sw.active = hw->enabled;
audio_capture_maybe_changed (sc->cap, 1);
sw->active = on;
}
}
void AUD_set_active_in (SWVoiceIn *sw, int on)
{
HWVoiceIn *hw;
if (!sw) {
return;
if (on) {
if (!hw->enabled) {
hw->enabled = 1;
if (hw->pcm_ops->enable_in) {
hw->pcm_ops->enable_in(hw, true);
}
sw->total_hw_samples_acquired = hw->total_samples_captured;
for (temp_sw = hw->sw_head.lh_first; temp_sw;
temp_sw = temp_sw->entries.le_next) {
nb_active += temp_sw->active != 0;
if (hw->pcm_ops->enable_in) {
hw->pcm_ops->enable_in(hw, false);
}