From nobody Sun Jan 25 12:01:03 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1769155107; cv=none; d=zohomail.com; s=zohoarc; b=k4kb6uZHI/oxO8YzklnvQLETS49vOsIeEIqCqvaUCrE9Zfv9X/X+LRFFasoveUowQi4KkjW91U2JU70gaWFWkIygJBjM4HqRhtBmM390qKaRXRLtAO2QI0EpSHLcy1akNdErJm4HKW25fr8p8seIO0QRrzcFVl6ofM0QAdJw3cI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1769155107; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=ZO4boBjPP1MjcJbe/8Zdgm9ExEtAR+bZVzt1t/3HsLI=; b=GEOkxSdxM6aB6hKXTCWDMMU0uQDv9A5omxQpCUO0qV3femGKv1GMNJtb94B/4omQXBRDduwBwkY965XZk9zRXPCUKnhjzzFlw7M6JdvbkGGAgfzTMgOxNf+Q0GzBUPZKzDBRg8Ou6QZSTd1Ku2HXZr5dvR6X/6+F9mqEQ+/dbR0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1769155107919761.4359698003494; Thu, 22 Jan 2026 23:58:27 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vjC0n-0000hk-6E; Fri, 23 Jan 2026 02:55:22 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vjBzF-0007Je-M0 for qemu-devel@nongnu.org; Fri, 23 Jan 2026 02:53:46 -0500 Received: from us-smtp-delivery-124.mimecast.com ([170.10.129.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vjBzB-0002Uv-5s for qemu-devel@nongnu.org; Fri, 23 Jan 2026 02:53:45 -0500 Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-615-52bOf5dbNU-Q3X5MjNb-5A-1; Fri, 23 Jan 2026 02:53:35 -0500 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 03F7118005B2; Fri, 23 Jan 2026 07:53:35 +0000 (UTC) Received: from localhost (unknown [10.45.242.5]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id EE6CB30002D1; Fri, 23 Jan 2026 07:53:31 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1769154819; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=ZO4boBjPP1MjcJbe/8Zdgm9ExEtAR+bZVzt1t/3HsLI=; b=HHBdaIsr7XDbm6CoAhxqIuH7g2OAb27/kokp9f1AFQmVkHedYNKvNh+lVrzbAooGu+/O89 mU0aj1V7T0CXprCKC0Fa6VNWAscjUxcDRxQb5oTMx8zuf2Y8QskJoSVENqhc2iWW/wfmy1 GxRPt/Dtz1lOtSbdDDr3lsqxZCxQKW0= X-MC-Unique: 52bOf5dbNU-Q3X5MjNb-5A-1 X-Mimecast-MFC-AGG-ID: 52bOf5dbNU-Q3X5MjNb-5A_1769154815 From: marcandre.lureau@redhat.com To: qemu-devel@nongnu.org Cc: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Gerd Hoffmann , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Subject: [PATCH 36/37] audio: split AudioMixengBackend code in audio-mixeng-be.c Date: Fri, 23 Jan 2026 11:49:39 +0400 Message-ID: <20260123074945.2563196-37-marcandre.lureau@redhat.com> In-Reply-To: <20260123074945.2563196-1-marcandre.lureau@redhat.com> References: <20260123074945.2563196-1-marcandre.lureau@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.129.124; envelope-from=marcandre.lureau@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -21 X-Spam_score: -2.2 X-Spam_bar: -- X-Spam_report: (-2.2 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.07, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=0.001, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1769155110937154100 From: Marc-Andr=C3=A9 Lureau Allow to build the audio/ base classes without the resampling/mixing/queuing code. Signed-off-by: Marc-Andr=C3=A9 Lureau --- audio/audio_int.h | 2 - include/qemu/audio.h | 2 + audio/audio-mixeng-be.c | 1979 ++++++++++++++++++++++++++++++++++ audio/audio.c | 2226 ++++----------------------------------- audio/meson.build | 1 + 5 files changed, 2159 insertions(+), 2051 deletions(-) create mode 100644 audio/audio-mixeng-be.c diff --git a/audio/audio_int.h b/audio/audio_int.h index 250fd45238d..6ecd75c4fbf 100644 --- a/audio/audio_int.h +++ b/audio/audio_int.h @@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond); =20 void audio_run(AudioMixengBackend *s, const char *msg); =20 -const char *audio_application_name(void); - typedef struct RateCtl { int64_t start_ticks; int64_t bytes_sent; diff --git a/include/qemu/audio.h b/include/qemu/audio.h index 42f97f732a6..dfe247ab8c4 100644 --- a/include/qemu/audio.h +++ b/include/qemu/audio.h @@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be, Error **errp); #endif =20 +const char *audio_application_name(void); + #define DEFINE_AUDIO_PROPERTIES(_s, _f) \ DEFINE_PROP_AUDIODEV("audiodev", _s, _f) =20 diff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c new file mode 100644 index 00000000000..8ebceb968ce --- /dev/null +++ b/audio/audio-mixeng-be.c @@ -0,0 +1,1979 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2003-2005 Vassili Karpov (malc) + */ + +#include "qemu/osdep.h" +#include "qemu/audio.h" +#include "migration/vmstate.h" +#include "qemu/timer.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 "qobject/qdict.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/help_option.h" +#include "qom/object.h" +#include "system/system.h" +#include "system/replay.h" +#include "system/runstate.h" +#include "trace.h" + +#define AUDIO_CAP "audio" +#include "audio_int.h" + +/* #define DEBUG_OUT */ +/* #define DEBUG_CAPTURE */ +/* #define DEBUG_POLL */ + +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" + +const struct mixeng_volume nominal_volume =3D { + .mute =3D 0, +#ifdef FLOAT_MIXENG + .r =3D 1.0, + .l =3D 1.0, +#else + .r =3D 1ULL << 32, + .l =3D 1ULL << 32, +#endif +}; + +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 =3D 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"); + } + + return cond; +} + +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); + return 0; + } +} + +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 (const struct audsettings *as) +{ + dolog ("frequency=3D%d nchannels=3D%d fmt=3D", as->freq, as->nchannels= ); + + switch (as->fmt) { + case AUDIO_FORMAT_S8: + AUD_log (NULL, "S8"); + break; + case AUDIO_FORMAT_U8: + AUD_log (NULL, "U8"); + break; + case AUDIO_FORMAT_S16: + AUD_log (NULL, "S16"); + break; + case AUDIO_FORMAT_U16: + AUD_log (NULL, "U16"); + break; + case AUDIO_FORMAT_S32: + AUD_log (NULL, "S32"); + break; + case AUDIO_FORMAT_U32: + AUD_log (NULL, "U32"); + break; + case AUDIO_FORMAT_F32: + AUD_log (NULL, "F32"); + break; + default: + AUD_log (NULL, "invalid(%d)", as->fmt); + break; + } + + AUD_log (NULL, " endianness=3D"); + switch (as->endianness) { + case 0: + AUD_log (NULL, "little"); + break; + case 1: + AUD_log (NULL, "big"); + break; + default: + AUD_log (NULL, "invalid"); + break; + } + AUD_log (NULL, "\n"); +} + +static int audio_validate_settings (const struct audsettings *as) +{ + int invalid; + + invalid =3D as->nchannels < 1; + invalid |=3D as->endianness !=3D 0 && as->endianness !=3D 1; + + switch (as->fmt) { + 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 =3D 1; + break; + } + + invalid |=3D as->freq <=3D 0; + return invalid ? -1 : 0; +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct au= dsettings *as) +{ + int bits =3D 8; + bool is_signed =3D false, is_float =3D false; + + switch (as->fmt) { + case AUDIO_FORMAT_S8: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U8: + break; + + case AUDIO_FORMAT_S16: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U16: + bits =3D 16; + break; + + case AUDIO_FORMAT_F32: + is_float =3D true; + /* fall through */ + case AUDIO_FORMAT_S32: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U32: + bits =3D 32; + break; + + default: + abort(); + } + return info->freq =3D=3D as->freq + && info->nchannels =3D=3D as->nchannels + && info->is_signed =3D=3D is_signed + && info->is_float =3D=3D is_float + && info->bits =3D=3D bits + && info->swap_endianness =3D=3D (as->endianness !=3D HOST_BIG_ENDI= AN); +} + +void audio_pcm_init_info (struct audio_pcm_info *info, const struct audset= tings *as) +{ + int bits =3D 8, mul; + bool is_signed =3D false, is_float =3D false; + + switch (as->fmt) { + case AUDIO_FORMAT_S8: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U8: + mul =3D 1; + break; + + case AUDIO_FORMAT_S16: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U16: + bits =3D 16; + mul =3D 2; + break; + + case AUDIO_FORMAT_F32: + is_float =3D true; + /* fall through */ + case AUDIO_FORMAT_S32: + is_signed =3D true; + /* fall through */ + case AUDIO_FORMAT_U32: + bits =3D 32; + mul =3D 4; + break; + + default: + abort(); + } + + info->freq =3D as->freq; + info->bits =3D bits; + info->is_signed =3D is_signed; + info->is_float =3D is_float; + info->nchannels =3D as->nchannels; + info->bytes_per_frame =3D as->nchannels * mul; + info->bytes_per_second =3D info->freq * info->bytes_per_frame; + info->swap_endianness =3D (as->endianness !=3D HOST_BIG_ENDIAN); +} + +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); + } else { + switch (info->bits) { + case 8: + memset(buf, 0x80, len * info->bytes_per_frame); + break; + + case 16: + { + int i; + uint16_t *p =3D buf; + short s =3D INT16_MAX; + + if (info->swap_endianness) { + s =3D bswap16 (s); + } + + for (i =3D 0; i < len * info->nchannels; i++) { + p[i] =3D s; + } + } + break; + + case 32: + { + int i; + uint32_t *p =3D buf; + int32_t s =3D INT32_MAX; + + if (info->swap_endianness) { + s =3D bswap32 (s); + } + + for (i =3D 0; i < len * info->nchannels; i++) { + p[i] =3D 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(AudioMixengBackend= *s, + struct audsettings= *as) +{ + CaptureVoiceOut *cap; + + for (cap =3D s->cap_head.lh_first; cap; cap =3D 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 =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_next) { + cb->ops.notify (cb->opaque, cmd); + } +} + +static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled) +{ + if (cap->hw.enabled !=3D enabled) { + audcnotification_e cmd; + cap->hw.enabled =3D enabled; + cmd =3D enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; + audio_notify_capture (cap, cmd); + } +} + +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) +{ + HWVoiceOut *hw =3D &cap->hw; + SWVoiceOut *sw; + bool enabled =3D false; + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (sw->active) { + enabled =3D true; + break; + } + } + audio_capture_maybe_changed (cap, enabled); +} + +static void audio_detach_capture (HWVoiceOut *hw) +{ + SWVoiceCap *sc =3D hw->cap_head.lh_first; + + while (sc) { + SWVoiceCap *sc1 =3D sc->entries.le_next; + SWVoiceOut *sw =3D &sc->sw; + CaptureVoiceOut *cap =3D sc->cap; + int was_active =3D sw->active; + + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate =3D NULL; + } + + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + 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 =3D sc1; + } +} + +static int audio_attach_capture (HWVoiceOut *hw) +{ + AudioMixengBackend *s =3D hw->s; + CaptureVoiceOut *cap; + + audio_detach_capture (hw); + for (cap =3D s->cap_head.lh_first; cap; cap =3D cap->entries.le_next) { + SWVoiceCap *sc; + SWVoiceOut *sw; + HWVoiceOut *hw_cap =3D &cap->hw; + + sc =3D g_malloc0(sizeof(*sc)); + + sc->cap =3D cap; + sw =3D &sc->sw; + sw->hw =3D hw_cap; + sw->info =3D hw->info; + sw->empty =3D true; + sw->active =3D hw->enabled; + sw->vol =3D nominal_volume; + sw->rate =3D 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); +#ifdef DEBUG_CAPTURE + sw->name =3D g_strdup_printf ("for %p %d,%d,%d", + hw, sw->info.freq, sw->info.bits, + sw->info.nchannels); + dolog ("Added %s active =3D %d\n", sw->name, sw->active); +#endif + if (sw->active) { + audio_capture_maybe_changed (cap, 1); + } + } + return 0; +} + +/* + * Hard voice (capture) + */ +static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw) +{ + SWVoiceIn *sw; + size_t m =3D hw->total_samples_captured; + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (sw->active) { + m =3D MIN (m, sw->total_hw_samples_acquired); + } + } + return m; +} + +static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw) +{ + size_t live =3D hw->total_samples_captured - audio_pcm_hw_find_min_in = (hw); + if (audio_bug(__func__, live > hw->conv_buf.size)) { + dolog("live=3D%zu hw->conv_buf.size=3D%zu\n", live, hw->conv_buf.s= ize); + return 0; + } + return live; +} + +static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t sa= mples) +{ + size_t conv =3D 0; + STSampleBuffer *conv_buf =3D &hw->conv_buf; + + while (samples) { + uint8_t *src =3D advance(pcm_buf, conv * hw->info.bytes_per_frame); + size_t proc =3D MIN(samples, conv_buf->size - conv_buf->pos); + + hw->conv(conv_buf->buffer + conv_buf->pos, src, proc); + conv_buf->pos =3D (conv_buf->pos + proc) % conv_buf->size; + samples -=3D proc; + conv +=3D proc; + } + + return conv; +} + +/* + * Soft voice (capture) + */ +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 =3D sw->hw; + struct st_sample *src, *dst; + size_t live, rpos, frames_in, frames_out; + + live =3D hw->total_samples_captured - sw->total_hw_samples_acquired; + rpos =3D audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size); + + /* resample conv_buf from rpos to end of buffer */ + src =3D hw->conv_buf.buffer + rpos; + frames_in =3D MIN(frames_in_max, hw->conv_buf.size - rpos); + dst =3D sw->resample_buf.buffer; + frames_out =3D frames_out_max; + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); + rpos +=3D frames_in; + *total_in =3D frames_in; + *total_out =3D frames_out; + + /* resample conv_buf from start of buffer if there are input frames le= ft */ + if (frames_in_max - frames_in && rpos =3D=3D hw->conv_buf.size) { + src =3D hw->conv_buf.buffer; + frames_in =3D frames_in_max - frames_in; + dst +=3D frames_out; + frames_out =3D frames_out_max - frames_out; + st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); + *total_in +=3D frames_in; + *total_out +=3D frames_out; + } +} + +static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len) +{ + HWVoiceIn *hw =3D sw->hw; + size_t live, frames_out_max, total_in, total_out; + + live =3D 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=3D%zu hw->conv_buf.size=3D%zu\n", live, hw->conv_bu= f.size); + return 0; + } + + frames_out_max =3D 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_o= ut); + + if (!hw->pcm_ops->volume_in) { + mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol); + } + sw->clip(buf, sw->resample_buf.buffer, total_out); + + sw->total_hw_samples_acquired +=3D 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) +{ + SWVoiceOut *sw; + size_t m =3D SIZE_MAX; + int nb_live =3D 0; + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (sw->active || !sw->empty) { + m =3D MIN (m, sw->total_hw_samples_mixed); + nb_live +=3D 1; + } + } + + *nb_livep =3D nb_live; + return m; +} + +static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) +{ + size_t smin; + int nb_live1; + + smin =3D audio_pcm_hw_find_min_out (hw, &nb_live1); + if (nb_live) { + *nb_live =3D nb_live1; + } + + if (nb_live1) { + size_t live =3D smin; + + if (audio_bug(__func__, live > hw->mix_buf.size)) { + dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf= .size); + return 0; + } + return live; + } + return 0; +} + +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 le= n) +{ + size_t clipped =3D 0; + size_t pos =3D hw->mix_buf.pos; + + while (len) { + st_sample *src =3D hw->mix_buf.buffer + pos; + uint8_t *dst =3D advance(pcm_buf, clipped * hw->info.bytes_per_fra= me); + size_t samples_till_end_of_buf =3D hw->mix_buf.size - pos; + size_t samples_to_clip =3D MIN(len, samples_till_end_of_buf); + + hw->clip(dst, src, samples_to_clip); + + pos =3D (pos + samples_to_clip) % hw->mix_buf.size; + len -=3D samples_to_clip; + clipped +=3D samples_to_clip; + } +} + +/* + * Soft voice (playback) + */ +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 =3D sw->hw; + struct st_sample *src, *dst; + size_t live, wpos, frames_in, frames_out; + + live =3D sw->total_hw_samples_mixed; + wpos =3D (hw->mix_buf.pos + live) % hw->mix_buf.size; + + /* write to mix_buf from wpos to end of buffer */ + src =3D sw->resample_buf.buffer; + frames_in =3D frames_in_max; + dst =3D hw->mix_buf.buffer + wpos; + frames_out =3D MIN(frames_out_max, hw->mix_buf.size - wpos); + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); + wpos +=3D frames_out; + *total_in =3D frames_in; + *total_out =3D frames_out; + + /* write to mix_buf from start of buffer if there are input frames lef= t */ + if (frames_in_max - frames_in > 0 && wpos =3D=3D hw->mix_buf.size) { + src +=3D frames_in; + frames_in =3D frames_in_max - frames_in; + dst =3D hw->mix_buf.buffer; + frames_out =3D frames_out_max - frames_out; + st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); + *total_in +=3D frames_in; + *total_out +=3D frames_out; + } +} + +static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len) +{ + HWVoiceOut *hw =3D 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 =3D sw->total_hw_samples_mixed; + if (audio_bug(__func__, live > hw->mix_buf.size)) { + dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf.siz= e); + return 0; + } + + if (live =3D=3D hw->mix_buf.size) { +#ifdef DEBUG_OUT + dolog ("%s is full %zu\n", sw->name, live); +#endif + return 0; + } + + dead =3D hw->mix_buf.size - live; + hw_free =3D audio_pcm_hw_get_free(hw); + hw_free =3D hw_free > live ? hw_free - live : 0; + frames_out_max =3D MIN(dead, hw_free); + sw_max =3D st_rate_frames_in(sw->rate, frames_out_max); + fe_max =3D MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.p= os, + sw->resample_buf.size); + frames_in_max =3D 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 +=3D total_out; + sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; + + /* + * Upsampling may leave one audio frame in the resample buffer. Decrem= ent + * total_in by one if there was a leftover frame from the previous res= ample + * pass in the resample buffer. Increment total_in by one if the curre= nt + * resample pass left one frame in the resample buffer. + */ + if (frames_in_max - total_in =3D=3D 1) { + /* copy one leftover audio frame to the beginning of the buffer */ + *sw->resample_buf.buffer =3D *(sw->resample_buf.buffer + total_in); + total_in +=3D 1 - sw->resample_buf.pos; + sw->resample_buf.pos =3D 1; + } else if (total_in >=3D sw->resample_buf.pos) { + total_in -=3D sw->resample_buf.pos; + sw->resample_buf.pos =3D 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, + sw->total_hw_samples_mixed + ); +#endif + + 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); +} +#endif + +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" + +/* + * Timer + */ +static int audio_is_timer_needed(AudioMixengBackend *s) +{ + HWVoiceIn *hwi =3D NULL; + HWVoiceOut *hwo =3D NULL; + + while ((hwo =3D audio_pcm_hw_find_any_enabled_out(s, hwo))) { + if (!hwo->poll_mode) { + return 1; + } + } + while ((hwi =3D audio_pcm_hw_find_any_enabled_in(s, hwi))) { + if (!hwi->poll_mode) { + return 1; + } + } + return 0; +} + +static void audio_reset_timer(AudioMixengBackend *s) +{ + if (audio_is_timer_needed(s)) { + timer_mod_anticipate_ns(s->ts, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks); + if (!s->timer_running) { + s->timer_running =3D true; + s->timer_last =3D 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 =3D false; + trace_audio_timer_stop(); + } + } +} + +static void audio_timer (void *opaque) +{ + int64_t now, diff; + AudioMixengBackend *s =3D opaque; + + now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + diff =3D now - s->timer_last; + if (diff > s->period_ticks * 3 / 2) { + trace_audio_timer_delayed(diff / SCALE_MS); + } + s->timer_last =3D now; + + audio_run(s, "timer"); + audio_reset_timer(s); +} + +/* + * Public API + */ +static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw, + void *buf, size_t size) +{ + HWVoiceOut *hw; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + hw =3D sw->hw; + + if (!hw->enabled) { + dolog("Writing to disabled voice %s\n", SW_NAME(sw)); + return 0; + } + + 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); + } +} + +static size_t audio_mixeng_backend_read(AudioBackend *be, + SWVoiceIn *sw, void *buf, size_t s= ize) +{ + HWVoiceIn *hw; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + hw =3D sw->hw; + + if (!hw->enabled) { + dolog("Reading from disabled voice %s\n", SW_NAME(sw)); + return 0; + } + + 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); + } + +} + +static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVo= iceOut *sw) +{ + if (!sw) { + return 0; + } + + if (audio_get_pdo_out(sw->s->dev)->mixing_engine) { + return sw->resample_buf.size * sw->info.bytes_per_frame; + } + + return sw->hw->samples * sw->hw->info.bytes_per_frame; +} + +static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceO= ut *sw, + bool on) +{ + HWVoiceOut *hw; + + if (!sw) { + return; + } + + hw =3D sw->hw; + if (sw->active !=3D on) { + AudioMixengBackend *s =3D sw->s; + SWVoiceOut *temp_sw; + SWVoiceCap *sc; + + if (on) { + hw->pending_disable =3D 0; + if (!hw->enabled) { + hw->enabled =3D true; + if (runstate_is_running()) { + if (hw->pcm_ops->enable_out) { + hw->pcm_ops->enable_out(hw, true); + } + audio_reset_timer (s); + } + } + } else { + if (hw->enabled) { + int nb_active =3D 0; + + for (temp_sw =3D hw->sw_head.lh_first; temp_sw; + temp_sw =3D temp_sw->entries.le_next) { + nb_active +=3D temp_sw->active !=3D 0; + } + + hw->pending_disable =3D nb_active =3D=3D 1; + } + } + + for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_next)= { + sc->sw.active =3D hw->enabled; + if (hw->enabled) { + audio_capture_maybe_changed (sc->cap, 1); + } + } + sw->active =3D on; + } + +} + +static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn= *sw, bool on) +{ + HWVoiceIn *hw; + + if (!sw) { + return; + } + + hw =3D sw->hw; + if (sw->active !=3D on) { + AudioMixengBackend *s =3D sw->s; + SWVoiceIn *temp_sw; + + if (on) { + if (!hw->enabled) { + hw->enabled =3D true; + if (runstate_is_running()) { + if (hw->pcm_ops->enable_in) { + hw->pcm_ops->enable_in(hw, true); + } + audio_reset_timer (s); + } + } + sw->total_hw_samples_acquired =3D hw->total_samples_captured; + } else { + if (hw->enabled) { + int nb_active =3D 0; + + for (temp_sw =3D hw->sw_head.lh_first; temp_sw; + temp_sw =3D temp_sw->entries.le_next) { + nb_active +=3D temp_sw->active !=3D 0; + } + + if (nb_active =3D=3D 1) { + hw->enabled =3D false; + if (hw->pcm_ops->enable_in) { + hw->pcm_ops->enable_in(hw, false); + } + } + } + } + sw->active =3D on; + } +} + +static size_t audio_get_avail(SWVoiceIn *sw) +{ + size_t live; + + if (!sw) { + return 0; + } + + live =3D sw->hw->total_samples_captured - sw->total_hw_samples_acquire= d; + if (audio_bug(__func__, live > sw->hw->conv_buf.size)) { + dolog("live=3D%zu sw->hw->conv_buf.size=3D%zu\n", live, + sw->hw->conv_buf.size); + return 0; + } + + ldebug ( + "%s: get_avail live %zu frontend frames %u\n", + SW_NAME (sw), + live, st_rate_frames_out(sw->rate, live) + ); + + return live; +} + +static size_t audio_get_free(SWVoiceOut *sw) +{ + size_t live, dead; + + if (!sw) { + return 0; + } + + live =3D sw->total_hw_samples_mixed; + + if (audio_bug(__func__, live > sw->hw->mix_buf.size)) { + dolog("live=3D%zu sw->hw->mix_buf.size=3D%zu\n", live, + sw->hw->mix_buf.size); + return 0; + } + + dead =3D sw->hw->mix_buf.size - live; + +#ifdef DEBUG_OUT + dolog("%s: get_free live %zu dead %zu frontend frames %u\n", + SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead)); +#endif + + return dead; +} + +static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos, + size_t samples) +{ + size_t n; + + if (hw->enabled) { + SWVoiceCap *sc; + + for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_next)= { + SWVoiceOut *sw =3D &sc->sw; + size_t rpos2 =3D rpos; + + n =3D samples; + while (n) { + size_t till_end_of_hw =3D hw->mix_buf.size - rpos2; + size_t to_read =3D MIN(till_end_of_hw, n); + size_t live, frames_in, frames_out; + + sw->resample_buf.buffer =3D hw->mix_buf.buffer + rpos2; + sw->resample_buf.size =3D to_read; + live =3D sw->total_hw_samples_mixed; + + audio_pcm_sw_resample_out(sw, + to_read, sw->hw->mix_buf.size - = live, + &frames_in, &frames_out); + + sw->total_hw_samples_mixed +=3D frames_out; + sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; + + if (to_read - frames_in) { + dolog("Could not mix %zu frames into a capture " + "buffer, mixed %zu\n", + to_read, frames_in); + break; + } + n -=3D to_read; + rpos2 =3D (rpos2 + to_read) % hw->mix_buf.size; + } + } + } + + n =3D MIN(samples, hw->mix_buf.size - rpos); + mixeng_clear(hw->mix_buf.buffer + rpos, n); + mixeng_clear(hw->mix_buf.buffer, samples - n); +} + +static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live) +{ + size_t clipped =3D 0; + + while (live) { + size_t size =3D live * hw->info.bytes_per_frame; + size_t decr, proc; + void *buf =3D hw->pcm_ops->get_buffer_out(hw, &size); + + if (size =3D=3D 0) { + break; + } + + decr =3D MIN(size / hw->info.bytes_per_frame, live); + if (buf) { + audio_pcm_hw_clip_out(hw, buf, decr); + } + proc =3D hw->pcm_ops->put_buffer_out(hw, buf, + decr * hw->info.bytes_per_frame= ) / + hw->info.bytes_per_frame; + + live -=3D proc; + clipped +=3D proc; + hw->mix_buf.pos =3D (hw->mix_buf.pos + proc) % hw->mix_buf.size; + + if (proc =3D=3D 0 || proc < decr) { + break; + } + } + + if (hw->pcm_ops->run_buffer_out) { + hw->pcm_ops->run_buffer_out(hw); + } + + return clipped; +} + +static void audio_run_out(AudioMixengBackend *s) +{ + HWVoiceOut *hw =3D NULL; + SWVoiceOut *sw; + + while ((hw =3D audio_pcm_hw_find_any_enabled_out(s, hw))) { + size_t played, live, prev_rpos; + size_t hw_free =3D audio_pcm_hw_get_free(hw); + int nb_live; + + if (!audio_get_pdo_out(s->dev)->mixing_engine) { + /* there is exactly 1 sw for each hw with no mixeng */ + sw =3D hw->sw_head.lh_first; + + if (hw->pending_disable) { + hw->enabled =3D false; + hw->pending_disable =3D false; + if (hw->pcm_ops->enable_out) { + hw->pcm_ops->enable_out(hw, false); + } + } + + if (sw->active) { + sw->callback.fn(sw->callback.opaque, + hw_free * sw->info.bytes_per_frame); + } + + if (hw->pcm_ops->run_buffer_out) { + hw->pcm_ops->run_buffer_out(hw); + } + + continue; + } + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (sw->active) { + size_t sw_free =3D audio_get_free(sw); + size_t free; + + if (hw_free > sw->total_hw_samples_mixed) { + free =3D st_rate_frames_in(sw->rate, + MIN(sw_free, hw_free - sw->total_hw_samples_mixed)= ); + } else { + free =3D 0; + } + if (free > sw->resample_buf.pos) { + free =3D MIN(free, sw->resample_buf.size) + - sw->resample_buf.pos; + sw->callback.fn(sw->callback.opaque, + free * sw->info.bytes_per_frame); + } + } + } + + live =3D audio_pcm_hw_get_live_out (hw, &nb_live); + if (!nb_live) { + live =3D 0; + } + + if (audio_bug(__func__, live > hw->mix_buf.size)) { + dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf= .size); + continue; + } + + if (hw->pending_disable && !nb_live) { + SWVoiceCap *sc; +#ifdef DEBUG_OUT + dolog ("Disabling voice\n"); +#endif + hw->enabled =3D false; + hw->pending_disable =3D false; + if (hw->pcm_ops->enable_out) { + hw->pcm_ops->enable_out(hw, false); + } + for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_n= ext) { + sc->sw.active =3D false; + audio_recalc_and_notify_capture (sc->cap); + } + continue; + } + + if (!live) { + if (hw->pcm_ops->run_buffer_out) { + hw->pcm_ops->run_buffer_out(hw); + } + continue; + } + + prev_rpos =3D hw->mix_buf.pos; + played =3D audio_pcm_hw_run_out(hw, live); + replay_audio_out(&played); + if (audio_bug(__func__, hw->mix_buf.pos >=3D hw->mix_buf.size)) { + dolog("hw->mix_buf.pos=3D%zu hw->mix_buf.size=3D%zu played=3D%= zu\n", + hw->mix_buf.pos, hw->mix_buf.size, played); + hw->mix_buf.pos =3D 0; + } + +#ifdef DEBUG_OUT + dolog("played=3D%zu\n", played); +#endif + + if (played) { + audio_capture_mix_and_clear (hw, prev_rpos, played); + } + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) { + dolog("played=3D%zu sw->total_hw_samples_mixed=3D%zu\n", + played, sw->total_hw_samples_mixed); + played =3D sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -=3D played; + + if (!sw->total_hw_samples_mixed) { + sw->empty =3D true; + } + } + } +} + +static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples) +{ + size_t conv =3D 0; + + if (hw->pcm_ops->run_buffer_in) { + hw->pcm_ops->run_buffer_in(hw); + } + + while (samples) { + size_t proc; + size_t size =3D samples * hw->info.bytes_per_frame; + void *buf =3D hw->pcm_ops->get_buffer_in(hw, &size); + + assert(size % hw->info.bytes_per_frame =3D=3D 0); + if (size =3D=3D 0) { + break; + } + + proc =3D audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_f= rame); + + samples -=3D proc; + conv +=3D proc; + hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_fram= e); + } + + return conv; +} + +static void audio_run_in(AudioMixengBackend *s) +{ + HWVoiceIn *hw =3D NULL; + + if (!audio_get_pdo_in(s->dev)->mixing_engine) { + while ((hw =3D audio_pcm_hw_find_any_enabled_in(s, hw))) { + /* there is exactly 1 sw for each hw with no mixeng */ + SWVoiceIn *sw =3D hw->sw_head.lh_first; + if (sw->active) { + sw->callback.fn(sw->callback.opaque, INT_MAX); + } + } + return; + } + + while ((hw =3D audio_pcm_hw_find_any_enabled_in(s, hw))) { + SWVoiceIn *sw; + size_t captured =3D 0, min; + int pos; + + if (replay_mode !=3D REPLAY_MODE_PLAY) { + captured =3D audio_pcm_hw_run_in( + hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw)); + } + + replay_audio_in_start(&captured); + assert(captured <=3D hw->conv_buf.size); + if (replay_mode =3D=3D REPLAY_MODE_PLAY) { + hw->conv_buf.pos =3D (hw->conv_buf.pos + captured) % hw->conv_= buf.size; + } + for (pos =3D (hw->conv_buf.pos - captured + hw->conv_buf.size) % h= w->conv_buf.size; + pos !=3D hw->conv_buf.pos; + pos =3D (pos + 1) % hw->conv_buf.size) { + uint64_t left, right; + + if (replay_mode =3D=3D REPLAY_MODE_RECORD) { + audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left= , &right); + } + replay_audio_in_sample_lr(&left, &right); + if (replay_mode =3D=3D REPLAY_MODE_PLAY) { + audio_sample_from_uint64(hw->conv_buf.buffer, pos, lef= t, right); + } + } + replay_audio_in_finish(); + + min =3D audio_pcm_hw_find_min_in (hw); + hw->total_samples_captured +=3D captured - min; + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + sw->total_hw_samples_acquired -=3D min; + + if (sw->active) { + size_t sw_avail =3D audio_get_avail(sw); + size_t avail; + + avail =3D st_rate_frames_out(sw->rate, sw_avail); + if (avail > 0) { + avail =3D MIN(avail, sw->resample_buf.size); + sw->callback.fn(sw->callback.opaque, + avail * sw->info.bytes_per_frame); + } + } + } + } +} + +static void audio_run_capture(AudioMixengBackend *s) +{ + CaptureVoiceOut *cap; + + for (cap =3D s->cap_head.lh_first; cap; cap =3D cap->entries.le_next) { + size_t live, rpos, captured; + HWVoiceOut *hw =3D &cap->hw; + SWVoiceOut *sw; + + captured =3D live =3D audio_pcm_hw_get_live_out (hw, NULL); + rpos =3D hw->mix_buf.pos; + while (live) { + size_t left =3D hw->mix_buf.size - rpos; + size_t to_capture =3D MIN(live, left); + struct st_sample *src; + struct capture_callback *cb; + + src =3D hw->mix_buf.buffer + rpos; + hw->clip (cap->buf, src, to_capture); + mixeng_clear (src, to_capture); + + for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_n= ext) { + cb->ops.capture (cb->opaque, cap->buf, + to_capture * hw->info.bytes_per_frame); + } + rpos =3D (rpos + to_capture) % hw->mix_buf.size; + live -=3D to_capture; + } + hw->mix_buf.pos =3D rpos; + + for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)= ) { + dolog("captured=3D%zu sw->total_hw_samples_mixed=3D%zu\n", + captured, sw->total_hw_samples_mixed); + captured =3D sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -=3D captured; + sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; + } + } +} + +void audio_run(AudioMixengBackend *s, const char *msg) +{ + audio_run_out(s); + audio_run_in(s); + audio_run_capture(s); + +#ifdef DEBUG_POLL + { + static double prevtime; + double currtime; + struct timeval tv; + + if (gettimeofday (&tv, NULL)) { + perror ("audio_run: gettimeofday"); + return; + } + + currtime =3D tv.tv_sec + tv.tv_usec * 1e-6; + dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); + prevtime =3D currtime; + } +#endif +} + +void audio_generic_run_buffer_in(HWVoiceIn *hw) +{ + if (unlikely(!hw->buf_emul)) { + hw->size_emul =3D hw->samples * hw->info.bytes_per_frame; + hw->buf_emul =3D g_malloc(hw->size_emul); + hw->pos_emul =3D hw->pending_emul =3D 0; + } + + while (hw->pending_emul < hw->size_emul) { + size_t read_len =3D MIN(hw->size_emul - hw->pos_emul, + hw->size_emul - hw->pending_emul); + size_t read =3D hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul, + read_len); + hw->pending_emul +=3D read; + hw->pos_emul =3D (hw->pos_emul + read) % hw->size_emul; + if (read < read_len) { + break; + } + } +} + +void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size) +{ + size_t start; + + start =3D audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emu= l); + assert(start < hw->size_emul); + + *size =3D MIN(*size, hw->pending_emul); + *size =3D MIN(*size, hw->size_emul - start); + return hw->buf_emul + start; +} + +void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) +{ + assert(size <=3D hw->pending_emul); + hw->pending_emul -=3D size; +} + +size_t audio_generic_buffer_get_free(HWVoiceOut *hw) +{ + if (hw->buf_emul) { + return hw->size_emul - hw->pending_emul; + } else { + return hw->samples * hw->info.bytes_per_frame; + } +} + +void audio_generic_run_buffer_out(HWVoiceOut *hw) +{ + while (hw->pending_emul) { + size_t write_len, written, start; + + start =3D audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size= _emul); + assert(start < hw->size_emul); + + write_len =3D MIN(hw->pending_emul, hw->size_emul - start); + + written =3D hw->pcm_ops->write(hw, hw->buf_emul + start, write_len= ); + hw->pending_emul -=3D written; + + if (written < write_len) { + break; + } + } +} + +void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size) +{ + if (unlikely(!hw->buf_emul)) { + hw->size_emul =3D hw->samples * hw->info.bytes_per_frame; + hw->buf_emul =3D g_malloc(hw->size_emul); + hw->pos_emul =3D hw->pending_emul =3D 0; + } + + *size =3D MIN(hw->size_emul - hw->pending_emul, + hw->size_emul - hw->pos_emul); + return hw->buf_emul + hw->pos_emul; +} + +size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size) +{ + assert(buf =3D=3D hw->buf_emul + hw->pos_emul && + size + hw->pending_emul <=3D hw->size_emul); + + hw->pending_emul +=3D size; + hw->pos_emul =3D (hw->pos_emul + size) % hw->size_emul; + + return size; +} + +size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size) +{ + size_t total =3D 0; + + if (hw->pcm_ops->buffer_get_free) { + size_t free =3D hw->pcm_ops->buffer_get_free(hw); + + size =3D MIN(size, free); + } + + while (total < size) { + size_t dst_size =3D size - total; + size_t copy_size, proc; + void *dst =3D hw->pcm_ops->get_buffer_out(hw, &dst_size); + + if (dst_size =3D=3D 0) { + break; + } + + copy_size =3D MIN(size - total, dst_size); + if (dst) { + memcpy(dst, (char *)buf + total, copy_size); + } + proc =3D hw->pcm_ops->put_buffer_out(hw, dst, copy_size); + total +=3D proc; + + if (proc =3D=3D 0 || proc < copy_size) { + break; + } + } + + return total; +} + +size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size) +{ + size_t total =3D 0; + + if (hw->pcm_ops->run_buffer_in) { + hw->pcm_ops->run_buffer_in(hw); + } + + while (total < size) { + size_t src_size =3D size - total; + void *src =3D hw->pcm_ops->get_buffer_in(hw, &src_size); + + if (src_size =3D=3D 0) { + break; + } + + memcpy((char *)buf + total, src, src_size); + hw->pcm_ops->put_buffer_in(hw, src, src_size); + total +=3D src_size; + } + + return total; +} + +static bool audio_mixeng_backend_realize(AudioBackend *abe, + Audiodev *dev, Error **errp) +{ + AudioMixengBackend *be =3D AUDIO_MIXENG_BACKEND(abe); + audio_driver *drv =3D AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver; + + be->dev =3D dev; + be->drv_opaque =3D drv->init(be->dev, errp); + if (!be->drv_opaque) { + return false; + } + + if (!drv->pcm_ops->get_buffer_in) { + drv->pcm_ops->get_buffer_in =3D audio_generic_get_buffer_in; + drv->pcm_ops->put_buffer_in =3D audio_generic_put_buffer_in; + } + if (!drv->pcm_ops->get_buffer_out) { + drv->pcm_ops->get_buffer_out =3D audio_generic_get_buffer_out; + drv->pcm_ops->put_buffer_out =3D audio_generic_put_buffer_out; + } + + audio_init_nb_voices_out(be, drv, 1); + audio_init_nb_voices_in(be, drv, 0); + be->drv =3D drv; + + if (be->dev->timer_period <=3D 0) { + be->period_ticks =3D 1; + } else { + be->period_ticks =3D be->dev->timer_period * (int64_t)SCALE_US; + } + + return true; +} + +static void audio_vm_change_state_handler (void *opaque, bool running, + RunState state) +{ + AudioMixengBackend *s =3D opaque; + HWVoiceOut *hwo =3D NULL; + HWVoiceIn *hwi =3D NULL; + + while ((hwo =3D audio_pcm_hw_find_any_enabled_out(s, hwo))) { + if (hwo->pcm_ops->enable_out) { + hwo->pcm_ops->enable_out(hwo, running); + } + } + + while ((hwi =3D audio_pcm_hw_find_any_enabled_in(s, hwi))) { + if (hwi->pcm_ops->enable_in) { + hwi->pcm_ops->enable_in(hwi, running); + } + } + audio_reset_timer (s); +} + +static const VMStateDescription vmstate_audio; + +static const char *audio_mixeng_backend_get_id(AudioBackend *be) +{ + return AUDIO_MIXENG_BACKEND(be)->dev->id; +} + +static CaptureVoiceOut *audio_mixeng_backend_add_capture( + AudioBackend *be, + struct audsettings *as, + struct audio_capture_ops *ops, + void *cb_opaque); + +static void audio_mixeng_backend_del_capture( + AudioBackend *be, + CaptureVoiceOut *cap, + void *cb_opaque); + +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceO= ut *sw, + Volume *vol); +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn= *sw, + Volume *vol); + +static void audio_mixeng_backend_class_init(ObjectClass *klass, const void= *data) +{ + AudioBackendClass *be =3D AUDIO_BACKEND_CLASS(klass); + + be->realize =3D audio_mixeng_backend_realize; + be->get_id =3D audio_mixeng_backend_get_id; + be->open_in =3D audio_mixeng_backend_open_in; + be->open_out =3D audio_mixeng_backend_open_out; + be->close_in =3D audio_mixeng_backend_close_in; + be->close_out =3D audio_mixeng_backend_close_out; + be->is_active_out =3D audio_mixeng_backend_is_active_out; + be->is_active_in =3D audio_mixeng_backend_is_active_in; + be->set_active_out =3D audio_mixeng_backend_set_active_out; + be->set_active_in =3D audio_mixeng_backend_set_active_in; + be->set_volume_out =3D audio_mixeng_backend_set_volume_out; + be->set_volume_in =3D audio_mixeng_backend_set_volume_in; + be->read =3D audio_mixeng_backend_read; + be->write =3D audio_mixeng_backend_write; + be->get_buffer_size_out =3D audio_mixeng_backend_get_buffer_size_out; + be->add_capture =3D audio_mixeng_backend_add_capture; + be->del_capture =3D audio_mixeng_backend_del_capture; +} + +static void audio_mixeng_backend_init(Object *obj) +{ + AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(obj); + + QLIST_INIT(&s->hw_head_out); + QLIST_INIT(&s->hw_head_in); + QLIST_INIT(&s->cap_head); + s->ts =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); + + s->vmse =3D qemu_add_vm_change_state_handler(audio_vm_change_state_han= dler, s); + assert(s->vmse !=3D NULL); + + vmstate_register_any(NULL, &vmstate_audio, s); +} + +static void audio_mixeng_backend_finalize(Object *obj) +{ + AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(obj); + HWVoiceOut *hwo, *hwon; + HWVoiceIn *hwi, *hwin; + + QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) { + SWVoiceCap *sc; + + if (hwo->enabled && hwo->pcm_ops->enable_out) { + hwo->pcm_ops->enable_out(hwo, false); + } + hwo->pcm_ops->fini_out (hwo); + + for (sc =3D hwo->cap_head.lh_first; sc; sc =3D sc->entries.le_next= ) { + CaptureVoiceOut *cap =3D sc->cap; + struct capture_callback *cb; + + for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_n= ext) { + cb->ops.destroy (cb->opaque); + } + } + QLIST_REMOVE(hwo, entries); + } + + QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) { + if (hwi->enabled && hwi->pcm_ops->enable_in) { + hwi->pcm_ops->enable_in(hwi, false); + } + hwi->pcm_ops->fini_in (hwi); + QLIST_REMOVE(hwi, entries); + } + + if (s->drv) { + s->drv->fini (s->drv_opaque); + s->drv =3D NULL; + } + + if (s->dev) { + qapi_free_Audiodev(s->dev); + s->dev =3D NULL; + } + + if (s->ts) { + timer_free(s->ts); + s->ts =3D NULL; + } + + if (s->vmse) { + qemu_del_vm_change_state_handler(s->vmse); + s->vmse =3D NULL; + } + + vmstate_unregister(NULL, &vmstate_audio, s); +} + +static bool vmstate_audio_needed(void *opaque) +{ + /* + * Never needed, this vmstate only exists in case + * an old qemu sends it to us. + */ + return false; +} + +static const VMStateDescription vmstate_audio =3D { + .name =3D "audio", + .version_id =3D 1, + .minimum_version_id =3D 1, + .needed =3D vmstate_audio_needed, + .fields =3D (const VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static struct audio_pcm_ops capture_pcm_ops; + +static CaptureVoiceOut *audio_mixeng_backend_add_capture( + AudioBackend *be, + struct audsettings *as, + struct audio_capture_ops *ops, + void *cb_opaque) +{ + AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(be); + CaptureVoiceOut *cap; + struct capture_callback *cb; + + if (!s) { + error_report("Capturing without setting an audiodev is not support= ed"); + abort(); + } + + if (!audio_get_pdo_out(s->dev)->mixing_engine) { + dolog("Can't capture with mixeng disabled\n"); + return NULL; + } + + if (audio_validate_settings (as)) { + dolog ("Invalid settings were passed when trying to add capture\n"= ); + audio_print_settings (as); + return NULL; + } + + cb =3D g_malloc0(sizeof(*cb)); + cb->ops =3D *ops; + cb->opaque =3D cb_opaque; + + cap =3D audio_pcm_capture_find_specific(s, as); + if (cap) { + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + } else { + HWVoiceOut *hw; + + cap =3D g_malloc0(sizeof(*cap)); + + hw =3D &cap->hw; + hw->s =3D s; + hw->pcm_ops =3D &capture_pcm_ops; + QLIST_INIT (&hw->sw_head); + QLIST_INIT (&cap->cb_head); + + /* XXX find a more elegant way */ + hw->samples =3D 4096 * 4; + audio_pcm_hw_alloc_resources_out(hw); + + audio_pcm_init_info (&hw->info, as); + + cap->buf =3D g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_fram= e); + + if (hw->info.is_float) { + hw->clip =3D mixeng_clip_float[hw->info.nchannels =3D=3D 2] + [hw->info.swap_endianness]; + } else { + hw->clip =3D mixeng_clip + [hw->info.nchannels =3D=3D 2] + [hw->info.is_signed] + [hw->info.swap_endianness] + [audio_bits_to_index(hw->info.bits)]; + } + + QLIST_INSERT_HEAD (&s->cap_head, cap, entries); + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + + QLIST_FOREACH(hw, &s->hw_head_out, entries) { + audio_attach_capture (hw); + } + } + + return cap; +} + +static void audio_mixeng_backend_del_capture( + AudioBackend *be, + CaptureVoiceOut *cap, + void *cb_opaque) +{ + struct capture_callback *cb; + + for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_next) { + if (cb->opaque =3D=3D cb_opaque) { + cb->ops.destroy (cb_opaque); + QLIST_REMOVE (cb, entries); + g_free (cb); + + if (!cap->cb_head.lh_first) { + SWVoiceOut *sw =3D cap->hw.sw_head.lh_first, *sw1; + + while (sw) { + SWVoiceCap *sc =3D (SWVoiceCap *) sw; +#ifdef DEBUG_CAPTURE + dolog ("freeing %s\n", sw->name); +#endif + + sw1 =3D sw->entries.le_next; + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate =3D NULL; + } + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + g_free (sc); + sw =3D sw1; + } + QLIST_REMOVE (cap, entries); + g_free(cap->hw.mix_buf.buffer); + g_free (cap->buf); + g_free (cap); + } + return; + } + } +} + +static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceO= ut *sw, + Volume *vol) +{ + if (sw) { + HWVoiceOut *hw =3D sw->hw; + + sw->vol.mute =3D vol->mute; + sw->vol.l =3D nominal_volume.l * vol->vol[0] / 255; + sw->vol.r =3D nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : = 0] / + 255; + + if (hw->pcm_ops->volume_out) { + hw->pcm_ops->volume_out(hw, vol); + } + } +} + +static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn= *sw, + Volume *vol) +{ + if (sw) { + HWVoiceIn *hw =3D sw->hw; + + sw->vol.mute =3D vol->mute; + sw->vol.l =3D nominal_volume.l * vol->vol[0] / 255; + sw->vol.r =3D nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : = 0] / + 255; + + if (hw->pcm_ops->volume_in) { + hw->pcm_ops->volume_in(hw, vol); + } + } +} + +audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo) +{ + return (audsettings) { + .freq =3D pdo->frequency, + .nchannels =3D pdo->channels, + .fmt =3D pdo->format, + .endianness =3D HOST_BIG_ENDIAN, + }; +} + +int audioformat_bytes_per_sample(AudioFormat fmt) +{ + switch (fmt) { + case AUDIO_FORMAT_U8: + case AUDIO_FORMAT_S8: + return 1; + + case AUDIO_FORMAT_U16: + case AUDIO_FORMAT_S16: + return 2; + + case AUDIO_FORMAT_U32: + case AUDIO_FORMAT_S32: + case AUDIO_FORMAT_F32: + return 4; + + case AUDIO_FORMAT__MAX: + ; + } + abort(); +} + + +/* frames =3D freq * usec / 1e6 */ +int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + uint64_t usecs =3D pdo->has_buffer_length ? pdo->buffer_length : def_u= secs; + return (as->freq * usecs + 500000) / 1000000; +} + +/* samples =3D channels * frames =3D channels * freq * usec / 1e6 */ +int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return as->nchannels * audio_buffer_frames(pdo, as, def_usecs); +} + +/* + * bytes =3D bytes_per_sample * samples =3D + * bytes_per_sample * channels * freq * usec / 1e6 + */ +int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, + audsettings *as, int def_usecs) +{ + return audio_buffer_samples(pdo, as, def_usecs) * + audioformat_bytes_per_sample(as->fmt); +} + +void audio_rate_start(RateCtl *rate) +{ + memset(rate, 0, sizeof(RateCtl)); + rate->start_ticks =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info) +{ + int64_t now; + int64_t ticks; + int64_t bytes; + int64_t frames; + + now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + ticks =3D now - rate->start_ticks; + bytes =3D muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECO= ND); + frames =3D (bytes - rate->bytes_sent) / info->bytes_per_frame; + rate->peeked_frames =3D frames; + + return frames < 0 ? 0 : frames * info->bytes_per_frame; +} + +void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used) +{ + if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) { + AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n", + rate->peeked_frames); + audio_rate_start(rate); + } + + rate->bytes_sent +=3D bytes_used; +} + +size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info, + size_t bytes_avail) +{ + size_t bytes; + + bytes =3D audio_rate_peek_bytes(rate, info); + bytes =3D MIN(bytes, bytes_avail); + audio_rate_add_bytes(rate, bytes); + + return bytes; +} + +static const TypeInfo audio_mixeng_backend_info =3D { + .name =3D TYPE_AUDIO_MIXENG_BACKEND, + .parent =3D TYPE_AUDIO_BACKEND, + .instance_size =3D sizeof(AudioMixengBackend), + .instance_init =3D audio_mixeng_backend_init, + .instance_finalize =3D audio_mixeng_backend_finalize, + .abstract =3D false, + .class_size =3D sizeof(AudioMixengBackendClass), + .class_init =3D audio_mixeng_backend_class_init, +}; + +static void register_types(void) +{ + type_register_static(&audio_mixeng_backend_info); +} + +type_init(register_types); diff --git a/audio/audio.c b/audio/audio.c index 1d948084e80..6f10ee470ee 100644 --- a/audio/audio.c +++ b/audio/audio.c @@ -1,1750 +1,55 @@ -/* - * QEMU Audio subsystem - * - * 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"), t= o deal - * in the Software without restriction, including without limitation the r= ights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll - * 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 OT= HER - * 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 "qemu/osdep.h" -#include "qemu/audio.h" -#include "migration/vmstate.h" -#include "qemu/timer.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 "qobject/qdict.h" -#include "qemu/error-report.h" -#include "qemu/log.h" -#include "qemu/module.h" -#include "qemu/help_option.h" -#include "qom/object.h" -#include "system/system.h" -#include "system/replay.h" -#include "system/runstate.h" -#include "trace.h" - -#define AUDIO_CAP "audio" -#include "audio_int.h" - -/* #define DEBUG_LIVE */ -/* #define DEBUG_OUT */ -/* #define DEBUG_CAPTURE */ -/* #define DEBUG_POLL */ - -#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[] =3D { -#ifdef CONFIG_GIO - "dbus", -#endif - "spice", - CONFIG_AUDIO_DRIVERS - "none", - NULL -}; - -typedef struct AudiodevListEntry { - Audiodev *dev; - QSIMPLEQ_ENTRY(AudiodevListEntry) next; -} AudiodevListEntry; - -typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead; - -static AudiodevListHead audiodevs =3D - QSIMPLEQ_HEAD_INITIALIZER(audiodevs); -static AudiodevListHead default_audiodevs =3D - QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs); - -static AudioBackendClass *audio_be_class_by_name(const char *name) -{ - g_autofree char *tname =3D g_strconcat("audio-", name, NULL); - ObjectClass *oc =3D module_object_class_by_name(tname); - - if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) { - return NULL; - } - - return AUDIO_BACKEND_CLASS(oc); -} - -static AudioBackend *default_audio_be; - -const struct mixeng_volume nominal_volume =3D { - .mute =3D 0, -#ifdef FLOAT_MIXENG - .r =3D 1.0, - .l =3D 1.0, -#else - .r =3D 1ULL << 32, - .l =3D 1ULL << 32, -#endif -}; - -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 =3D 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"); - } - - return cond; -} - -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); - return 0; - } -} - -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 (const struct audsettings *as) -{ - dolog ("frequency=3D%d nchannels=3D%d fmt=3D", as->freq, as->nchannels= ); - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - AUD_log (NULL, "S8"); - break; - case AUDIO_FORMAT_U8: - AUD_log (NULL, "U8"); - break; - case AUDIO_FORMAT_S16: - AUD_log (NULL, "S16"); - break; - case AUDIO_FORMAT_U16: - AUD_log (NULL, "U16"); - break; - case AUDIO_FORMAT_S32: - AUD_log (NULL, "S32"); - break; - case AUDIO_FORMAT_U32: - AUD_log (NULL, "U32"); - break; - case AUDIO_FORMAT_F32: - AUD_log (NULL, "F32"); - break; - default: - AUD_log (NULL, "invalid(%d)", as->fmt); - break; - } - - AUD_log (NULL, " endianness=3D"); - switch (as->endianness) { - case 0: - AUD_log (NULL, "little"); - break; - case 1: - AUD_log (NULL, "big"); - break; - default: - AUD_log (NULL, "invalid"); - break; - } - AUD_log (NULL, "\n"); -} - -static int audio_validate_settings (const struct audsettings *as) -{ - int invalid; - - invalid =3D as->nchannels < 1; - invalid |=3D as->endianness !=3D 0 && as->endianness !=3D 1; - - switch (as->fmt) { - 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 =3D 1; - break; - } - - invalid |=3D as->freq <=3D 0; - return invalid ? -1 : 0; -} - -static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct au= dsettings *as) -{ - int bits =3D 8; - bool is_signed =3D false, is_float =3D false; - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U8: - break; - - case AUDIO_FORMAT_S16: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U16: - bits =3D 16; - break; - - case AUDIO_FORMAT_F32: - is_float =3D true; - /* fall through */ - case AUDIO_FORMAT_S32: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U32: - bits =3D 32; - break; - - default: - abort(); - } - return info->freq =3D=3D as->freq - && info->nchannels =3D=3D as->nchannels - && info->is_signed =3D=3D is_signed - && info->is_float =3D=3D is_float - && info->bits =3D=3D bits - && info->swap_endianness =3D=3D (as->endianness !=3D HOST_BIG_ENDI= AN); -} - -void audio_pcm_init_info (struct audio_pcm_info *info, const struct audset= tings *as) -{ - int bits =3D 8, mul; - bool is_signed =3D false, is_float =3D false; - - switch (as->fmt) { - case AUDIO_FORMAT_S8: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U8: - mul =3D 1; - break; - - case AUDIO_FORMAT_S16: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U16: - bits =3D 16; - mul =3D 2; - break; - - case AUDIO_FORMAT_F32: - is_float =3D true; - /* fall through */ - case AUDIO_FORMAT_S32: - is_signed =3D true; - /* fall through */ - case AUDIO_FORMAT_U32: - bits =3D 32; - mul =3D 4; - break; - - default: - abort(); - } - - info->freq =3D as->freq; - info->bits =3D bits; - info->is_signed =3D is_signed; - info->is_float =3D is_float; - info->nchannels =3D as->nchannels; - info->bytes_per_frame =3D as->nchannels * mul; - info->bytes_per_second =3D info->freq * info->bytes_per_frame; - info->swap_endianness =3D (as->endianness !=3D HOST_BIG_ENDIAN); -} - -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); - } else { - switch (info->bits) { - case 8: - memset(buf, 0x80, len * info->bytes_per_frame); - break; - - case 16: - { - int i; - uint16_t *p =3D buf; - short s =3D INT16_MAX; - - if (info->swap_endianness) { - s =3D bswap16 (s); - } - - for (i =3D 0; i < len * info->nchannels; i++) { - p[i] =3D s; - } - } - break; - - case 32: - { - int i; - uint32_t *p =3D buf; - int32_t s =3D INT32_MAX; - - if (info->swap_endianness) { - s =3D bswap32 (s); - } - - for (i =3D 0; i < len * info->nchannels; i++) { - p[i] =3D 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(AudioMixengBackend= *s, - struct audsettings= *as) -{ - CaptureVoiceOut *cap; - - for (cap =3D s->cap_head.lh_first; cap; cap =3D 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 =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_next) { - cb->ops.notify (cb->opaque, cmd); - } -} - -static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled) -{ - if (cap->hw.enabled !=3D enabled) { - audcnotification_e cmd; - cap->hw.enabled =3D enabled; - cmd =3D enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; - audio_notify_capture (cap, cmd); - } -} - -static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) -{ - HWVoiceOut *hw =3D &cap->hw; - SWVoiceOut *sw; - bool enabled =3D false; - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (sw->active) { - enabled =3D true; - break; - } - } - audio_capture_maybe_changed (cap, enabled); -} - -static void audio_detach_capture (HWVoiceOut *hw) -{ - SWVoiceCap *sc =3D hw->cap_head.lh_first; - - while (sc) { - SWVoiceCap *sc1 =3D sc->entries.le_next; - SWVoiceOut *sw =3D &sc->sw; - CaptureVoiceOut *cap =3D sc->cap; - int was_active =3D sw->active; - - if (sw->rate) { - st_rate_stop (sw->rate); - sw->rate =3D NULL; - } - - QLIST_REMOVE (sw, entries); - QLIST_REMOVE (sc, entries); - g_free (sc); - 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 =3D sc1; - } -} - -static int audio_attach_capture (HWVoiceOut *hw) -{ - AudioMixengBackend *s =3D hw->s; - CaptureVoiceOut *cap; - - audio_detach_capture (hw); - for (cap =3D s->cap_head.lh_first; cap; cap =3D cap->entries.le_next) { - SWVoiceCap *sc; - SWVoiceOut *sw; - HWVoiceOut *hw_cap =3D &cap->hw; - - sc =3D g_malloc0(sizeof(*sc)); - - sc->cap =3D cap; - sw =3D &sc->sw; - sw->hw =3D hw_cap; - sw->info =3D hw->info; - sw->empty =3D true; - sw->active =3D hw->enabled; - sw->vol =3D nominal_volume; - sw->rate =3D 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); -#ifdef DEBUG_CAPTURE - sw->name =3D g_strdup_printf ("for %p %d,%d,%d", - hw, sw->info.freq, sw->info.bits, - sw->info.nchannels); - dolog ("Added %s active =3D %d\n", sw->name, sw->active); -#endif - if (sw->active) { - audio_capture_maybe_changed (cap, 1); - } - } - return 0; -} - -/* - * Hard voice (capture) - */ -static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw) -{ - SWVoiceIn *sw; - size_t m =3D hw->total_samples_captured; - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (sw->active) { - m =3D MIN (m, sw->total_hw_samples_acquired); - } - } - return m; -} - -static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw) -{ - size_t live =3D hw->total_samples_captured - audio_pcm_hw_find_min_in = (hw); - if (audio_bug(__func__, live > hw->conv_buf.size)) { - dolog("live=3D%zu hw->conv_buf.size=3D%zu\n", live, hw->conv_buf.s= ize); - return 0; - } - return live; -} - -static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t sa= mples) -{ - size_t conv =3D 0; - STSampleBuffer *conv_buf =3D &hw->conv_buf; - - while (samples) { - uint8_t *src =3D advance(pcm_buf, conv * hw->info.bytes_per_frame); - size_t proc =3D MIN(samples, conv_buf->size - conv_buf->pos); - - hw->conv(conv_buf->buffer + conv_buf->pos, src, proc); - conv_buf->pos =3D (conv_buf->pos + proc) % conv_buf->size; - samples -=3D proc; - conv +=3D proc; - } - - return conv; -} - -/* - * Soft voice (capture) - */ -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 =3D sw->hw; - struct st_sample *src, *dst; - size_t live, rpos, frames_in, frames_out; - - live =3D hw->total_samples_captured - sw->total_hw_samples_acquired; - rpos =3D audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size); - - /* resample conv_buf from rpos to end of buffer */ - src =3D hw->conv_buf.buffer + rpos; - frames_in =3D MIN(frames_in_max, hw->conv_buf.size - rpos); - dst =3D sw->resample_buf.buffer; - frames_out =3D frames_out_max; - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); - rpos +=3D frames_in; - *total_in =3D frames_in; - *total_out =3D frames_out; - - /* resample conv_buf from start of buffer if there are input frames le= ft */ - if (frames_in_max - frames_in && rpos =3D=3D hw->conv_buf.size) { - src =3D hw->conv_buf.buffer; - frames_in =3D frames_in_max - frames_in; - dst +=3D frames_out; - frames_out =3D frames_out_max - frames_out; - st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out); - *total_in +=3D frames_in; - *total_out +=3D frames_out; - } -} - -static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len) -{ - HWVoiceIn *hw =3D sw->hw; - size_t live, frames_out_max, total_in, total_out; - - live =3D 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=3D%zu hw->conv_buf.size=3D%zu\n", live, hw->conv_bu= f.size); - return 0; - } - - frames_out_max =3D 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_o= ut); - - if (!hw->pcm_ops->volume_in) { - mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol); - } - sw->clip(buf, sw->resample_buf.buffer, total_out); - - sw->total_hw_samples_acquired +=3D 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) -{ - SWVoiceOut *sw; - size_t m =3D SIZE_MAX; - int nb_live =3D 0; - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (sw->active || !sw->empty) { - m =3D MIN (m, sw->total_hw_samples_mixed); - nb_live +=3D 1; - } - } - - *nb_livep =3D nb_live; - return m; -} - -static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) -{ - size_t smin; - int nb_live1; - - smin =3D audio_pcm_hw_find_min_out (hw, &nb_live1); - if (nb_live) { - *nb_live =3D nb_live1; - } - - if (nb_live1) { - size_t live =3D smin; - - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf= .size); - return 0; - } - return live; - } - return 0; -} - -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 le= n) -{ - size_t clipped =3D 0; - size_t pos =3D hw->mix_buf.pos; - - while (len) { - st_sample *src =3D hw->mix_buf.buffer + pos; - uint8_t *dst =3D advance(pcm_buf, clipped * hw->info.bytes_per_fra= me); - size_t samples_till_end_of_buf =3D hw->mix_buf.size - pos; - size_t samples_to_clip =3D MIN(len, samples_till_end_of_buf); - - hw->clip(dst, src, samples_to_clip); - - pos =3D (pos + samples_to_clip) % hw->mix_buf.size; - len -=3D samples_to_clip; - clipped +=3D samples_to_clip; - } -} - -/* - * Soft voice (playback) - */ -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 =3D sw->hw; - struct st_sample *src, *dst; - size_t live, wpos, frames_in, frames_out; - - live =3D sw->total_hw_samples_mixed; - wpos =3D (hw->mix_buf.pos + live) % hw->mix_buf.size; - - /* write to mix_buf from wpos to end of buffer */ - src =3D sw->resample_buf.buffer; - frames_in =3D frames_in_max; - dst =3D hw->mix_buf.buffer + wpos; - frames_out =3D MIN(frames_out_max, hw->mix_buf.size - wpos); - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); - wpos +=3D frames_out; - *total_in =3D frames_in; - *total_out =3D frames_out; - - /* write to mix_buf from start of buffer if there are input frames lef= t */ - if (frames_in_max - frames_in > 0 && wpos =3D=3D hw->mix_buf.size) { - src +=3D frames_in; - frames_in =3D frames_in_max - frames_in; - dst =3D hw->mix_buf.buffer; - frames_out =3D frames_out_max - frames_out; - st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out); - *total_in +=3D frames_in; - *total_out +=3D frames_out; - } -} - -static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len) -{ - HWVoiceOut *hw =3D 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 =3D sw->total_hw_samples_mixed; - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf.siz= e); - return 0; - } - - if (live =3D=3D hw->mix_buf.size) { -#ifdef DEBUG_OUT - dolog ("%s is full %zu\n", sw->name, live); -#endif - return 0; - } - - dead =3D hw->mix_buf.size - live; - hw_free =3D audio_pcm_hw_get_free(hw); - hw_free =3D hw_free > live ? hw_free - live : 0; - frames_out_max =3D MIN(dead, hw_free); - sw_max =3D st_rate_frames_in(sw->rate, frames_out_max); - fe_max =3D MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.p= os, - sw->resample_buf.size); - frames_in_max =3D 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 +=3D total_out; - sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; - - /* - * Upsampling may leave one audio frame in the resample buffer. Decrem= ent - * total_in by one if there was a leftover frame from the previous res= ample - * pass in the resample buffer. Increment total_in by one if the curre= nt - * resample pass left one frame in the resample buffer. - */ - if (frames_in_max - total_in =3D=3D 1) { - /* copy one leftover audio frame to the beginning of the buffer */ - *sw->resample_buf.buffer =3D *(sw->resample_buf.buffer + total_in); - total_in +=3D 1 - sw->resample_buf.pos; - sw->resample_buf.pos =3D 1; - } else if (total_in >=3D sw->resample_buf.pos) { - total_in -=3D sw->resample_buf.pos; - sw->resample_buf.pos =3D 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, - sw->total_hw_samples_mixed - ); -#endif - - 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); -} -#endif - -#define DAC -#include "audio_template.h" -#undef DAC -#include "audio_template.h" - -/* - * Timer - */ -static int audio_is_timer_needed(AudioMixengBackend *s) -{ - HWVoiceIn *hwi =3D NULL; - HWVoiceOut *hwo =3D NULL; - - while ((hwo =3D audio_pcm_hw_find_any_enabled_out(s, hwo))) { - if (!hwo->poll_mode) { - return 1; - } - } - while ((hwi =3D audio_pcm_hw_find_any_enabled_in(s, hwi))) { - if (!hwi->poll_mode) { - return 1; - } - } - return 0; -} - -static void audio_reset_timer(AudioMixengBackend *s) -{ - if (audio_is_timer_needed(s)) { - timer_mod_anticipate_ns(s->ts, - qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks); - if (!s->timer_running) { - s->timer_running =3D true; - s->timer_last =3D 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 =3D false; - trace_audio_timer_stop(); - } - } -} - -static void audio_timer (void *opaque) -{ - int64_t now, diff; - AudioMixengBackend *s =3D opaque; - - now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - diff =3D now - s->timer_last; - if (diff > s->period_ticks * 3 / 2) { - trace_audio_timer_delayed(diff / SCALE_MS); - } - s->timer_last =3D now; - - audio_run(s, "timer"); - audio_reset_timer(s); -} - -/* - * Public API - */ -static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw, - void *buf, size_t size) -{ - HWVoiceOut *hw; - - if (!sw) { - /* XXX: Consider options */ - return size; - } - hw =3D sw->hw; - - if (!hw->enabled) { - dolog("Writing to disabled voice %s\n", SW_NAME(sw)); - return 0; - } - - 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); - } -} - -static size_t audio_mixeng_backend_read(AudioBackend *be, - SWVoiceIn *sw, void *buf, size_t s= ize) -{ - HWVoiceIn *hw; - - if (!sw) { - /* XXX: Consider options */ - return size; - } - hw =3D sw->hw; - - if (!hw->enabled) { - dolog("Reading from disabled voice %s\n", SW_NAME(sw)); - return 0; - } - - 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); - } - -} - -static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVo= iceOut *sw) -{ - if (!sw) { - return 0; - } - - if (audio_get_pdo_out(sw->s->dev)->mixing_engine) { - return sw->resample_buf.size * sw->info.bytes_per_frame; - } - - return sw->hw->samples * sw->hw->info.bytes_per_frame; -} - -static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceO= ut *sw, - bool on) -{ - HWVoiceOut *hw; - - if (!sw) { - return; - } - - hw =3D sw->hw; - if (sw->active !=3D on) { - AudioMixengBackend *s =3D sw->s; - SWVoiceOut *temp_sw; - SWVoiceCap *sc; - - if (on) { - hw->pending_disable =3D 0; - if (!hw->enabled) { - hw->enabled =3D true; - if (runstate_is_running()) { - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, true); - } - audio_reset_timer (s); - } - } - } else { - if (hw->enabled) { - int nb_active =3D 0; - - for (temp_sw =3D hw->sw_head.lh_first; temp_sw; - temp_sw =3D temp_sw->entries.le_next) { - nb_active +=3D temp_sw->active !=3D 0; - } - - hw->pending_disable =3D nb_active =3D=3D 1; - } - } - - for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_next)= { - sc->sw.active =3D hw->enabled; - if (hw->enabled) { - audio_capture_maybe_changed (sc->cap, 1); - } - } - sw->active =3D on; - } - -} - -static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn= *sw, bool on) -{ - HWVoiceIn *hw; - - if (!sw) { - return; - } - - hw =3D sw->hw; - if (sw->active !=3D on) { - AudioMixengBackend *s =3D sw->s; - SWVoiceIn *temp_sw; - - if (on) { - if (!hw->enabled) { - hw->enabled =3D true; - if (runstate_is_running()) { - if (hw->pcm_ops->enable_in) { - hw->pcm_ops->enable_in(hw, true); - } - audio_reset_timer (s); - } - } - sw->total_hw_samples_acquired =3D hw->total_samples_captured; - } else { - if (hw->enabled) { - int nb_active =3D 0; - - for (temp_sw =3D hw->sw_head.lh_first; temp_sw; - temp_sw =3D temp_sw->entries.le_next) { - nb_active +=3D temp_sw->active !=3D 0; - } - - if (nb_active =3D=3D 1) { - hw->enabled =3D false; - if (hw->pcm_ops->enable_in) { - hw->pcm_ops->enable_in(hw, false); - } - } - } - } - sw->active =3D on; - } -} - -static size_t audio_get_avail(SWVoiceIn *sw) -{ - size_t live; - - if (!sw) { - return 0; - } - - live =3D sw->hw->total_samples_captured - sw->total_hw_samples_acquire= d; - if (audio_bug(__func__, live > sw->hw->conv_buf.size)) { - dolog("live=3D%zu sw->hw->conv_buf.size=3D%zu\n", live, - sw->hw->conv_buf.size); - return 0; - } - - ldebug ( - "%s: get_avail live %zu frontend frames %u\n", - SW_NAME (sw), - live, st_rate_frames_out(sw->rate, live) - ); - - return live; -} - -static size_t audio_get_free(SWVoiceOut *sw) -{ - size_t live, dead; - - if (!sw) { - return 0; - } - - live =3D sw->total_hw_samples_mixed; - - if (audio_bug(__func__, live > sw->hw->mix_buf.size)) { - dolog("live=3D%zu sw->hw->mix_buf.size=3D%zu\n", live, - sw->hw->mix_buf.size); - return 0; - } - - dead =3D sw->hw->mix_buf.size - live; - -#ifdef DEBUG_OUT - dolog("%s: get_free live %zu dead %zu frontend frames %u\n", - SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead)); -#endif - - return dead; -} - -static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos, - size_t samples) -{ - size_t n; - - if (hw->enabled) { - SWVoiceCap *sc; - - for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_next)= { - SWVoiceOut *sw =3D &sc->sw; - size_t rpos2 =3D rpos; - - n =3D samples; - while (n) { - size_t till_end_of_hw =3D hw->mix_buf.size - rpos2; - size_t to_read =3D MIN(till_end_of_hw, n); - size_t live, frames_in, frames_out; - - sw->resample_buf.buffer =3D hw->mix_buf.buffer + rpos2; - sw->resample_buf.size =3D to_read; - live =3D sw->total_hw_samples_mixed; - - audio_pcm_sw_resample_out(sw, - to_read, sw->hw->mix_buf.size - = live, - &frames_in, &frames_out); - - sw->total_hw_samples_mixed +=3D frames_out; - sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; - - if (to_read - frames_in) { - dolog("Could not mix %zu frames into a capture " - "buffer, mixed %zu\n", - to_read, frames_in); - break; - } - n -=3D to_read; - rpos2 =3D (rpos2 + to_read) % hw->mix_buf.size; - } - } - } - - n =3D MIN(samples, hw->mix_buf.size - rpos); - mixeng_clear(hw->mix_buf.buffer + rpos, n); - mixeng_clear(hw->mix_buf.buffer, samples - n); -} - -static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live) -{ - size_t clipped =3D 0; - - while (live) { - size_t size =3D live * hw->info.bytes_per_frame; - size_t decr, proc; - void *buf =3D hw->pcm_ops->get_buffer_out(hw, &size); - - if (size =3D=3D 0) { - break; - } - - decr =3D MIN(size / hw->info.bytes_per_frame, live); - if (buf) { - audio_pcm_hw_clip_out(hw, buf, decr); - } - proc =3D hw->pcm_ops->put_buffer_out(hw, buf, - decr * hw->info.bytes_per_frame= ) / - hw->info.bytes_per_frame; - - live -=3D proc; - clipped +=3D proc; - hw->mix_buf.pos =3D (hw->mix_buf.pos + proc) % hw->mix_buf.size; - - if (proc =3D=3D 0 || proc < decr) { - break; - } - } - - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - - return clipped; -} - -static void audio_run_out(AudioMixengBackend *s) -{ - HWVoiceOut *hw =3D NULL; - SWVoiceOut *sw; - - while ((hw =3D audio_pcm_hw_find_any_enabled_out(s, hw))) { - size_t played, live, prev_rpos; - size_t hw_free =3D audio_pcm_hw_get_free(hw); - int nb_live; - - if (!audio_get_pdo_out(s->dev)->mixing_engine) { - /* there is exactly 1 sw for each hw with no mixeng */ - sw =3D hw->sw_head.lh_first; - - if (hw->pending_disable) { - hw->enabled =3D false; - hw->pending_disable =3D false; - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, false); - } - } - - if (sw->active) { - sw->callback.fn(sw->callback.opaque, - hw_free * sw->info.bytes_per_frame); - } - - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - - continue; - } - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (sw->active) { - size_t sw_free =3D audio_get_free(sw); - size_t free; - - if (hw_free > sw->total_hw_samples_mixed) { - free =3D st_rate_frames_in(sw->rate, - MIN(sw_free, hw_free - sw->total_hw_samples_mixed)= ); - } else { - free =3D 0; - } - if (free > sw->resample_buf.pos) { - free =3D MIN(free, sw->resample_buf.size) - - sw->resample_buf.pos; - sw->callback.fn(sw->callback.opaque, - free * sw->info.bytes_per_frame); - } - } - } - - live =3D audio_pcm_hw_get_live_out (hw, &nb_live); - if (!nb_live) { - live =3D 0; - } - - if (audio_bug(__func__, live > hw->mix_buf.size)) { - dolog("live=3D%zu hw->mix_buf.size=3D%zu\n", live, hw->mix_buf= .size); - continue; - } - - if (hw->pending_disable && !nb_live) { - SWVoiceCap *sc; -#ifdef DEBUG_OUT - dolog ("Disabling voice\n"); -#endif - hw->enabled =3D false; - hw->pending_disable =3D false; - if (hw->pcm_ops->enable_out) { - hw->pcm_ops->enable_out(hw, false); - } - for (sc =3D hw->cap_head.lh_first; sc; sc =3D sc->entries.le_n= ext) { - sc->sw.active =3D false; - audio_recalc_and_notify_capture (sc->cap); - } - continue; - } - - if (!live) { - if (hw->pcm_ops->run_buffer_out) { - hw->pcm_ops->run_buffer_out(hw); - } - continue; - } - - prev_rpos =3D hw->mix_buf.pos; - played =3D audio_pcm_hw_run_out(hw, live); - replay_audio_out(&played); - if (audio_bug(__func__, hw->mix_buf.pos >=3D hw->mix_buf.size)) { - dolog("hw->mix_buf.pos=3D%zu hw->mix_buf.size=3D%zu played=3D%= zu\n", - hw->mix_buf.pos, hw->mix_buf.size, played); - hw->mix_buf.pos =3D 0; - } - -#ifdef DEBUG_OUT - dolog("played=3D%zu\n", played); -#endif - - if (played) { - audio_capture_mix_and_clear (hw, prev_rpos, played); - } - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (!sw->active && sw->empty) { - continue; - } - - if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) { - dolog("played=3D%zu sw->total_hw_samples_mixed=3D%zu\n", - played, sw->total_hw_samples_mixed); - played =3D sw->total_hw_samples_mixed; - } - - sw->total_hw_samples_mixed -=3D played; - - if (!sw->total_hw_samples_mixed) { - sw->empty =3D true; - } - } - } -} - -static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples) -{ - size_t conv =3D 0; - - if (hw->pcm_ops->run_buffer_in) { - hw->pcm_ops->run_buffer_in(hw); - } - - while (samples) { - size_t proc; - size_t size =3D samples * hw->info.bytes_per_frame; - void *buf =3D hw->pcm_ops->get_buffer_in(hw, &size); - - assert(size % hw->info.bytes_per_frame =3D=3D 0); - if (size =3D=3D 0) { - break; - } - - proc =3D audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_f= rame); - - samples -=3D proc; - conv +=3D proc; - hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_fram= e); - } - - return conv; -} - -static void audio_run_in(AudioMixengBackend *s) -{ - HWVoiceIn *hw =3D NULL; - - if (!audio_get_pdo_in(s->dev)->mixing_engine) { - while ((hw =3D audio_pcm_hw_find_any_enabled_in(s, hw))) { - /* there is exactly 1 sw for each hw with no mixeng */ - SWVoiceIn *sw =3D hw->sw_head.lh_first; - if (sw->active) { - sw->callback.fn(sw->callback.opaque, INT_MAX); - } - } - return; - } - - while ((hw =3D audio_pcm_hw_find_any_enabled_in(s, hw))) { - SWVoiceIn *sw; - size_t captured =3D 0, min; - int pos; - - if (replay_mode !=3D REPLAY_MODE_PLAY) { - captured =3D audio_pcm_hw_run_in( - hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw)); - } - - replay_audio_in_start(&captured); - assert(captured <=3D hw->conv_buf.size); - if (replay_mode =3D=3D REPLAY_MODE_PLAY) { - hw->conv_buf.pos =3D (hw->conv_buf.pos + captured) % hw->conv_= buf.size; - } - for (pos =3D (hw->conv_buf.pos - captured + hw->conv_buf.size) % h= w->conv_buf.size; - pos !=3D hw->conv_buf.pos; - pos =3D (pos + 1) % hw->conv_buf.size) { - uint64_t left, right; - - if (replay_mode =3D=3D REPLAY_MODE_RECORD) { - audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left= , &right); - } - replay_audio_in_sample_lr(&left, &right); - if (replay_mode =3D=3D REPLAY_MODE_PLAY) { - audio_sample_from_uint64(hw->conv_buf.buffer, pos, lef= t, right); - } - } - replay_audio_in_finish(); - - min =3D audio_pcm_hw_find_min_in (hw); - hw->total_samples_captured +=3D captured - min; - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - sw->total_hw_samples_acquired -=3D min; - - if (sw->active) { - size_t sw_avail =3D audio_get_avail(sw); - size_t avail; - - avail =3D st_rate_frames_out(sw->rate, sw_avail); - if (avail > 0) { - avail =3D MIN(avail, sw->resample_buf.size); - sw->callback.fn(sw->callback.opaque, - avail * sw->info.bytes_per_frame); - } - } - } - } -} - -static void audio_run_capture(AudioMixengBackend *s) -{ - CaptureVoiceOut *cap; - - for (cap =3D s->cap_head.lh_first; cap; cap =3D cap->entries.le_next) { - size_t live, rpos, captured; - HWVoiceOut *hw =3D &cap->hw; - SWVoiceOut *sw; - - captured =3D live =3D audio_pcm_hw_get_live_out (hw, NULL); - rpos =3D hw->mix_buf.pos; - while (live) { - size_t left =3D hw->mix_buf.size - rpos; - size_t to_capture =3D MIN(live, left); - struct st_sample *src; - struct capture_callback *cb; - - src =3D hw->mix_buf.buffer + rpos; - hw->clip (cap->buf, src, to_capture); - mixeng_clear (src, to_capture); - - for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_n= ext) { - cb->ops.capture (cb->opaque, cap->buf, - to_capture * hw->info.bytes_per_frame); - } - rpos =3D (rpos + to_capture) % hw->mix_buf.size; - live -=3D to_capture; - } - hw->mix_buf.pos =3D rpos; - - for (sw =3D hw->sw_head.lh_first; sw; sw =3D sw->entries.le_next) { - if (!sw->active && sw->empty) { - continue; - } - - if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)= ) { - dolog("captured=3D%zu sw->total_hw_samples_mixed=3D%zu\n", - captured, sw->total_hw_samples_mixed); - captured =3D sw->total_hw_samples_mixed; - } - - sw->total_hw_samples_mixed -=3D captured; - sw->empty =3D sw->total_hw_samples_mixed =3D=3D 0; - } - } -} - -void audio_run(AudioMixengBackend *s, const char *msg) -{ - audio_run_out(s); - audio_run_in(s); - audio_run_capture(s); - -#ifdef DEBUG_POLL - { - static double prevtime; - double currtime; - struct timeval tv; - - if (gettimeofday (&tv, NULL)) { - perror ("audio_run: gettimeofday"); - return; - } - - currtime =3D tv.tv_sec + tv.tv_usec * 1e-6; - dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); - prevtime =3D currtime; - } -#endif -} - -void audio_generic_run_buffer_in(HWVoiceIn *hw) -{ - if (unlikely(!hw->buf_emul)) { - hw->size_emul =3D hw->samples * hw->info.bytes_per_frame; - hw->buf_emul =3D g_malloc(hw->size_emul); - hw->pos_emul =3D hw->pending_emul =3D 0; - } - - while (hw->pending_emul < hw->size_emul) { - size_t read_len =3D MIN(hw->size_emul - hw->pos_emul, - hw->size_emul - hw->pending_emul); - size_t read =3D hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul, - read_len); - hw->pending_emul +=3D read; - hw->pos_emul =3D (hw->pos_emul + read) % hw->size_emul; - if (read < read_len) { - break; - } - } -} - -void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size) -{ - size_t start; - - start =3D audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emu= l); - assert(start < hw->size_emul); - - *size =3D MIN(*size, hw->pending_emul); - *size =3D MIN(*size, hw->size_emul - start); - return hw->buf_emul + start; -} - -void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size) -{ - assert(size <=3D hw->pending_emul); - hw->pending_emul -=3D size; -} - -size_t audio_generic_buffer_get_free(HWVoiceOut *hw) -{ - if (hw->buf_emul) { - return hw->size_emul - hw->pending_emul; - } else { - return hw->samples * hw->info.bytes_per_frame; - } -} - -void audio_generic_run_buffer_out(HWVoiceOut *hw) -{ - while (hw->pending_emul) { - size_t write_len, written, start; - - start =3D audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size= _emul); - assert(start < hw->size_emul); - - write_len =3D MIN(hw->pending_emul, hw->size_emul - start); - - written =3D hw->pcm_ops->write(hw, hw->buf_emul + start, write_len= ); - hw->pending_emul -=3D written; - - if (written < write_len) { - break; - } - } -} - -void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size) -{ - if (unlikely(!hw->buf_emul)) { - hw->size_emul =3D hw->samples * hw->info.bytes_per_frame; - hw->buf_emul =3D g_malloc(hw->size_emul); - hw->pos_emul =3D hw->pending_emul =3D 0; - } - - *size =3D MIN(hw->size_emul - hw->pending_emul, - hw->size_emul - hw->pos_emul); - return hw->buf_emul + hw->pos_emul; -} - -size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size) -{ - assert(buf =3D=3D hw->buf_emul + hw->pos_emul && - size + hw->pending_emul <=3D hw->size_emul); - - hw->pending_emul +=3D size; - hw->pos_emul =3D (hw->pos_emul + size) % hw->size_emul; - - return size; -} - -size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size) -{ - size_t total =3D 0; - - if (hw->pcm_ops->buffer_get_free) { - size_t free =3D hw->pcm_ops->buffer_get_free(hw); - - size =3D MIN(size, free); - } - - while (total < size) { - size_t dst_size =3D size - total; - size_t copy_size, proc; - void *dst =3D hw->pcm_ops->get_buffer_out(hw, &dst_size); - - if (dst_size =3D=3D 0) { - break; - } - - copy_size =3D MIN(size - total, dst_size); - if (dst) { - memcpy(dst, (char *)buf + total, copy_size); - } - proc =3D hw->pcm_ops->put_buffer_out(hw, dst, copy_size); - total +=3D proc; - - if (proc =3D=3D 0 || proc < copy_size) { - break; - } - } - - return total; -} - -size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size) -{ - size_t total =3D 0; - - if (hw->pcm_ops->run_buffer_in) { - hw->pcm_ops->run_buffer_in(hw); - } - - while (total < size) { - size_t src_size =3D size - total; - void *src =3D hw->pcm_ops->get_buffer_in(hw, &src_size); - - if (src_size =3D=3D 0) { - break; - } - - memcpy((char *)buf + total, src, src_size); - hw->pcm_ops->put_buffer_in(hw, src, src_size); - total +=3D src_size; - } - - return total; -} - -static bool audio_mixeng_backend_realize(AudioBackend *abe, - Audiodev *dev, Error **errp) -{ - AudioMixengBackend *be =3D AUDIO_MIXENG_BACKEND(abe); - audio_driver *drv =3D AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver; - - be->dev =3D dev; - be->drv_opaque =3D drv->init(be->dev, errp); - if (!be->drv_opaque) { - return false; - } - - if (!drv->pcm_ops->get_buffer_in) { - drv->pcm_ops->get_buffer_in =3D audio_generic_get_buffer_in; - drv->pcm_ops->put_buffer_in =3D audio_generic_put_buffer_in; - } - if (!drv->pcm_ops->get_buffer_out) { - drv->pcm_ops->get_buffer_out =3D audio_generic_get_buffer_out; - drv->pcm_ops->put_buffer_out =3D audio_generic_put_buffer_out; - } - - audio_init_nb_voices_out(be, drv, 1); - audio_init_nb_voices_in(be, drv, 0); - be->drv =3D drv; - - if (be->dev->timer_period <=3D 0) { - be->period_ticks =3D 1; - } else { - be->period_ticks =3D be->dev->timer_period * (int64_t)SCALE_US; - } - - return true; -} - -static void audio_vm_change_state_handler (void *opaque, bool running, - RunState state) -{ - AudioMixengBackend *s =3D opaque; - HWVoiceOut *hwo =3D NULL; - HWVoiceIn *hwi =3D NULL; - - while ((hwo =3D audio_pcm_hw_find_any_enabled_out(s, hwo))) { - if (hwo->pcm_ops->enable_out) { - hwo->pcm_ops->enable_out(hwo, running); - } - } - - while ((hwi =3D audio_pcm_hw_find_any_enabled_in(s, hwi))) { - if (hwi->pcm_ops->enable_in) { - hwi->pcm_ops->enable_in(hwi, running); - } - } - audio_reset_timer (s); -} - -static const VMStateDescription vmstate_audio; - -static const char *audio_mixeng_backend_get_id(AudioBackend *be) -{ - return AUDIO_MIXENG_BACKEND(be)->dev->id; -} - -static CaptureVoiceOut *audio_mixeng_backend_add_capture( - AudioBackend *be, - struct audsettings *as, - struct audio_capture_ops *ops, - void *cb_opaque); - -static void audio_mixeng_backend_del_capture( - AudioBackend *be, - CaptureVoiceOut *cap, - void *cb_opaque); - -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceO= ut *sw, - Volume *vol); -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn= *sw, - Volume *vol); - -static void audio_mixeng_backend_class_init(ObjectClass *klass, const void= *data) -{ - AudioBackendClass *be =3D AUDIO_BACKEND_CLASS(klass); - - be->realize =3D audio_mixeng_backend_realize; - be->get_id =3D audio_mixeng_backend_get_id; - be->open_in =3D audio_mixeng_backend_open_in; - be->open_out =3D audio_mixeng_backend_open_out; - be->close_in =3D audio_mixeng_backend_close_in; - be->close_out =3D audio_mixeng_backend_close_out; - be->is_active_out =3D audio_mixeng_backend_is_active_out; - be->is_active_in =3D audio_mixeng_backend_is_active_in; - be->set_active_out =3D audio_mixeng_backend_set_active_out; - be->set_active_in =3D audio_mixeng_backend_set_active_in; - be->set_volume_out =3D audio_mixeng_backend_set_volume_out; - be->set_volume_in =3D audio_mixeng_backend_set_volume_in; - be->read =3D audio_mixeng_backend_read; - be->write =3D audio_mixeng_backend_write; - be->get_buffer_size_out =3D audio_mixeng_backend_get_buffer_size_out; - be->add_capture =3D audio_mixeng_backend_add_capture; - be->del_capture =3D audio_mixeng_backend_del_capture; -} - -static void audio_mixeng_backend_init(Object *obj) -{ - AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(obj); - - QLIST_INIT(&s->hw_head_out); - QLIST_INIT(&s->hw_head_in); - QLIST_INIT(&s->cap_head); - s->ts =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s); - - s->vmse =3D qemu_add_vm_change_state_handler(audio_vm_change_state_han= dler, s); - assert(s->vmse !=3D NULL); - - vmstate_register_any(NULL, &vmstate_audio, s); -} - -static void audio_mixeng_backend_finalize(Object *obj) -{ - AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(obj); - HWVoiceOut *hwo, *hwon; - HWVoiceIn *hwi, *hwin; - - QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) { - SWVoiceCap *sc; - - if (hwo->enabled && hwo->pcm_ops->enable_out) { - hwo->pcm_ops->enable_out(hwo, false); - } - hwo->pcm_ops->fini_out (hwo); +/* SPDX-License-Identifier: MIT */ =20 - for (sc =3D hwo->cap_head.lh_first; sc; sc =3D sc->entries.le_next= ) { - CaptureVoiceOut *cap =3D sc->cap; - struct capture_callback *cb; +#include "qemu/osdep.h" +#include "qemu/audio.h" +#include "qemu/help_option.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 "qobject/qdict.h" +#include "system/system.h" =20 - for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_n= ext) { - cb->ops.destroy (cb->opaque); - } - } - QLIST_REMOVE(hwo, entries); - } +/* 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[] =3D { +#ifdef CONFIG_GIO + "dbus", +#endif + "spice", + CONFIG_AUDIO_DRIVERS + "none", + NULL +}; =20 - QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) { - if (hwi->enabled && hwi->pcm_ops->enable_in) { - hwi->pcm_ops->enable_in(hwi, false); - } - hwi->pcm_ops->fini_in (hwi); - QLIST_REMOVE(hwi, entries); - } +typedef struct AudiodevListEntry { + Audiodev *dev; + QSIMPLEQ_ENTRY(AudiodevListEntry) next; +} AudiodevListEntry; =20 - if (s->drv) { - s->drv->fini (s->drv_opaque); - s->drv =3D NULL; - } +typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead; =20 - if (s->dev) { - qapi_free_Audiodev(s->dev); - s->dev =3D NULL; - } +static AudiodevListHead audiodevs =3D + QSIMPLEQ_HEAD_INITIALIZER(audiodevs); +static AudiodevListHead default_audiodevs =3D + QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs); =20 - if (s->ts) { - timer_free(s->ts); - s->ts =3D NULL; - } +static AudioBackendClass *audio_be_class_by_name(const char *name) +{ + g_autofree char *tname =3D g_strconcat("audio-", name, NULL); + ObjectClass *oc =3D module_object_class_by_name(tname); =20 - if (s->vmse) { - qemu_del_vm_change_state_handler(s->vmse); - s->vmse =3D NULL; + if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) { + return NULL; } =20 - vmstate_unregister(NULL, &vmstate_audio, s); + return AUDIO_BACKEND_CLASS(oc); } =20 +static AudioBackend *default_audio_be; + static Object *get_audiodevs_root(void) { return object_get_container("audiodevs"); @@ -1757,25 +62,6 @@ void audio_cleanup(void) object_unparent(get_audiodevs_root()); } =20 -static bool vmstate_audio_needed(void *opaque) -{ - /* - * Never needed, this vmstate only exists in case - * an old qemu sends it to us. - */ - return false; -} - -static const VMStateDescription vmstate_audio =3D { - .name =3D "audio", - .version_id =3D 1, - .minimum_version_id =3D 1, - .needed =3D vmstate_audio_needed, - .fields =3D (const VMStateField[]) { - VMSTATE_END_OF_LIST() - } -}; - void audio_create_default_audiodevs(void) { for (int i =3D 0; audio_prio_list[i]; i++) { @@ -1816,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error= **errp) assert(!default_audio_be); for (;;) { AudiodevListEntry *e =3D QSIMPLEQ_FIRST(&default_audiodevs); - if (!e) { error_setg(errp, "no default audio driver available"); return NULL; } - dev =3D e->dev; QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next); + be =3D audio_be_new(e->dev, NULL); g_free(e); - be =3D audio_be_new(dev, NULL); if (be) { break; } @@ -1855,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **er= rp) return default_audio_be; } =20 -static struct audio_pcm_ops capture_pcm_ops; - -static CaptureVoiceOut *audio_mixeng_backend_add_capture( - AudioBackend *be, - struct audsettings *as, - struct audio_capture_ops *ops, - void *cb_opaque) -{ - AudioMixengBackend *s =3D AUDIO_MIXENG_BACKEND(be); - CaptureVoiceOut *cap; - struct capture_callback *cb; - - if (!s) { - error_report("Capturing without setting an audiodev is not support= ed"); - abort(); - } - - if (!audio_get_pdo_out(s->dev)->mixing_engine) { - dolog("Can't capture with mixeng disabled\n"); - return NULL; - } - - if (audio_validate_settings (as)) { - dolog ("Invalid settings were passed when trying to add capture\n"= ); - audio_print_settings (as); - return NULL; - } - - cb =3D g_malloc0(sizeof(*cb)); - cb->ops =3D *ops; - cb->opaque =3D cb_opaque; - - cap =3D audio_pcm_capture_find_specific(s, as); - if (cap) { - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); - } else { - HWVoiceOut *hw; - - cap =3D g_malloc0(sizeof(*cap)); - - hw =3D &cap->hw; - hw->s =3D s; - hw->pcm_ops =3D &capture_pcm_ops; - QLIST_INIT (&hw->sw_head); - QLIST_INIT (&cap->cb_head); - - /* XXX find a more elegant way */ - hw->samples =3D 4096 * 4; - audio_pcm_hw_alloc_resources_out(hw); - - audio_pcm_init_info (&hw->info, as); - - cap->buf =3D g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_fram= e); - - if (hw->info.is_float) { - hw->clip =3D mixeng_clip_float[hw->info.nchannels =3D=3D 2] - [hw->info.swap_endianness]; - } else { - hw->clip =3D mixeng_clip - [hw->info.nchannels =3D=3D 2] - [hw->info.is_signed] - [hw->info.swap_endianness] - [audio_bits_to_index(hw->info.bits)]; - } - - QLIST_INSERT_HEAD (&s->cap_head, cap, entries); - QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); - - QLIST_FOREACH(hw, &s->hw_head_out, entries) { - audio_attach_capture (hw); - } - } - - return cap; -} - -static void audio_mixeng_backend_del_capture( - AudioBackend *be, - CaptureVoiceOut *cap, - void *cb_opaque) +void audio_help(void) { - struct capture_callback *cb; - - for (cb =3D cap->cb_head.lh_first; cb; cb =3D cb->entries.le_next) { - if (cb->opaque =3D=3D cb_opaque) { - cb->ops.destroy (cb_opaque); - QLIST_REMOVE (cb, entries); - g_free (cb); + int i; =20 - if (!cap->cb_head.lh_first) { - SWVoiceOut *sw =3D cap->hw.sw_head.lh_first, *sw1; + printf("Available audio drivers:\n"); =20 - while (sw) { - SWVoiceCap *sc =3D (SWVoiceCap *) sw; -#ifdef DEBUG_CAPTURE - dolog ("freeing %s\n", sw->name); -#endif + for (i =3D 0; i < AUDIODEV_DRIVER__MAX; i++) { + const char *name =3D AudiodevDriver_str(i); + AudioBackendClass *be =3D audio_be_class_by_name(name); =20 - sw1 =3D sw->entries.le_next; - if (sw->rate) { - st_rate_stop (sw->rate); - sw->rate =3D NULL; - } - QLIST_REMOVE (sw, entries); - QLIST_REMOVE (sc, entries); - g_free (sc); - sw =3D sw1; - } - QLIST_REMOVE (cap, entries); - g_free(cap->hw.mix_buf.buffer); - g_free (cap->buf); - g_free (cap); - } - return; + if (be) { + printf("%s\n", name); } } } =20 -static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceO= ut *sw, - Volume *vol) +void audio_parse_option(const char *opt) { - if (sw) { - HWVoiceOut *hw =3D sw->hw; - - sw->vol.mute =3D vol->mute; - sw->vol.l =3D nominal_volume.l * vol->vol[0] / 255; - sw->vol.r =3D nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : = 0] / - 255; + Audiodev *dev =3D NULL; =20 - if (hw->pcm_ops->volume_out) { - hw->pcm_ops->volume_out(hw, vol); - } + if (is_help_option(opt)) { + audio_help(); + exit(EXIT_SUCCESS); } -} - -static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn= *sw, - Volume *vol) -{ - if (sw) { - HWVoiceIn *hw =3D sw->hw; - - sw->vol.mute =3D vol->mute; - sw->vol.l =3D nominal_volume.l * vol->vol[0] / 255; - sw->vol.r =3D nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : = 0] / - 255; + Visitor *v =3D qobject_input_visitor_new_str(opt, "driver", &error_fat= al); + visit_type_Audiodev(v, NULL, &dev, &error_fatal); + visit_free(v); =20 - if (hw->pcm_ops->volume_in) { - hw->pcm_ops->volume_in(hw, vol); - } - } + audio_add_audiodev(dev); } =20 static void audio_create_pdos(Audiodev *dev) @@ -2103,6 +266,124 @@ static void audio_validate_per_direction_opts( } } =20 +static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev) +{ + switch (dev->driver) { + case AUDIODEV_DRIVER_NONE: + return dev->u.none.out; +#ifdef CONFIG_AUDIO_ALSA + case AUDIODEV_DRIVER_ALSA: + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out); +#endif +#ifdef CONFIG_AUDIO_COREAUDIO + case AUDIODEV_DRIVER_COREAUDIO: + return qapi_AudiodevCoreaudioPerDirectionOptions_base( + dev->u.coreaudio.out); +#endif +#ifdef CONFIG_DBUS_DISPLAY + case AUDIODEV_DRIVER_DBUS: + return dev->u.dbus.out; +#endif +#ifdef CONFIG_AUDIO_DSOUND + case AUDIODEV_DRIVER_DSOUND: + return dev->u.dsound.out; +#endif +#ifdef CONFIG_AUDIO_JACK + case AUDIODEV_DRIVER_JACK: + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out); +#endif +#ifdef CONFIG_AUDIO_OSS + case AUDIODEV_DRIVER_OSS: + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out); +#endif +#ifdef CONFIG_AUDIO_PA + case AUDIODEV_DRIVER_PA: + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out); +#endif +#ifdef CONFIG_AUDIO_PIPEWIRE + case AUDIODEV_DRIVER_PIPEWIRE: + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewi= re.out); +#endif +#ifdef CONFIG_AUDIO_SDL + case AUDIODEV_DRIVER_SDL: + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out); +#endif +#ifdef CONFIG_AUDIO_SNDIO + case AUDIODEV_DRIVER_SNDIO: + return dev->u.sndio.out; +#endif +#ifdef CONFIG_SPICE + case AUDIODEV_DRIVER_SPICE: + return dev->u.spice.out; +#endif + case AUDIODEV_DRIVER_WAV: + return dev->u.wav.out; + + case AUDIODEV_DRIVER__MAX: + break; + } + abort(); +} + +static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev) +{ + switch (dev->driver) { + case AUDIODEV_DRIVER_NONE: + return dev->u.none.in; +#ifdef CONFIG_AUDIO_ALSA + case AUDIODEV_DRIVER_ALSA: + return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in); +#endif +#ifdef CONFIG_AUDIO_COREAUDIO + case AUDIODEV_DRIVER_COREAUDIO: + return qapi_AudiodevCoreaudioPerDirectionOptions_base( + dev->u.coreaudio.in); +#endif +#ifdef CONFIG_DBUS_DISPLAY + case AUDIODEV_DRIVER_DBUS: + return dev->u.dbus.in; +#endif +#ifdef CONFIG_AUDIO_DSOUND + case AUDIODEV_DRIVER_DSOUND: + return dev->u.dsound.in; +#endif +#ifdef CONFIG_AUDIO_JACK + case AUDIODEV_DRIVER_JACK: + return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in); +#endif +#ifdef CONFIG_AUDIO_OSS + case AUDIODEV_DRIVER_OSS: + return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in); +#endif +#ifdef CONFIG_AUDIO_PA + case AUDIODEV_DRIVER_PA: + return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in); +#endif +#ifdef CONFIG_AUDIO_PIPEWIRE + case AUDIODEV_DRIVER_PIPEWIRE: + return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewi= re.in); +#endif +#ifdef CONFIG_AUDIO_SDL + case AUDIODEV_DRIVER_SDL: + return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in); +#endif +#ifdef CONFIG_AUDIO_SNDIO + case AUDIODEV_DRIVER_SNDIO: + return dev->u.sndio.in; +#endif +#ifdef CONFIG_SPICE + case AUDIODEV_DRIVER_SPICE: + return dev->u.spice.in; +#endif + case AUDIODEV_DRIVER_WAV: + return dev->u.wav.in; + + case AUDIODEV_DRIVER__MAX: + break; + } + abort(); +} + static void audio_validate_opts(Audiodev *dev, Error **errp) { Error *err =3D NULL; @@ -2127,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error = **errp) } } =20 -void audio_help(void) -{ - int i; - - printf("Available audio drivers:\n"); - - for (i =3D 0; i < AUDIODEV_DRIVER__MAX; i++) { - const char *name =3D AudiodevDriver_str(i); - AudioBackendClass *be =3D audio_be_class_by_name(name); - - if (be) { - printf("%s\n", name); - } - } -} - -void audio_parse_option(const char *opt) -{ - Audiodev *dev =3D NULL; - - if (is_help_option(opt)) { - audio_help(); - exit(EXIT_SUCCESS); - } - Visitor *v =3D qobject_input_visitor_new_str(opt, "driver", &error_fat= al); - visit_type_Audiodev(v, NULL, &dev, &error_fatal); - visit_free(v); - - audio_add_audiodev(dev); -} - void audio_add_audiodev(Audiodev *dev) { AudiodevListEntry *e; @@ -2189,65 +439,6 @@ void audio_init_audiodevs(void) } } =20 -audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo) -{ - return (audsettings) { - .freq =3D pdo->frequency, - .nchannels =3D pdo->channels, - .fmt =3D pdo->format, - .endianness =3D HOST_BIG_ENDIAN, - }; -} - -int audioformat_bytes_per_sample(AudioFormat fmt) -{ - switch (fmt) { - case AUDIO_FORMAT_U8: - case AUDIO_FORMAT_S8: - return 1; - - case AUDIO_FORMAT_U16: - case AUDIO_FORMAT_S16: - return 2; - - case AUDIO_FORMAT_U32: - case AUDIO_FORMAT_S32: - case AUDIO_FORMAT_F32: - return 4; - - case AUDIO_FORMAT__MAX: - ; - } - abort(); -} - - -/* frames =3D freq * usec / 1e6 */ -int audio_buffer_frames(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - uint64_t usecs =3D pdo->has_buffer_length ? pdo->buffer_length : def_u= secs; - return (as->freq * usecs + 500000) / 1000000; -} - -/* samples =3D channels * frames =3D channels * freq * usec / 1e6 */ -int audio_buffer_samples(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - return as->nchannels * audio_buffer_frames(pdo, as, def_usecs); -} - -/* - * bytes =3D bytes_per_sample * samples =3D - * bytes_per_sample * channels * freq * usec / 1e6 - */ -int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo, - audsettings *as, int def_usecs) -{ - return audio_buffer_samples(pdo, as, def_usecs) * - audioformat_bytes_per_sample(as->fmt); -} - AudioBackend *audio_be_by_name(const char *name, Error **errp) { Object *obj =3D object_resolve_path_component(get_audiodevs_root(), na= me); @@ -2268,51 +459,6 @@ const char *audio_application_name(void) return vm_name ? vm_name : "qemu"; } =20 -void audio_rate_start(RateCtl *rate) -{ - memset(rate, 0, sizeof(RateCtl)); - rate->start_ticks =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); -} - -size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info) -{ - int64_t now; - int64_t ticks; - int64_t bytes; - int64_t frames; - - now =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); - ticks =3D now - rate->start_ticks; - bytes =3D muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECO= ND); - frames =3D (bytes - rate->bytes_sent) / info->bytes_per_frame; - rate->peeked_frames =3D frames; - - return frames < 0 ? 0 : frames * info->bytes_per_frame; -} - -void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used) -{ - if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) { - AUD_log(NULL, "Resetting rate control (%" PRId64 " frames)\n", - rate->peeked_frames); - audio_rate_start(rate); - } - - rate->bytes_sent +=3D bytes_used; -} - -size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info, - size_t bytes_avail) -{ - size_t bytes; - - bytes =3D audio_rate_peek_bytes(rate, info); - bytes =3D MIN(bytes, bytes_avail); - audio_rate_add_bytes(rate, bytes); - - return bytes; -} - AudiodevList *qmp_query_audiodevs(Error **errp) { AudiodevList *ret =3D NULL; @@ -2322,21 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp) } return ret; } - -static const TypeInfo audio_mixeng_backend_info =3D { - .name =3D TYPE_AUDIO_MIXENG_BACKEND, - .parent =3D TYPE_AUDIO_BACKEND, - .instance_size =3D sizeof(AudioMixengBackend), - .instance_init =3D audio_mixeng_backend_init, - .instance_finalize =3D audio_mixeng_backend_finalize, - .abstract =3D false, - .class_size =3D sizeof(AudioMixengBackendClass), - .class_init =3D audio_mixeng_backend_class_init, -}; - -static void register_types(void) -{ - type_register_static(&audio_mixeng_backend_info); -} - -type_init(register_types); diff --git a/audio/meson.build b/audio/meson.build index 417670bd4c7..d2ff49280ba 100644 --- a/audio/meson.build +++ b/audio/meson.build @@ -2,6 +2,7 @@ audio_ss =3D ss.source_set() audio_ss.add(files( 'audio.c', 'audio-be.c', + 'audio-mixeng-be.c', 'mixeng.c', 'noaudio.c', 'wavaudio.c', --=20 2.52.0