Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2196830/?format=api
{ "id": 2196830, "url": "http://patchwork.ozlabs.org/api/patches/2196830/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260216-audio-v1-40-e676662e4514@redhat.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260216-audio-v1-40-e676662e4514@redhat.com>", "list_archive_url": null, "date": "2026-02-16T11:15:29", "name": "[40/85] audio: split AudioMixengBackend code in audio-mixeng-be.c", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "3aad0d8cc579f391cbed19cf2ac3b8c77050d71c", "submitter": { "id": 66774, "url": "http://patchwork.ozlabs.org/api/people/66774/?format=api", "name": "Marc-André Lureau", "email": "marcandre.lureau@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260216-audio-v1-40-e676662e4514@redhat.com/mbox/", "series": [ { "id": 492294, "url": "http://patchwork.ozlabs.org/api/series/492294/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=492294", "date": "2026-02-16T11:14:52", "name": "audio: cleanups & add a manual test", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/492294/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2196830/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2196830/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=afs/Byea;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fF0ks4wrYz1xpY\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 16 Feb 2026 22:23:17 +1100 (AEDT)", "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1vrweH-000321-Uc; Mon, 16 Feb 2026 06:20:18 -0500", "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1vrwbt-00066N-6F\n for qemu-devel@nongnu.org; Mon, 16 Feb 2026 06:17:50 -0500", "from us-smtp-delivery-124.mimecast.com ([170.10.129.124])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <marcandre.lureau@redhat.com>)\n id 1vrwbn-00085a-Uv\n for qemu-devel@nongnu.org; Mon, 16 Feb 2026 06:17:48 -0500", "from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com\n (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by\n relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n cipher=TLS_AES_256_GCM_SHA384) id us-mta-335-dCZx-Z68PK2mogcB9TQqZw-1; Mon,\n 16 Feb 2026 06:17:32 -0500", "from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n (mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.93])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS\n id 7F78819560B2; Mon, 16 Feb 2026 11:17:30 +0000 (UTC)", "from localhost (unknown [10.45.242.26])\n by mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP\n id 00BBA18004AD; Mon, 16 Feb 2026 11:17:28 +0000 (UTC)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1771240656;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=II/peA+q82xMlUb9oma13QrmJYqBB9KQPJ/lpc1p7P4=;\n b=afs/Byeasl9qq6Sf1goRtl91LEO2Gp+P/YU+SEaGzkR1nW4ljE8UJVBZ8pZt+ihDUbs1qK\n czZHdswDq9eSRmPfSs6kPhdDLlN8xmT1HiC3oIahr5U+A4qIM+EoAmlov7h0xowNyzSPHi\n J14QwlPyiAid5JfwmmvdsOlstgSdRfo=", "X-MC-Unique": "dCZx-Z68PK2mogcB9TQqZw-1", "X-Mimecast-MFC-AGG-ID": "dCZx-Z68PK2mogcB9TQqZw_1771240650", "From": "=?utf-8?q?Marc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>", "Date": "Mon, 16 Feb 2026 12:15:29 +0100", "Subject": "[PATCH 40/85] audio: split AudioMixengBackend code in\n audio-mixeng-be.c", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "8bit", "Message-Id": "<20260216-audio-v1-40-e676662e4514@redhat.com>", "References": "<20260216-audio-v1-0-e676662e4514@redhat.com>", "In-Reply-To": "<20260216-audio-v1-0-e676662e4514@redhat.com>", "To": "qemu-devel@nongnu.org", "Cc": "Gerd Hoffmann <kraxel@redhat.com>, Eduardo Habkost <eduardo@habkost.net>,\n Paolo Bonzini <pbonzini@redhat.com>,\n =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= <berrange@redhat.com>, =?utf-8?q?Phil?=\n\t=?utf-8?q?ippe_Mathieu-Daud=C3=A9?= <philmd@linaro.org>,\n John Snow <jsnow@redhat.com>, Cleber Rosa <crosa@redhat.com>,\n Christian Schoenebeck <qemu_oss@crudebyte.com>,\n Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>,\n Thomas Huth <huth@tuxfamily.org>, Alexandre Ratchov <alex@caoua.org>,\n\t=?utf-8?q?Alex_Benn=C3=A9e?= <alex.bennee@linaro.org>,\n Laurent Vivier <laurent@vivier.eu>, \"Michael S. Tsirkin\" <mst@redhat.com>,\n Manos Pitsidianakis <manos.pitsidianakis@linaro.org>,\n Alistair Francis <alistair@alistair23.me>,\n \"Edgar E. Iglesias\" <edgar.iglesias@gmail.com>,\n Peter Maydell <peter.maydell@linaro.org>, qemu-arm@nongnu.org, =?utf-8?q?M?=\n\t=?utf-8?q?arc-Andr=C3=A9_Lureau?= <marcandre.lureau@redhat.com>,\n Mark Cave-Ayland <mark.caveayland@nutanix.com>", "X-Developer-Signature": "v=1; a=openpgp-sha256; l=129255;\n i=marcandre.lureau@redhat.com; h=from:subject:message-id;\n bh=SYwwr9Fv/ErOF8w3ompPE10xHRSMs0ku9X6Rs2Ehs6A=;\n b=owEBbQKS/ZANAwAKAdro4Ql1lpzlAcsmYgBpkvxQTD463Yp8QBtSLnkm4/6SBt8F22XjZYSyF\n /ZgarWBmQGJAjMEAAEKAB0WIQSHqb2TP4fGBtJ29i3a6OEJdZac5QUCaZL8UAAKCRDa6OEJdZac\n 5U67D/4vu0Wva0slaEFvvhM/f6Uw8lBafldZJFqLdI7riSLvqNSsKNSq0CNmgp09cvlPnwZohcM\n cmqDARptiopkoUk+AUUmfWNasw2N7+irK8WYGP0DOMTWmLnk+s5Nao0bx/jIcoaL455myBtbZ6B\n g893XYU8KPe8+Q/dGWE+uTt78dCe61Ok3ZnqFX+tl+h+2f9QbxF//cbjiWSBJ0A5oAIx8pFPwQ7\n s+tcAypQARLiCOK+gRaTFlfKSs8M2qfzjXiPszyo26w6dLGvnBZToNDGjjJl+WQM6fA54qZb64j\n y7Fiv4eNdPB/RW7lm+hh68Hg9XWt6NKtzqhRcXuzMHyPtkjcwuDPAnuRHiE/4uc+fYgi+NQ7yfR\n Qu5WRtGHaq2SRkc6FoWXJWOImBT7LmMy1JXjwu8Yzm9dpjglNFzPYPuHYa5S2Kt/sjVQq7OFlfp\n YJ3lPFYPI+0YOsfderMMjwNNU76HQ+BuRCwGAHRBaBRCW0vBsMk5fvgXxoWJ3s61JZFuh8yg6Te\n Gv2l75X9ZN853a+HX+gXSCtDj1dJmctkktw6lax1dKKmXP7Geers8VQTDrmHy2apuHM+gVzj+2/\n ZkHhetkPrD8RasWP2cXES8jCzBuDoodUnL4F+eNkR93XYMeTaYiEcHOTBmiooJ6OyPn116hnDt0\n oFj8N1+Kh+1qPCw==", "X-Developer-Key": "i=marcandre.lureau@redhat.com; a=openpgp;\n fpr=87A9BD933F87C606D276F62DDAE8E10975969CE5", "X-Scanned-By": "MIMEDefang 3.4.1 on 10.30.177.93", "Received-SPF": "pass client-ip=170.10.129.124;\n envelope-from=marcandre.lureau@redhat.com;\n helo=us-smtp-delivery-124.mimecast.com", "X-Spam_score_int": "-20", "X-Spam_score": "-2.1", "X-Spam_bar": "--", "X-Spam_report": "(-2.1 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.001,\n DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1,\n RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001,\n RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001,\n 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 <qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org" }, "content": "Allow to build the audio/ base classes without the\nresampling/mixing/queuing code.\n\nSigned-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>\nReviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com>\n---\n audio/audio_int.h | 2 -\n include/qemu/audio.h | 2 +\n audio/audio-be.c | 2 +\n audio/audio-mixeng-be.c | 1976 +++++++++++++++++++++++++++++++++++++++++\n audio/audio.c | 2223 ++++-------------------------------------------\n audio/meson.build | 1 +\n 6 files changed, 2158 insertions(+), 2048 deletions(-)", "diff": "diff --git a/audio/audio_int.h b/audio/audio_int.h\nindex 250fd45238d..6ecd75c4fbf 100644\n--- a/audio/audio_int.h\n+++ b/audio/audio_int.h\n@@ -266,8 +266,6 @@ int audio_bug (const char *funcname, int cond);\n \n void audio_run(AudioMixengBackend *s, const char *msg);\n \n-const char *audio_application_name(void);\n-\n typedef struct RateCtl {\n int64_t start_ticks;\n int64_t bytes_sent;\ndiff --git a/include/qemu/audio.h b/include/qemu/audio.h\nindex 42f97f732a6..dfe247ab8c4 100644\n--- a/include/qemu/audio.h\n+++ b/include/qemu/audio.h\n@@ -183,6 +183,8 @@ bool audio_be_set_dbus_server(AudioBackend *be,\n Error **errp);\n #endif\n \n+const char *audio_application_name(void);\n+\n #define DEFINE_AUDIO_PROPERTIES(_s, _f) \\\n DEFINE_PROP_AUDIODEV(\"audiodev\", _s, _f)\n \ndiff --git a/audio/audio-be.c b/audio/audio-be.c\nindex 3203bb80e05..8094a3196c7 100644\n--- a/audio/audio-be.c\n+++ b/audio/audio-be.c\n@@ -5,6 +5,7 @@\n #include \"qemu/audio-capture.h\"\n #include \"qapi/error.h\"\n #include \"trace.h\"\n+#include \"qapi-types-audio.h\"\n \n bool audio_be_check(AudioBackend **be, Error **errp)\n {\n@@ -248,6 +249,7 @@ AudioBackend *audio_be_new(Audiodev *dev, Error **errp)\n \n if (!be) {\n error_setg(errp, \"Unknown audio driver `%s'\", drvname);\n+ qapi_free_Audiodev(dev);\n return NULL;\n }\n \ndiff --git a/audio/audio-mixeng-be.c b/audio/audio-mixeng-be.c\nnew file mode 100644\nindex 00000000000..f906c84bf7c\n--- /dev/null\n+++ b/audio/audio-mixeng-be.c\n@@ -0,0 +1,1976 @@\n+/*\n+ * SPDX-License-Identifier: MIT\n+ *\n+ * Copyright (c) 2003-2005 Vassili Karpov (malc)\n+ */\n+\n+#include \"qemu/osdep.h\"\n+#include \"qemu/audio.h\"\n+#include \"migration/vmstate.h\"\n+#include \"qemu/bswap.h\"\n+#include \"qemu/timer.h\"\n+#include \"qapi/error.h\"\n+#include \"qapi/clone-visitor.h\"\n+#include \"qapi/qobject-input-visitor.h\"\n+#include \"qapi/qapi-visit-audio.h\"\n+#include \"qapi/qapi-commands-audio.h\"\n+#include \"qobject/qdict.h\"\n+#include \"qemu/error-report.h\"\n+#include \"qemu/log.h\"\n+#include \"qemu/module.h\"\n+#include \"qemu/help_option.h\"\n+#include \"qom/object.h\"\n+#include \"system/system.h\"\n+#include \"system/replay.h\"\n+#include \"system/runstate.h\"\n+#include \"trace.h\"\n+\n+#define AUDIO_CAP \"audio\"\n+#include \"audio_int.h\"\n+\n+/* #define DEBUG_OUT */\n+/* #define DEBUG_CAPTURE */\n+/* #define DEBUG_POLL */\n+\n+#define SW_NAME(sw) (sw)->name ? (sw)->name : \"unknown\"\n+\n+const struct mixeng_volume nominal_volume = {\n+ .mute = 0,\n+#ifdef FLOAT_MIXENG\n+ .r = 1.0,\n+ .l = 1.0,\n+#else\n+ .r = 1ULL << 32,\n+ .l = 1ULL << 32,\n+#endif\n+};\n+\n+int audio_bug (const char *funcname, int cond)\n+{\n+ if (cond) {\n+ static int shown;\n+\n+ AUD_log (NULL, \"A bug was just triggered in %s\\n\", funcname);\n+ if (!shown) {\n+ shown = 1;\n+ AUD_log (NULL, \"Save all your work and restart without audio\\n\");\n+ AUD_log (NULL, \"I am sorry\\n\");\n+ }\n+ AUD_log (NULL, \"Context:\\n\");\n+ }\n+\n+ return cond;\n+}\n+\n+static inline int audio_bits_to_index (int bits)\n+{\n+ switch (bits) {\n+ case 8:\n+ return 0;\n+\n+ case 16:\n+ return 1;\n+\n+ case 32:\n+ return 2;\n+\n+ default:\n+ audio_bug (\"bits_to_index\", 1);\n+ AUD_log (NULL, \"invalid bits %d\\n\", bits);\n+ return 0;\n+ }\n+}\n+\n+void AUD_vlog (const char *cap, const char *fmt, va_list ap)\n+{\n+ if (cap) {\n+ fprintf(stderr, \"%s: \", cap);\n+ }\n+\n+ vfprintf(stderr, fmt, ap);\n+}\n+\n+void AUD_log (const char *cap, const char *fmt, ...)\n+{\n+ va_list ap;\n+\n+ va_start (ap, fmt);\n+ AUD_vlog (cap, fmt, ap);\n+ va_end (ap);\n+}\n+\n+static void audio_print_settings (const struct audsettings *as)\n+{\n+ dolog (\"frequency=%d nchannels=%d fmt=\", as->freq, as->nchannels);\n+\n+ switch (as->fmt) {\n+ case AUDIO_FORMAT_S8:\n+ AUD_log (NULL, \"S8\");\n+ break;\n+ case AUDIO_FORMAT_U8:\n+ AUD_log (NULL, \"U8\");\n+ break;\n+ case AUDIO_FORMAT_S16:\n+ AUD_log (NULL, \"S16\");\n+ break;\n+ case AUDIO_FORMAT_U16:\n+ AUD_log (NULL, \"U16\");\n+ break;\n+ case AUDIO_FORMAT_S32:\n+ AUD_log (NULL, \"S32\");\n+ break;\n+ case AUDIO_FORMAT_U32:\n+ AUD_log (NULL, \"U32\");\n+ break;\n+ case AUDIO_FORMAT_F32:\n+ AUD_log (NULL, \"F32\");\n+ break;\n+ default:\n+ AUD_log (NULL, \"invalid(%d)\", as->fmt);\n+ break;\n+ }\n+\n+ AUD_log (NULL, \" endianness=\");\n+ switch (as->endianness) {\n+ case 0:\n+ AUD_log (NULL, \"little\");\n+ break;\n+ case 1:\n+ AUD_log (NULL, \"big\");\n+ break;\n+ default:\n+ AUD_log (NULL, \"invalid\");\n+ break;\n+ }\n+ AUD_log (NULL, \"\\n\");\n+}\n+\n+static int audio_validate_settings (const struct audsettings *as)\n+{\n+ int invalid;\n+\n+ invalid = as->nchannels < 1;\n+ invalid |= as->endianness != 0 && as->endianness != 1;\n+\n+ switch (as->fmt) {\n+ case AUDIO_FORMAT_S8:\n+ case AUDIO_FORMAT_U8:\n+ case AUDIO_FORMAT_S16:\n+ case AUDIO_FORMAT_U16:\n+ case AUDIO_FORMAT_S32:\n+ case AUDIO_FORMAT_U32:\n+ case AUDIO_FORMAT_F32:\n+ break;\n+ default:\n+ invalid = 1;\n+ break;\n+ }\n+\n+ invalid |= as->freq <= 0;\n+ return invalid ? -1 : 0;\n+}\n+\n+static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)\n+{\n+ int bits = 8;\n+ bool is_signed = false, is_float = false;\n+\n+ switch (as->fmt) {\n+ case AUDIO_FORMAT_S8:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U8:\n+ break;\n+\n+ case AUDIO_FORMAT_S16:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U16:\n+ bits = 16;\n+ break;\n+\n+ case AUDIO_FORMAT_F32:\n+ is_float = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_S32:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U32:\n+ bits = 32;\n+ break;\n+\n+ default:\n+ abort();\n+ }\n+ return info->freq == as->freq\n+ && info->nchannels == as->nchannels\n+ && info->is_signed == is_signed\n+ && info->is_float == is_float\n+ && info->bits == bits\n+ && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);\n+}\n+\n+void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)\n+{\n+ int bits = 8, mul;\n+ bool is_signed = false, is_float = false;\n+\n+ switch (as->fmt) {\n+ case AUDIO_FORMAT_S8:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U8:\n+ mul = 1;\n+ break;\n+\n+ case AUDIO_FORMAT_S16:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U16:\n+ bits = 16;\n+ mul = 2;\n+ break;\n+\n+ case AUDIO_FORMAT_F32:\n+ is_float = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_S32:\n+ is_signed = true;\n+ /* fall through */\n+ case AUDIO_FORMAT_U32:\n+ bits = 32;\n+ mul = 4;\n+ break;\n+\n+ default:\n+ abort();\n+ }\n+\n+ info->freq = as->freq;\n+ info->bits = bits;\n+ info->is_signed = is_signed;\n+ info->is_float = is_float;\n+ info->nchannels = as->nchannels;\n+ info->bytes_per_frame = as->nchannels * mul;\n+ info->bytes_per_second = info->freq * info->bytes_per_frame;\n+ info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);\n+}\n+\n+void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)\n+{\n+ if (!len) {\n+ return;\n+ }\n+\n+ if (info->is_signed || info->is_float) {\n+ memset(buf, 0x00, len * info->bytes_per_frame);\n+ } else {\n+ switch (info->bits) {\n+ case 8:\n+ memset(buf, 0x80, len * info->bytes_per_frame);\n+ break;\n+\n+ case 16:\n+ {\n+ int i;\n+ uint16_t *p = buf;\n+ short s = INT16_MAX;\n+\n+ if (info->swap_endianness) {\n+ s = bswap16 (s);\n+ }\n+\n+ for (i = 0; i < len * info->nchannels; i++) {\n+ p[i] = s;\n+ }\n+ }\n+ break;\n+\n+ case 32:\n+ {\n+ int i;\n+ uint32_t *p = buf;\n+ int32_t s = INT32_MAX;\n+\n+ if (info->swap_endianness) {\n+ s = bswap32 (s);\n+ }\n+\n+ for (i = 0; i < len * info->nchannels; i++) {\n+ p[i] = s;\n+ }\n+ }\n+ break;\n+\n+ default:\n+ AUD_log (NULL, \"audio_pcm_info_clear_buf: invalid bits %d\\n\",\n+ info->bits);\n+ break;\n+ }\n+ }\n+}\n+\n+/*\n+ * Capture\n+ */\n+static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,\n+ struct audsettings *as)\n+{\n+ CaptureVoiceOut *cap;\n+\n+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n+ if (audio_pcm_info_eq (&cap->hw.info, as)) {\n+ return cap;\n+ }\n+ }\n+ return NULL;\n+}\n+\n+static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)\n+{\n+ struct capture_callback *cb;\n+\n+#ifdef DEBUG_CAPTURE\n+ dolog (\"notification %d sent\\n\", cmd);\n+#endif\n+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n+ cb->ops.notify (cb->opaque, cmd);\n+ }\n+}\n+\n+static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)\n+{\n+ if (cap->hw.enabled != enabled) {\n+ audcnotification_e cmd;\n+ cap->hw.enabled = enabled;\n+ cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;\n+ audio_notify_capture (cap, cmd);\n+ }\n+}\n+\n+static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)\n+{\n+ HWVoiceOut *hw = &cap->hw;\n+ SWVoiceOut *sw;\n+ bool enabled = false;\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (sw->active) {\n+ enabled = true;\n+ break;\n+ }\n+ }\n+ audio_capture_maybe_changed (cap, enabled);\n+}\n+\n+static void audio_detach_capture (HWVoiceOut *hw)\n+{\n+ SWVoiceCap *sc = hw->cap_head.lh_first;\n+\n+ while (sc) {\n+ SWVoiceCap *sc1 = sc->entries.le_next;\n+ SWVoiceOut *sw = &sc->sw;\n+ CaptureVoiceOut *cap = sc->cap;\n+ int was_active = sw->active;\n+\n+ if (sw->rate) {\n+ st_rate_stop (sw->rate);\n+ sw->rate = NULL;\n+ }\n+\n+ QLIST_REMOVE (sw, entries);\n+ QLIST_REMOVE (sc, entries);\n+ g_free (sc);\n+ if (was_active) {\n+ /* We have removed soft voice from the capture:\n+ this might have changed the overall status of the capture\n+ since this might have been the only active voice */\n+ audio_recalc_and_notify_capture (cap);\n+ }\n+ sc = sc1;\n+ }\n+}\n+\n+static int audio_attach_capture (HWVoiceOut *hw)\n+{\n+ AudioMixengBackend *s = hw->s;\n+ CaptureVoiceOut *cap;\n+\n+ audio_detach_capture (hw);\n+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n+ SWVoiceCap *sc;\n+ SWVoiceOut *sw;\n+ HWVoiceOut *hw_cap = &cap->hw;\n+\n+ sc = g_malloc0(sizeof(*sc));\n+\n+ sc->cap = cap;\n+ sw = &sc->sw;\n+ sw->hw = hw_cap;\n+ sw->info = hw->info;\n+ sw->empty = true;\n+ sw->active = hw->enabled;\n+ sw->vol = nominal_volume;\n+ sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);\n+ QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);\n+ QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);\n+#ifdef DEBUG_CAPTURE\n+ sw->name = g_strdup_printf (\"for %p %d,%d,%d\",\n+ hw, sw->info.freq, sw->info.bits,\n+ sw->info.nchannels);\n+ dolog (\"Added %s active = %d\\n\", sw->name, sw->active);\n+#endif\n+ if (sw->active) {\n+ audio_capture_maybe_changed (cap, 1);\n+ }\n+ }\n+ return 0;\n+}\n+\n+/*\n+ * Hard voice (capture)\n+ */\n+static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)\n+{\n+ SWVoiceIn *sw;\n+ size_t m = hw->total_samples_captured;\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (sw->active) {\n+ m = MIN (m, sw->total_hw_samples_acquired);\n+ }\n+ }\n+ return m;\n+}\n+\n+static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)\n+{\n+ size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);\n+ if (audio_bug(__func__, live > hw->conv_buf.size)) {\n+ dolog(\"live=%zu hw->conv_buf.size=%zu\\n\", live, hw->conv_buf.size);\n+ return 0;\n+ }\n+ return live;\n+}\n+\n+static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)\n+{\n+ size_t conv = 0;\n+ STSampleBuffer *conv_buf = &hw->conv_buf;\n+\n+ while (samples) {\n+ uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);\n+ size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);\n+\n+ hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);\n+ conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;\n+ samples -= proc;\n+ conv += proc;\n+ }\n+\n+ return conv;\n+}\n+\n+/*\n+ * Soft voice (capture)\n+ */\n+static void audio_pcm_sw_resample_in(SWVoiceIn *sw,\n+ size_t frames_in_max, size_t frames_out_max,\n+ size_t *total_in, size_t *total_out)\n+{\n+ HWVoiceIn *hw = sw->hw;\n+ struct st_sample *src, *dst;\n+ size_t live, rpos, frames_in, frames_out;\n+\n+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;\n+ rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);\n+\n+ /* resample conv_buf from rpos to end of buffer */\n+ src = hw->conv_buf.buffer + rpos;\n+ frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);\n+ dst = sw->resample_buf.buffer;\n+ frames_out = frames_out_max;\n+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);\n+ rpos += frames_in;\n+ *total_in = frames_in;\n+ *total_out = frames_out;\n+\n+ /* resample conv_buf from start of buffer if there are input frames left */\n+ if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {\n+ src = hw->conv_buf.buffer;\n+ frames_in = frames_in_max - frames_in;\n+ dst += frames_out;\n+ frames_out = frames_out_max - frames_out;\n+ st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);\n+ *total_in += frames_in;\n+ *total_out += frames_out;\n+ }\n+}\n+\n+static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)\n+{\n+ HWVoiceIn *hw = sw->hw;\n+ size_t live, frames_out_max, total_in, total_out;\n+\n+ live = hw->total_samples_captured - sw->total_hw_samples_acquired;\n+ if (!live) {\n+ return 0;\n+ }\n+ if (audio_bug(__func__, live > hw->conv_buf.size)) {\n+ dolog(\"live_in=%zu hw->conv_buf.size=%zu\\n\", live, hw->conv_buf.size);\n+ return 0;\n+ }\n+\n+ frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,\n+ sw->resample_buf.size);\n+\n+ audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);\n+\n+ if (!hw->pcm_ops->volume_in) {\n+ mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);\n+ }\n+ sw->clip(buf, sw->resample_buf.buffer, total_out);\n+\n+ sw->total_hw_samples_acquired += total_in;\n+ return total_out * sw->info.bytes_per_frame;\n+}\n+\n+/*\n+ * Hard voice (playback)\n+ */\n+static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)\n+{\n+ SWVoiceOut *sw;\n+ size_t m = SIZE_MAX;\n+ int nb_live = 0;\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (sw->active || !sw->empty) {\n+ m = MIN (m, sw->total_hw_samples_mixed);\n+ nb_live += 1;\n+ }\n+ }\n+\n+ *nb_livep = nb_live;\n+ return m;\n+}\n+\n+static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)\n+{\n+ size_t smin;\n+ int nb_live1;\n+\n+ smin = audio_pcm_hw_find_min_out (hw, &nb_live1);\n+ if (nb_live) {\n+ *nb_live = nb_live1;\n+ }\n+\n+ if (nb_live1) {\n+ size_t live = smin;\n+\n+ if (audio_bug(__func__, live > hw->mix_buf.size)) {\n+ dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n+ return 0;\n+ }\n+ return live;\n+ }\n+ return 0;\n+}\n+\n+static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)\n+{\n+ return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :\n+ INT_MAX) / hw->info.bytes_per_frame;\n+}\n+\n+static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)\n+{\n+ size_t clipped = 0;\n+ size_t pos = hw->mix_buf.pos;\n+\n+ while (len) {\n+ st_sample *src = hw->mix_buf.buffer + pos;\n+ uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);\n+ size_t samples_till_end_of_buf = hw->mix_buf.size - pos;\n+ size_t samples_to_clip = MIN(len, samples_till_end_of_buf);\n+\n+ hw->clip(dst, src, samples_to_clip);\n+\n+ pos = (pos + samples_to_clip) % hw->mix_buf.size;\n+ len -= samples_to_clip;\n+ clipped += samples_to_clip;\n+ }\n+}\n+\n+/*\n+ * Soft voice (playback)\n+ */\n+static void audio_pcm_sw_resample_out(SWVoiceOut *sw,\n+ size_t frames_in_max, size_t frames_out_max,\n+ size_t *total_in, size_t *total_out)\n+{\n+ HWVoiceOut *hw = sw->hw;\n+ struct st_sample *src, *dst;\n+ size_t live, wpos, frames_in, frames_out;\n+\n+ live = sw->total_hw_samples_mixed;\n+ wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;\n+\n+ /* write to mix_buf from wpos to end of buffer */\n+ src = sw->resample_buf.buffer;\n+ frames_in = frames_in_max;\n+ dst = hw->mix_buf.buffer + wpos;\n+ frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);\n+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);\n+ wpos += frames_out;\n+ *total_in = frames_in;\n+ *total_out = frames_out;\n+\n+ /* write to mix_buf from start of buffer if there are input frames left */\n+ if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {\n+ src += frames_in;\n+ frames_in = frames_in_max - frames_in;\n+ dst = hw->mix_buf.buffer;\n+ frames_out = frames_out_max - frames_out;\n+ st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);\n+ *total_in += frames_in;\n+ *total_out += frames_out;\n+ }\n+}\n+\n+static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)\n+{\n+ HWVoiceOut *hw = sw->hw;\n+ size_t live, dead, hw_free, sw_max, fe_max;\n+ size_t frames_in_max, frames_out_max, total_in, total_out;\n+\n+ live = sw->total_hw_samples_mixed;\n+ if (audio_bug(__func__, live > hw->mix_buf.size)) {\n+ dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n+ return 0;\n+ }\n+\n+ if (live == hw->mix_buf.size) {\n+#ifdef DEBUG_OUT\n+ dolog (\"%s is full %zu\\n\", sw->name, live);\n+#endif\n+ return 0;\n+ }\n+\n+ dead = hw->mix_buf.size - live;\n+ hw_free = audio_pcm_hw_get_free(hw);\n+ hw_free = hw_free > live ? hw_free - live : 0;\n+ frames_out_max = MIN(dead, hw_free);\n+ sw_max = st_rate_frames_in(sw->rate, frames_out_max);\n+ fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,\n+ sw->resample_buf.size);\n+ frames_in_max = MIN(sw_max, fe_max);\n+\n+ if (!frames_in_max) {\n+ return 0;\n+ }\n+\n+ if (frames_in_max > sw->resample_buf.pos) {\n+ sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,\n+ buf, frames_in_max - sw->resample_buf.pos);\n+ if (!sw->hw->pcm_ops->volume_out) {\n+ mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,\n+ frames_in_max - sw->resample_buf.pos, &sw->vol);\n+ }\n+ }\n+\n+ audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,\n+ &total_in, &total_out);\n+\n+ sw->total_hw_samples_mixed += total_out;\n+ sw->empty = sw->total_hw_samples_mixed == 0;\n+\n+ /*\n+ * Upsampling may leave one audio frame in the resample buffer. Decrement\n+ * total_in by one if there was a leftover frame from the previous resample\n+ * pass in the resample buffer. Increment total_in by one if the current\n+ * resample pass left one frame in the resample buffer.\n+ */\n+ if (frames_in_max - total_in == 1) {\n+ /* copy one leftover audio frame to the beginning of the buffer */\n+ *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);\n+ total_in += 1 - sw->resample_buf.pos;\n+ sw->resample_buf.pos = 1;\n+ } else if (total_in >= sw->resample_buf.pos) {\n+ total_in -= sw->resample_buf.pos;\n+ sw->resample_buf.pos = 0;\n+ }\n+\n+#ifdef DEBUG_OUT\n+ dolog (\n+ \"%s: write size %zu written %zu total mixed %zu\\n\",\n+ SW_NAME(sw),\n+ buf_len / sw->info.bytes_per_frame,\n+ total_in,\n+ sw->total_hw_samples_mixed\n+ );\n+#endif\n+\n+ return total_in * sw->info.bytes_per_frame;\n+}\n+\n+#ifdef DEBUG_AUDIO\n+static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)\n+{\n+ dolog(\"%s: bits %d, sign %d, float %d, freq %d, nchan %d\\n\",\n+ cap, info->bits, info->is_signed, info->is_float, info->freq,\n+ info->nchannels);\n+}\n+#endif\n+\n+#define DAC\n+#include \"audio_template.h\"\n+#undef DAC\n+#include \"audio_template.h\"\n+\n+/*\n+ * Timer\n+ */\n+static int audio_is_timer_needed(AudioMixengBackend *s)\n+{\n+ HWVoiceIn *hwi = NULL;\n+ HWVoiceOut *hwo = NULL;\n+\n+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {\n+ if (!hwo->poll_mode) {\n+ return 1;\n+ }\n+ }\n+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {\n+ if (!hwi->poll_mode) {\n+ return 1;\n+ }\n+ }\n+ return 0;\n+}\n+\n+static void audio_reset_timer(AudioMixengBackend *s)\n+{\n+ if (audio_is_timer_needed(s)) {\n+ timer_mod_anticipate_ns(s->ts,\n+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);\n+ if (!s->timer_running) {\n+ s->timer_running = true;\n+ s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+ trace_audio_timer_start(s->period_ticks / SCALE_MS);\n+ }\n+ } else {\n+ timer_del(s->ts);\n+ if (s->timer_running) {\n+ s->timer_running = false;\n+ trace_audio_timer_stop();\n+ }\n+ }\n+}\n+\n+static void audio_timer (void *opaque)\n+{\n+ int64_t now, diff;\n+ AudioMixengBackend *s = opaque;\n+\n+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+ diff = now - s->timer_last;\n+ if (diff > s->period_ticks * 3 / 2) {\n+ trace_audio_timer_delayed(diff / SCALE_MS);\n+ }\n+ s->timer_last = now;\n+\n+ audio_run(s, \"timer\");\n+ audio_reset_timer(s);\n+}\n+\n+/*\n+ * Public API\n+ */\n+static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,\n+ void *buf, size_t size)\n+{\n+ HWVoiceOut *hw;\n+\n+ if (!sw) {\n+ /* XXX: Consider options */\n+ return size;\n+ }\n+ hw = sw->hw;\n+\n+ if (!hw->enabled) {\n+ dolog(\"Writing to disabled voice %s\\n\", SW_NAME(sw));\n+ return 0;\n+ }\n+\n+ if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {\n+ return audio_pcm_sw_write(sw, buf, size);\n+ } else {\n+ return hw->pcm_ops->write(hw, buf, size);\n+ }\n+}\n+\n+static size_t audio_mixeng_backend_read(AudioBackend *be,\n+ SWVoiceIn *sw, void *buf, size_t size)\n+{\n+ HWVoiceIn *hw;\n+\n+ if (!sw) {\n+ /* XXX: Consider options */\n+ return size;\n+ }\n+ hw = sw->hw;\n+\n+ if (!hw->enabled) {\n+ dolog(\"Reading from disabled voice %s\\n\", SW_NAME(sw));\n+ return 0;\n+ }\n+\n+ if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {\n+ return audio_pcm_sw_read(sw, buf, size);\n+ } else {\n+ return hw->pcm_ops->read(hw, buf, size);\n+ }\n+\n+}\n+\n+static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)\n+{\n+ if (!sw) {\n+ return 0;\n+ }\n+\n+ if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {\n+ return sw->resample_buf.size * sw->info.bytes_per_frame;\n+ }\n+\n+ return sw->hw->samples * sw->hw->info.bytes_per_frame;\n+}\n+\n+static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,\n+ bool on)\n+{\n+ HWVoiceOut *hw;\n+\n+ if (!sw) {\n+ return;\n+ }\n+\n+ hw = sw->hw;\n+ if (sw->active != on) {\n+ AudioMixengBackend *s = sw->s;\n+ SWVoiceOut *temp_sw;\n+ SWVoiceCap *sc;\n+\n+ if (on) {\n+ hw->pending_disable = 0;\n+ if (!hw->enabled) {\n+ hw->enabled = true;\n+ if (runstate_is_running()) {\n+ if (hw->pcm_ops->enable_out) {\n+ hw->pcm_ops->enable_out(hw, true);\n+ }\n+ audio_reset_timer (s);\n+ }\n+ }\n+ } else {\n+ if (hw->enabled) {\n+ int nb_active = 0;\n+\n+ for (temp_sw = hw->sw_head.lh_first; temp_sw;\n+ temp_sw = temp_sw->entries.le_next) {\n+ nb_active += temp_sw->active != 0;\n+ }\n+\n+ hw->pending_disable = nb_active == 1;\n+ }\n+ }\n+\n+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n+ sc->sw.active = hw->enabled;\n+ if (hw->enabled) {\n+ audio_capture_maybe_changed (sc->cap, 1);\n+ }\n+ }\n+ sw->active = on;\n+ }\n+\n+}\n+\n+static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)\n+{\n+ HWVoiceIn *hw;\n+\n+ if (!sw) {\n+ return;\n+ }\n+\n+ hw = sw->hw;\n+ if (sw->active != on) {\n+ AudioMixengBackend *s = sw->s;\n+ SWVoiceIn *temp_sw;\n+\n+ if (on) {\n+ if (!hw->enabled) {\n+ hw->enabled = true;\n+ if (runstate_is_running()) {\n+ if (hw->pcm_ops->enable_in) {\n+ hw->pcm_ops->enable_in(hw, true);\n+ }\n+ audio_reset_timer (s);\n+ }\n+ }\n+ sw->total_hw_samples_acquired = hw->total_samples_captured;\n+ } else {\n+ if (hw->enabled) {\n+ int nb_active = 0;\n+\n+ for (temp_sw = hw->sw_head.lh_first; temp_sw;\n+ temp_sw = temp_sw->entries.le_next) {\n+ nb_active += temp_sw->active != 0;\n+ }\n+\n+ if (nb_active == 1) {\n+ hw->enabled = false;\n+ if (hw->pcm_ops->enable_in) {\n+ hw->pcm_ops->enable_in(hw, false);\n+ }\n+ }\n+ }\n+ }\n+ sw->active = on;\n+ }\n+}\n+\n+static size_t audio_get_avail(SWVoiceIn *sw)\n+{\n+ size_t live;\n+\n+ if (!sw) {\n+ return 0;\n+ }\n+\n+ live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;\n+ if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {\n+ dolog(\"live=%zu sw->hw->conv_buf.size=%zu\\n\", live,\n+ sw->hw->conv_buf.size);\n+ return 0;\n+ }\n+\n+ ldebug (\n+ \"%s: get_avail live %zu frontend frames %u\\n\",\n+ SW_NAME (sw),\n+ live, st_rate_frames_out(sw->rate, live)\n+ );\n+\n+ return live;\n+}\n+\n+static size_t audio_get_free(SWVoiceOut *sw)\n+{\n+ size_t live, dead;\n+\n+ if (!sw) {\n+ return 0;\n+ }\n+\n+ live = sw->total_hw_samples_mixed;\n+\n+ if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {\n+ dolog(\"live=%zu sw->hw->mix_buf.size=%zu\\n\", live,\n+ sw->hw->mix_buf.size);\n+ return 0;\n+ }\n+\n+ dead = sw->hw->mix_buf.size - live;\n+\n+#ifdef DEBUG_OUT\n+ dolog(\"%s: get_free live %zu dead %zu frontend frames %u\\n\",\n+ SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));\n+#endif\n+\n+ return dead;\n+}\n+\n+static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,\n+ size_t samples)\n+{\n+ size_t n;\n+\n+ if (hw->enabled) {\n+ SWVoiceCap *sc;\n+\n+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n+ SWVoiceOut *sw = &sc->sw;\n+ size_t rpos2 = rpos;\n+\n+ n = samples;\n+ while (n) {\n+ size_t till_end_of_hw = hw->mix_buf.size - rpos2;\n+ size_t to_read = MIN(till_end_of_hw, n);\n+ size_t live, frames_in, frames_out;\n+\n+ sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;\n+ sw->resample_buf.size = to_read;\n+ live = sw->total_hw_samples_mixed;\n+\n+ audio_pcm_sw_resample_out(sw,\n+ to_read, sw->hw->mix_buf.size - live,\n+ &frames_in, &frames_out);\n+\n+ sw->total_hw_samples_mixed += frames_out;\n+ sw->empty = sw->total_hw_samples_mixed == 0;\n+\n+ if (to_read - frames_in) {\n+ dolog(\"Could not mix %zu frames into a capture \"\n+ \"buffer, mixed %zu\\n\",\n+ to_read, frames_in);\n+ break;\n+ }\n+ n -= to_read;\n+ rpos2 = (rpos2 + to_read) % hw->mix_buf.size;\n+ }\n+ }\n+ }\n+\n+ n = MIN(samples, hw->mix_buf.size - rpos);\n+ mixeng_clear(hw->mix_buf.buffer + rpos, n);\n+ mixeng_clear(hw->mix_buf.buffer, samples - n);\n+}\n+\n+static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)\n+{\n+ size_t clipped = 0;\n+\n+ while (live) {\n+ size_t size = live * hw->info.bytes_per_frame;\n+ size_t decr, proc;\n+ void *buf = hw->pcm_ops->get_buffer_out(hw, &size);\n+\n+ if (size == 0) {\n+ break;\n+ }\n+\n+ decr = MIN(size / hw->info.bytes_per_frame, live);\n+ if (buf) {\n+ audio_pcm_hw_clip_out(hw, buf, decr);\n+ }\n+ proc = hw->pcm_ops->put_buffer_out(hw, buf,\n+ decr * hw->info.bytes_per_frame) /\n+ hw->info.bytes_per_frame;\n+\n+ live -= proc;\n+ clipped += proc;\n+ hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;\n+\n+ if (proc == 0 || proc < decr) {\n+ break;\n+ }\n+ }\n+\n+ if (hw->pcm_ops->run_buffer_out) {\n+ hw->pcm_ops->run_buffer_out(hw);\n+ }\n+\n+ return clipped;\n+}\n+\n+static void audio_run_out(AudioMixengBackend *s)\n+{\n+ HWVoiceOut *hw = NULL;\n+ SWVoiceOut *sw;\n+\n+ while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {\n+ size_t played, live, prev_rpos;\n+ size_t hw_free = audio_pcm_hw_get_free(hw);\n+ int nb_live;\n+\n+ if (!audio_get_pdo_out(s->dev)->mixing_engine) {\n+ /* there is exactly 1 sw for each hw with no mixeng */\n+ sw = hw->sw_head.lh_first;\n+\n+ if (hw->pending_disable) {\n+ hw->enabled = false;\n+ hw->pending_disable = false;\n+ if (hw->pcm_ops->enable_out) {\n+ hw->pcm_ops->enable_out(hw, false);\n+ }\n+ }\n+\n+ if (sw->active) {\n+ sw->callback.fn(sw->callback.opaque,\n+ hw_free * sw->info.bytes_per_frame);\n+ }\n+\n+ if (hw->pcm_ops->run_buffer_out) {\n+ hw->pcm_ops->run_buffer_out(hw);\n+ }\n+\n+ continue;\n+ }\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (sw->active) {\n+ size_t sw_free = audio_get_free(sw);\n+ size_t free;\n+\n+ if (hw_free > sw->total_hw_samples_mixed) {\n+ free = st_rate_frames_in(sw->rate,\n+ MIN(sw_free, hw_free - sw->total_hw_samples_mixed));\n+ } else {\n+ free = 0;\n+ }\n+ if (free > sw->resample_buf.pos) {\n+ free = MIN(free, sw->resample_buf.size)\n+ - sw->resample_buf.pos;\n+ sw->callback.fn(sw->callback.opaque,\n+ free * sw->info.bytes_per_frame);\n+ }\n+ }\n+ }\n+\n+ live = audio_pcm_hw_get_live_out (hw, &nb_live);\n+ if (!nb_live) {\n+ live = 0;\n+ }\n+\n+ if (audio_bug(__func__, live > hw->mix_buf.size)) {\n+ dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n+ continue;\n+ }\n+\n+ if (hw->pending_disable && !nb_live) {\n+ SWVoiceCap *sc;\n+#ifdef DEBUG_OUT\n+ dolog (\"Disabling voice\\n\");\n+#endif\n+ hw->enabled = false;\n+ hw->pending_disable = false;\n+ if (hw->pcm_ops->enable_out) {\n+ hw->pcm_ops->enable_out(hw, false);\n+ }\n+ for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n+ sc->sw.active = false;\n+ audio_recalc_and_notify_capture (sc->cap);\n+ }\n+ continue;\n+ }\n+\n+ if (!live) {\n+ if (hw->pcm_ops->run_buffer_out) {\n+ hw->pcm_ops->run_buffer_out(hw);\n+ }\n+ continue;\n+ }\n+\n+ prev_rpos = hw->mix_buf.pos;\n+ played = audio_pcm_hw_run_out(hw, live);\n+ replay_audio_out(&played);\n+ if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {\n+ dolog(\"hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\\n\",\n+ hw->mix_buf.pos, hw->mix_buf.size, played);\n+ hw->mix_buf.pos = 0;\n+ }\n+\n+#ifdef DEBUG_OUT\n+ dolog(\"played=%zu\\n\", played);\n+#endif\n+\n+ if (played) {\n+ audio_capture_mix_and_clear (hw, prev_rpos, played);\n+ }\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (!sw->active && sw->empty) {\n+ continue;\n+ }\n+\n+ if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {\n+ dolog(\"played=%zu sw->total_hw_samples_mixed=%zu\\n\",\n+ played, sw->total_hw_samples_mixed);\n+ played = sw->total_hw_samples_mixed;\n+ }\n+\n+ sw->total_hw_samples_mixed -= played;\n+\n+ if (!sw->total_hw_samples_mixed) {\n+ sw->empty = true;\n+ }\n+ }\n+ }\n+}\n+\n+static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)\n+{\n+ size_t conv = 0;\n+\n+ if (hw->pcm_ops->run_buffer_in) {\n+ hw->pcm_ops->run_buffer_in(hw);\n+ }\n+\n+ while (samples) {\n+ size_t proc;\n+ size_t size = samples * hw->info.bytes_per_frame;\n+ void *buf = hw->pcm_ops->get_buffer_in(hw, &size);\n+\n+ assert(size % hw->info.bytes_per_frame == 0);\n+ if (size == 0) {\n+ break;\n+ }\n+\n+ proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);\n+\n+ samples -= proc;\n+ conv += proc;\n+ hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);\n+ }\n+\n+ return conv;\n+}\n+\n+static void audio_run_in(AudioMixengBackend *s)\n+{\n+ HWVoiceIn *hw = NULL;\n+\n+ if (!audio_get_pdo_in(s->dev)->mixing_engine) {\n+ while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {\n+ /* there is exactly 1 sw for each hw with no mixeng */\n+ SWVoiceIn *sw = hw->sw_head.lh_first;\n+ if (sw->active) {\n+ sw->callback.fn(sw->callback.opaque, INT_MAX);\n+ }\n+ }\n+ return;\n+ }\n+\n+ while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {\n+ SWVoiceIn *sw;\n+ size_t captured = 0, min;\n+ int pos;\n+\n+ if (replay_mode != REPLAY_MODE_PLAY) {\n+ captured = audio_pcm_hw_run_in(\n+ hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));\n+ }\n+\n+ replay_audio_in_start(&captured);\n+ assert(captured <= hw->conv_buf.size);\n+ if (replay_mode == REPLAY_MODE_PLAY) {\n+ hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;\n+ }\n+ for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;\n+ pos != hw->conv_buf.pos;\n+ pos = (pos + 1) % hw->conv_buf.size) {\n+ uint64_t left, right;\n+\n+ if (replay_mode == REPLAY_MODE_RECORD) {\n+ audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);\n+ }\n+ replay_audio_in_sample_lr(&left, &right);\n+ if (replay_mode == REPLAY_MODE_PLAY) {\n+ audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);\n+ }\n+ }\n+ replay_audio_in_finish();\n+\n+ min = audio_pcm_hw_find_min_in (hw);\n+ hw->total_samples_captured += captured - min;\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ sw->total_hw_samples_acquired -= min;\n+\n+ if (sw->active) {\n+ size_t sw_avail = audio_get_avail(sw);\n+ size_t avail;\n+\n+ avail = st_rate_frames_out(sw->rate, sw_avail);\n+ if (avail > 0) {\n+ avail = MIN(avail, sw->resample_buf.size);\n+ sw->callback.fn(sw->callback.opaque,\n+ avail * sw->info.bytes_per_frame);\n+ }\n+ }\n+ }\n+ }\n+}\n+\n+static void audio_run_capture(AudioMixengBackend *s)\n+{\n+ CaptureVoiceOut *cap;\n+\n+ for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n+ size_t live, rpos, captured;\n+ HWVoiceOut *hw = &cap->hw;\n+ SWVoiceOut *sw;\n+\n+ captured = live = audio_pcm_hw_get_live_out (hw, NULL);\n+ rpos = hw->mix_buf.pos;\n+ while (live) {\n+ size_t left = hw->mix_buf.size - rpos;\n+ size_t to_capture = MIN(live, left);\n+ struct st_sample *src;\n+ struct capture_callback *cb;\n+\n+ src = hw->mix_buf.buffer + rpos;\n+ hw->clip (cap->buf, src, to_capture);\n+ mixeng_clear (src, to_capture);\n+\n+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n+ cb->ops.capture (cb->opaque, cap->buf,\n+ to_capture * hw->info.bytes_per_frame);\n+ }\n+ rpos = (rpos + to_capture) % hw->mix_buf.size;\n+ live -= to_capture;\n+ }\n+ hw->mix_buf.pos = rpos;\n+\n+ for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n+ if (!sw->active && sw->empty) {\n+ continue;\n+ }\n+\n+ if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {\n+ dolog(\"captured=%zu sw->total_hw_samples_mixed=%zu\\n\",\n+ captured, sw->total_hw_samples_mixed);\n+ captured = sw->total_hw_samples_mixed;\n+ }\n+\n+ sw->total_hw_samples_mixed -= captured;\n+ sw->empty = sw->total_hw_samples_mixed == 0;\n+ }\n+ }\n+}\n+\n+void audio_run(AudioMixengBackend *s, const char *msg)\n+{\n+ audio_run_out(s);\n+ audio_run_in(s);\n+ audio_run_capture(s);\n+\n+#ifdef DEBUG_POLL\n+ {\n+ static double prevtime;\n+ double currtime;\n+ struct timeval tv;\n+\n+ if (gettimeofday (&tv, NULL)) {\n+ perror (\"audio_run: gettimeofday\");\n+ return;\n+ }\n+\n+ currtime = tv.tv_sec + tv.tv_usec * 1e-6;\n+ dolog (\"Elapsed since last %s: %f\\n\", msg, currtime - prevtime);\n+ prevtime = currtime;\n+ }\n+#endif\n+}\n+\n+void audio_generic_run_buffer_in(HWVoiceIn *hw)\n+{\n+ if (unlikely(!hw->buf_emul)) {\n+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;\n+ hw->buf_emul = g_malloc(hw->size_emul);\n+ hw->pos_emul = hw->pending_emul = 0;\n+ }\n+\n+ while (hw->pending_emul < hw->size_emul) {\n+ size_t read_len = MIN(hw->size_emul - hw->pos_emul,\n+ hw->size_emul - hw->pending_emul);\n+ size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,\n+ read_len);\n+ hw->pending_emul += read;\n+ hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;\n+ if (read < read_len) {\n+ break;\n+ }\n+ }\n+}\n+\n+void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)\n+{\n+ size_t start;\n+\n+ start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);\n+ assert(start < hw->size_emul);\n+\n+ *size = MIN(*size, hw->pending_emul);\n+ *size = MIN(*size, hw->size_emul - start);\n+ return hw->buf_emul + start;\n+}\n+\n+void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)\n+{\n+ assert(size <= hw->pending_emul);\n+ hw->pending_emul -= size;\n+}\n+\n+size_t audio_generic_buffer_get_free(HWVoiceOut *hw)\n+{\n+ if (hw->buf_emul) {\n+ return hw->size_emul - hw->pending_emul;\n+ } else {\n+ return hw->samples * hw->info.bytes_per_frame;\n+ }\n+}\n+\n+void audio_generic_run_buffer_out(HWVoiceOut *hw)\n+{\n+ while (hw->pending_emul) {\n+ size_t write_len, written, start;\n+\n+ start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);\n+ assert(start < hw->size_emul);\n+\n+ write_len = MIN(hw->pending_emul, hw->size_emul - start);\n+\n+ written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);\n+ hw->pending_emul -= written;\n+\n+ if (written < write_len) {\n+ break;\n+ }\n+ }\n+}\n+\n+void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)\n+{\n+ if (unlikely(!hw->buf_emul)) {\n+ hw->size_emul = hw->samples * hw->info.bytes_per_frame;\n+ hw->buf_emul = g_malloc(hw->size_emul);\n+ hw->pos_emul = hw->pending_emul = 0;\n+ }\n+\n+ *size = MIN(hw->size_emul - hw->pending_emul,\n+ hw->size_emul - hw->pos_emul);\n+ return hw->buf_emul + hw->pos_emul;\n+}\n+\n+size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)\n+{\n+ assert(buf == hw->buf_emul + hw->pos_emul &&\n+ size + hw->pending_emul <= hw->size_emul);\n+\n+ hw->pending_emul += size;\n+ hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;\n+\n+ return size;\n+}\n+\n+size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)\n+{\n+ size_t total = 0;\n+\n+ if (hw->pcm_ops->buffer_get_free) {\n+ size_t free = hw->pcm_ops->buffer_get_free(hw);\n+\n+ size = MIN(size, free);\n+ }\n+\n+ while (total < size) {\n+ size_t dst_size = size - total;\n+ size_t copy_size, proc;\n+ void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);\n+\n+ if (dst_size == 0) {\n+ break;\n+ }\n+\n+ copy_size = MIN(size - total, dst_size);\n+ if (dst) {\n+ memcpy(dst, (char *)buf + total, copy_size);\n+ }\n+ proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);\n+ total += proc;\n+\n+ if (proc == 0 || proc < copy_size) {\n+ break;\n+ }\n+ }\n+\n+ return total;\n+}\n+\n+size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)\n+{\n+ size_t total = 0;\n+\n+ if (hw->pcm_ops->run_buffer_in) {\n+ hw->pcm_ops->run_buffer_in(hw);\n+ }\n+\n+ while (total < size) {\n+ size_t src_size = size - total;\n+ void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);\n+\n+ if (src_size == 0) {\n+ break;\n+ }\n+\n+ memcpy((char *)buf + total, src, src_size);\n+ hw->pcm_ops->put_buffer_in(hw, src, src_size);\n+ total += src_size;\n+ }\n+\n+ return total;\n+}\n+\n+static bool audio_mixeng_backend_realize(AudioBackend *abe,\n+ Audiodev *dev, Error **errp)\n+{\n+ AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);\n+ audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;\n+\n+ be->dev = dev;\n+ be->drv_opaque = drv->init(be->dev, errp);\n+ if (!be->drv_opaque) {\n+ return false;\n+ }\n+\n+ if (!drv->pcm_ops->get_buffer_in) {\n+ drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;\n+ drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;\n+ }\n+ if (!drv->pcm_ops->get_buffer_out) {\n+ drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;\n+ drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;\n+ }\n+\n+ audio_init_nb_voices_out(be, drv, 1);\n+ audio_init_nb_voices_in(be, drv, 0);\n+ be->drv = drv;\n+\n+ if (be->dev->timer_period <= 0) {\n+ be->period_ticks = 1;\n+ } else {\n+ be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;\n+ }\n+\n+ return true;\n+}\n+\n+static void audio_vm_change_state_handler (void *opaque, bool running,\n+ RunState state)\n+{\n+ AudioMixengBackend *s = opaque;\n+ HWVoiceOut *hwo = NULL;\n+ HWVoiceIn *hwi = NULL;\n+\n+ while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {\n+ if (hwo->pcm_ops->enable_out) {\n+ hwo->pcm_ops->enable_out(hwo, running);\n+ }\n+ }\n+\n+ while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {\n+ if (hwi->pcm_ops->enable_in) {\n+ hwi->pcm_ops->enable_in(hwi, running);\n+ }\n+ }\n+ audio_reset_timer (s);\n+}\n+\n+static const VMStateDescription vmstate_audio;\n+\n+static const char *audio_mixeng_backend_get_id(AudioBackend *be)\n+{\n+ return AUDIO_MIXENG_BACKEND(be)->dev->id;\n+}\n+\n+static CaptureVoiceOut *audio_mixeng_backend_add_capture(\n+ AudioBackend *be,\n+ struct audsettings *as,\n+ struct audio_capture_ops *ops,\n+ void *cb_opaque);\n+\n+static void audio_mixeng_backend_del_capture(\n+ AudioBackend *be,\n+ CaptureVoiceOut *cap,\n+ void *cb_opaque);\n+\n+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,\n+ Volume *vol);\n+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,\n+ Volume *vol);\n+\n+static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)\n+{\n+ AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);\n+\n+ be->realize = audio_mixeng_backend_realize;\n+ be->get_id = audio_mixeng_backend_get_id;\n+ be->open_in = audio_mixeng_backend_open_in;\n+ be->open_out = audio_mixeng_backend_open_out;\n+ be->close_in = audio_mixeng_backend_close_in;\n+ be->close_out = audio_mixeng_backend_close_out;\n+ be->is_active_out = audio_mixeng_backend_is_active_out;\n+ be->is_active_in = audio_mixeng_backend_is_active_in;\n+ be->set_active_out = audio_mixeng_backend_set_active_out;\n+ be->set_active_in = audio_mixeng_backend_set_active_in;\n+ be->set_volume_out = audio_mixeng_backend_set_volume_out;\n+ be->set_volume_in = audio_mixeng_backend_set_volume_in;\n+ be->read = audio_mixeng_backend_read;\n+ be->write = audio_mixeng_backend_write;\n+ be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;\n+ be->add_capture = audio_mixeng_backend_add_capture;\n+ be->del_capture = audio_mixeng_backend_del_capture;\n+}\n+\n+static void audio_mixeng_backend_init(Object *obj)\n+{\n+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);\n+\n+ QLIST_INIT(&s->hw_head_out);\n+ QLIST_INIT(&s->hw_head_in);\n+ QLIST_INIT(&s->cap_head);\n+ s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);\n+\n+ s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);\n+ assert(s->vmse != NULL);\n+\n+ vmstate_register_any(NULL, &vmstate_audio, s);\n+}\n+\n+static void audio_mixeng_backend_finalize(Object *obj)\n+{\n+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);\n+ HWVoiceOut *hwo, *hwon;\n+ HWVoiceIn *hwi, *hwin;\n+\n+ QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {\n+ SWVoiceCap *sc;\n+\n+ if (hwo->enabled && hwo->pcm_ops->enable_out) {\n+ hwo->pcm_ops->enable_out(hwo, false);\n+ }\n+ hwo->pcm_ops->fini_out (hwo);\n+\n+ for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n+ CaptureVoiceOut *cap = sc->cap;\n+ struct capture_callback *cb;\n+\n+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n+ cb->ops.destroy (cb->opaque);\n+ }\n+ }\n+ QLIST_REMOVE(hwo, entries);\n+ }\n+\n+ QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {\n+ if (hwi->enabled && hwi->pcm_ops->enable_in) {\n+ hwi->pcm_ops->enable_in(hwi, false);\n+ }\n+ hwi->pcm_ops->fini_in (hwi);\n+ QLIST_REMOVE(hwi, entries);\n+ }\n+\n+ if (s->drv) {\n+ s->drv->fini (s->drv_opaque);\n+ s->drv = NULL;\n+ }\n+\n+ if (s->dev) {\n+ qapi_free_Audiodev(s->dev);\n+ s->dev = NULL;\n+ }\n+\n+ if (s->ts) {\n+ timer_free(s->ts);\n+ s->ts = NULL;\n+ }\n+\n+ if (s->vmse) {\n+ qemu_del_vm_change_state_handler(s->vmse);\n+ s->vmse = NULL;\n+ }\n+\n+ vmstate_unregister(NULL, &vmstate_audio, s);\n+}\n+\n+static bool vmstate_audio_needed(void *opaque)\n+{\n+ /*\n+ * Never needed, this vmstate only exists in case\n+ * an old qemu sends it to us.\n+ */\n+ return false;\n+}\n+\n+static const VMStateDescription vmstate_audio = {\n+ .name = \"audio\",\n+ .version_id = 1,\n+ .minimum_version_id = 1,\n+ .needed = vmstate_audio_needed,\n+ .fields = (const VMStateField[]) {\n+ VMSTATE_END_OF_LIST()\n+ }\n+};\n+\n+static struct audio_pcm_ops capture_pcm_ops;\n+\n+static CaptureVoiceOut *audio_mixeng_backend_add_capture(\n+ AudioBackend *be,\n+ struct audsettings *as,\n+ struct audio_capture_ops *ops,\n+ void *cb_opaque)\n+{\n+ AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);\n+ CaptureVoiceOut *cap;\n+ struct capture_callback *cb;\n+\n+ if (!s) {\n+ error_report(\"Capturing without setting an audiodev is not supported\");\n+ abort();\n+ }\n+\n+ if (!audio_get_pdo_out(s->dev)->mixing_engine) {\n+ dolog(\"Can't capture with mixeng disabled\\n\");\n+ return NULL;\n+ }\n+\n+ if (audio_validate_settings (as)) {\n+ dolog (\"Invalid settings were passed when trying to add capture\\n\");\n+ audio_print_settings (as);\n+ return NULL;\n+ }\n+\n+ cb = g_malloc0(sizeof(*cb));\n+ cb->ops = *ops;\n+ cb->opaque = cb_opaque;\n+\n+ cap = audio_pcm_capture_find_specific(s, as);\n+ if (cap) {\n+ QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);\n+ } else {\n+ HWVoiceOut *hw;\n+\n+ cap = g_malloc0(sizeof(*cap));\n+\n+ hw = &cap->hw;\n+ hw->s = s;\n+ hw->pcm_ops = &capture_pcm_ops;\n+ QLIST_INIT (&hw->sw_head);\n+ QLIST_INIT (&cap->cb_head);\n+\n+ /* XXX find a more elegant way */\n+ hw->samples = 4096 * 4;\n+ audio_pcm_hw_alloc_resources_out(hw);\n+\n+ audio_pcm_init_info (&hw->info, as);\n+\n+ cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);\n+\n+ if (hw->info.is_float) {\n+ hw->clip = mixeng_clip_float[hw->info.nchannels == 2]\n+ [hw->info.swap_endianness];\n+ } else {\n+ hw->clip = mixeng_clip\n+ [hw->info.nchannels == 2]\n+ [hw->info.is_signed]\n+ [hw->info.swap_endianness]\n+ [audio_bits_to_index(hw->info.bits)];\n+ }\n+\n+ QLIST_INSERT_HEAD (&s->cap_head, cap, entries);\n+ QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);\n+\n+ QLIST_FOREACH(hw, &s->hw_head_out, entries) {\n+ audio_attach_capture (hw);\n+ }\n+ }\n+\n+ return cap;\n+}\n+\n+static void audio_mixeng_backend_del_capture(\n+ AudioBackend *be,\n+ CaptureVoiceOut *cap,\n+ void *cb_opaque)\n+{\n+ struct capture_callback *cb;\n+\n+ for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n+ if (cb->opaque == cb_opaque) {\n+ cb->ops.destroy (cb_opaque);\n+ QLIST_REMOVE (cb, entries);\n+ g_free (cb);\n+\n+ if (!cap->cb_head.lh_first) {\n+ SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;\n+\n+ while (sw) {\n+ SWVoiceCap *sc = (SWVoiceCap *) sw;\n+#ifdef DEBUG_CAPTURE\n+ dolog (\"freeing %s\\n\", sw->name);\n+#endif\n+\n+ sw1 = sw->entries.le_next;\n+ if (sw->rate) {\n+ st_rate_stop (sw->rate);\n+ sw->rate = NULL;\n+ }\n+ QLIST_REMOVE (sw, entries);\n+ QLIST_REMOVE (sc, entries);\n+ g_free (sc);\n+ sw = sw1;\n+ }\n+ QLIST_REMOVE (cap, entries);\n+ g_free(cap->hw.mix_buf.buffer);\n+ g_free (cap->buf);\n+ g_free (cap);\n+ }\n+ return;\n+ }\n+ }\n+}\n+\n+static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,\n+ Volume *vol)\n+{\n+ if (sw) {\n+ HWVoiceOut *hw = sw->hw;\n+\n+ sw->vol.mute = vol->mute;\n+ sw->vol.l = nominal_volume.l * vol->vol[0] / 255;\n+ sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /\n+ 255;\n+\n+ if (hw->pcm_ops->volume_out) {\n+ hw->pcm_ops->volume_out(hw, vol);\n+ }\n+ }\n+}\n+\n+static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,\n+ Volume *vol)\n+{\n+ if (sw) {\n+ HWVoiceIn *hw = sw->hw;\n+\n+ sw->vol.mute = vol->mute;\n+ sw->vol.l = nominal_volume.l * vol->vol[0] / 255;\n+ sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /\n+ 255;\n+\n+ if (hw->pcm_ops->volume_in) {\n+ hw->pcm_ops->volume_in(hw, vol);\n+ }\n+ }\n+}\n+\n+audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)\n+{\n+ return (audsettings) {\n+ .freq = pdo->frequency,\n+ .nchannels = pdo->channels,\n+ .fmt = pdo->format,\n+ .endianness = HOST_BIG_ENDIAN,\n+ };\n+}\n+\n+int audioformat_bytes_per_sample(AudioFormat fmt)\n+{\n+ switch (fmt) {\n+ case AUDIO_FORMAT_U8:\n+ case AUDIO_FORMAT_S8:\n+ return 1;\n+\n+ case AUDIO_FORMAT_U16:\n+ case AUDIO_FORMAT_S16:\n+ return 2;\n+\n+ case AUDIO_FORMAT_U32:\n+ case AUDIO_FORMAT_S32:\n+ case AUDIO_FORMAT_F32:\n+ return 4;\n+\n+ case AUDIO_FORMAT__MAX:\n+ ;\n+ }\n+ abort();\n+}\n+\n+\n+/* frames = freq * usec / 1e6 */\n+int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,\n+ audsettings *as, int def_usecs)\n+{\n+ uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;\n+ return (as->freq * usecs + 500000) / 1000000;\n+}\n+\n+/* samples = channels * frames = channels * freq * usec / 1e6 */\n+int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,\n+ audsettings *as, int def_usecs)\n+{\n+ return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);\n+}\n+\n+/*\n+ * bytes = bytes_per_sample * samples =\n+ * bytes_per_sample * channels * freq * usec / 1e6\n+ */\n+int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,\n+ audsettings *as, int def_usecs)\n+{\n+ return audio_buffer_samples(pdo, as, def_usecs) *\n+ audioformat_bytes_per_sample(as->fmt);\n+}\n+\n+void audio_rate_start(RateCtl *rate)\n+{\n+ memset(rate, 0, sizeof(RateCtl));\n+ rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+}\n+\n+size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)\n+{\n+ int64_t now;\n+ int64_t ticks;\n+ int64_t bytes;\n+ int64_t frames;\n+\n+ now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n+ ticks = now - rate->start_ticks;\n+ bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);\n+ frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;\n+ rate->peeked_frames = frames;\n+\n+ return frames < 0 ? 0 : frames * info->bytes_per_frame;\n+}\n+\n+void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)\n+{\n+ if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {\n+ AUD_log(NULL, \"Resetting rate control (%\" PRId64 \" frames)\\n\",\n+ rate->peeked_frames);\n+ audio_rate_start(rate);\n+ }\n+\n+ rate->bytes_sent += bytes_used;\n+}\n+\n+size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,\n+ size_t bytes_avail)\n+{\n+ size_t bytes;\n+\n+ bytes = audio_rate_peek_bytes(rate, info);\n+ bytes = MIN(bytes, bytes_avail);\n+ audio_rate_add_bytes(rate, bytes);\n+\n+ return bytes;\n+}\n+\n+static const TypeInfo audio_types[] = {\n+ {\n+ .name = TYPE_AUDIO_MIXENG_BACKEND,\n+ .parent = TYPE_AUDIO_BACKEND,\n+ .instance_size = sizeof(AudioMixengBackend),\n+ .instance_init = audio_mixeng_backend_init,\n+ .instance_finalize = audio_mixeng_backend_finalize,\n+ .class_size = sizeof(AudioMixengBackendClass),\n+ .class_init = audio_mixeng_backend_class_init,\n+ },\n+};\n+\n+DEFINE_TYPES(audio_types)\ndiff --git a/audio/audio.c b/audio/audio.c\nindex 22e55ac9758..6f10ee470ee 100644\n--- a/audio/audio.c\n+++ b/audio/audio.c\n@@ -1,1751 +1,55 @@\n-/*\n- * QEMU Audio subsystem\n- *\n- * Copyright (c) 2003-2005 Vassili Karpov (malc)\n- *\n- * Permission is hereby granted, free of charge, to any person obtaining a copy\n- * of this software and associated documentation files (the \"Software\"), to deal\n- * in the Software without restriction, including without limitation the rights\n- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n- * copies of the Software, and to permit persons to whom the Software is\n- * furnished to do so, subject to the following conditions:\n- *\n- * The above copyright notice and this permission notice shall be included in\n- * all copies or substantial portions of the Software.\n- *\n- * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL\n- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n- * THE SOFTWARE.\n- */\n-\n-#include \"qemu/osdep.h\"\n-#include \"qemu/audio.h\"\n-#include \"migration/vmstate.h\"\n-#include \"qemu/timer.h\"\n-#include \"qapi/error.h\"\n-#include \"qapi/clone-visitor.h\"\n-#include \"qapi/qobject-input-visitor.h\"\n-#include \"qapi/qapi-visit-audio.h\"\n-#include \"qapi/qapi-commands-audio.h\"\n-#include \"qobject/qdict.h\"\n-#include \"qemu/bswap.h\"\n-#include \"qemu/error-report.h\"\n-#include \"qemu/log.h\"\n-#include \"qemu/module.h\"\n-#include \"qemu/help_option.h\"\n-#include \"qom/object.h\"\n-#include \"system/system.h\"\n-#include \"system/replay.h\"\n-#include \"system/runstate.h\"\n-#include \"trace.h\"\n-\n-#define AUDIO_CAP \"audio\"\n-#include \"audio_int.h\"\n-\n-/* #define DEBUG_LIVE */\n-/* #define DEBUG_OUT */\n-/* #define DEBUG_CAPTURE */\n-/* #define DEBUG_POLL */\n-\n-#define SW_NAME(sw) (sw)->name ? (sw)->name : \"unknown\"\n-\n-\n-/* Order of CONFIG_AUDIO_DRIVERS is import.\n- The 1st one is the one used by default, that is the reason\n- that we generate the list.\n-*/\n-const char *audio_prio_list[] = {\n-#ifdef CONFIG_GIO\n- \"dbus\",\n-#endif\n- \"spice\",\n- CONFIG_AUDIO_DRIVERS\n- \"none\",\n- NULL\n-};\n-\n-typedef struct AudiodevListEntry {\n- Audiodev *dev;\n- QSIMPLEQ_ENTRY(AudiodevListEntry) next;\n-} AudiodevListEntry;\n-\n-typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;\n-\n-static AudiodevListHead audiodevs =\n- QSIMPLEQ_HEAD_INITIALIZER(audiodevs);\n-static AudiodevListHead default_audiodevs =\n- QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);\n-\n-static AudioBackendClass *audio_be_class_by_name(const char *name)\n-{\n- g_autofree char *tname = g_strconcat(\"audio-\", name, NULL);\n- ObjectClass *oc = module_object_class_by_name(tname);\n-\n- if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {\n- return NULL;\n- }\n-\n- return AUDIO_BACKEND_CLASS(oc);\n-}\n-\n-static AudioBackend *default_audio_be;\n-\n-const struct mixeng_volume nominal_volume = {\n- .mute = 0,\n-#ifdef FLOAT_MIXENG\n- .r = 1.0,\n- .l = 1.0,\n-#else\n- .r = 1ULL << 32,\n- .l = 1ULL << 32,\n-#endif\n-};\n-\n-int audio_bug (const char *funcname, int cond)\n-{\n- if (cond) {\n- static int shown;\n-\n- AUD_log (NULL, \"A bug was just triggered in %s\\n\", funcname);\n- if (!shown) {\n- shown = 1;\n- AUD_log (NULL, \"Save all your work and restart without audio\\n\");\n- AUD_log (NULL, \"I am sorry\\n\");\n- }\n- AUD_log (NULL, \"Context:\\n\");\n- }\n-\n- return cond;\n-}\n-\n-static inline int audio_bits_to_index (int bits)\n-{\n- switch (bits) {\n- case 8:\n- return 0;\n-\n- case 16:\n- return 1;\n-\n- case 32:\n- return 2;\n-\n- default:\n- audio_bug (\"bits_to_index\", 1);\n- AUD_log (NULL, \"invalid bits %d\\n\", bits);\n- return 0;\n- }\n-}\n-\n-void AUD_vlog (const char *cap, const char *fmt, va_list ap)\n-{\n- if (cap) {\n- fprintf(stderr, \"%s: \", cap);\n- }\n-\n- vfprintf(stderr, fmt, ap);\n-}\n-\n-void AUD_log (const char *cap, const char *fmt, ...)\n-{\n- va_list ap;\n-\n- va_start (ap, fmt);\n- AUD_vlog (cap, fmt, ap);\n- va_end (ap);\n-}\n-\n-static void audio_print_settings (const struct audsettings *as)\n-{\n- dolog (\"frequency=%d nchannels=%d fmt=\", as->freq, as->nchannels);\n-\n- switch (as->fmt) {\n- case AUDIO_FORMAT_S8:\n- AUD_log (NULL, \"S8\");\n- break;\n- case AUDIO_FORMAT_U8:\n- AUD_log (NULL, \"U8\");\n- break;\n- case AUDIO_FORMAT_S16:\n- AUD_log (NULL, \"S16\");\n- break;\n- case AUDIO_FORMAT_U16:\n- AUD_log (NULL, \"U16\");\n- break;\n- case AUDIO_FORMAT_S32:\n- AUD_log (NULL, \"S32\");\n- break;\n- case AUDIO_FORMAT_U32:\n- AUD_log (NULL, \"U32\");\n- break;\n- case AUDIO_FORMAT_F32:\n- AUD_log (NULL, \"F32\");\n- break;\n- default:\n- AUD_log (NULL, \"invalid(%d)\", as->fmt);\n- break;\n- }\n-\n- AUD_log (NULL, \" endianness=\");\n- switch (as->endianness) {\n- case 0:\n- AUD_log (NULL, \"little\");\n- break;\n- case 1:\n- AUD_log (NULL, \"big\");\n- break;\n- default:\n- AUD_log (NULL, \"invalid\");\n- break;\n- }\n- AUD_log (NULL, \"\\n\");\n-}\n-\n-static int audio_validate_settings (const struct audsettings *as)\n-{\n- int invalid;\n-\n- invalid = as->nchannels < 1;\n- invalid |= as->endianness != 0 && as->endianness != 1;\n-\n- switch (as->fmt) {\n- case AUDIO_FORMAT_S8:\n- case AUDIO_FORMAT_U8:\n- case AUDIO_FORMAT_S16:\n- case AUDIO_FORMAT_U16:\n- case AUDIO_FORMAT_S32:\n- case AUDIO_FORMAT_U32:\n- case AUDIO_FORMAT_F32:\n- break;\n- default:\n- invalid = 1;\n- break;\n- }\n-\n- invalid |= as->freq <= 0;\n- return invalid ? -1 : 0;\n-}\n-\n-static int audio_pcm_info_eq (struct audio_pcm_info *info, const struct audsettings *as)\n-{\n- int bits = 8;\n- bool is_signed = false, is_float = false;\n-\n- switch (as->fmt) {\n- case AUDIO_FORMAT_S8:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U8:\n- break;\n-\n- case AUDIO_FORMAT_S16:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U16:\n- bits = 16;\n- break;\n-\n- case AUDIO_FORMAT_F32:\n- is_float = true;\n- /* fall through */\n- case AUDIO_FORMAT_S32:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U32:\n- bits = 32;\n- break;\n-\n- default:\n- abort();\n- }\n- return info->freq == as->freq\n- && info->nchannels == as->nchannels\n- && info->is_signed == is_signed\n- && info->is_float == is_float\n- && info->bits == bits\n- && info->swap_endianness == (as->endianness != HOST_BIG_ENDIAN);\n-}\n-\n-void audio_pcm_init_info (struct audio_pcm_info *info, const struct audsettings *as)\n-{\n- int bits = 8, mul;\n- bool is_signed = false, is_float = false;\n-\n- switch (as->fmt) {\n- case AUDIO_FORMAT_S8:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U8:\n- mul = 1;\n- break;\n-\n- case AUDIO_FORMAT_S16:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U16:\n- bits = 16;\n- mul = 2;\n- break;\n-\n- case AUDIO_FORMAT_F32:\n- is_float = true;\n- /* fall through */\n- case AUDIO_FORMAT_S32:\n- is_signed = true;\n- /* fall through */\n- case AUDIO_FORMAT_U32:\n- bits = 32;\n- mul = 4;\n- break;\n-\n- default:\n- abort();\n- }\n-\n- info->freq = as->freq;\n- info->bits = bits;\n- info->is_signed = is_signed;\n- info->is_float = is_float;\n- info->nchannels = as->nchannels;\n- info->bytes_per_frame = as->nchannels * mul;\n- info->bytes_per_second = info->freq * info->bytes_per_frame;\n- info->swap_endianness = (as->endianness != HOST_BIG_ENDIAN);\n-}\n-\n-void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len)\n-{\n- if (!len) {\n- return;\n- }\n-\n- if (info->is_signed || info->is_float) {\n- memset(buf, 0x00, len * info->bytes_per_frame);\n- } else {\n- switch (info->bits) {\n- case 8:\n- memset(buf, 0x80, len * info->bytes_per_frame);\n- break;\n-\n- case 16:\n- {\n- int i;\n- uint16_t *p = buf;\n- short s = INT16_MAX;\n-\n- if (info->swap_endianness) {\n- s = bswap16 (s);\n- }\n-\n- for (i = 0; i < len * info->nchannels; i++) {\n- p[i] = s;\n- }\n- }\n- break;\n-\n- case 32:\n- {\n- int i;\n- uint32_t *p = buf;\n- int32_t s = INT32_MAX;\n-\n- if (info->swap_endianness) {\n- s = bswap32 (s);\n- }\n-\n- for (i = 0; i < len * info->nchannels; i++) {\n- p[i] = s;\n- }\n- }\n- break;\n-\n- default:\n- AUD_log (NULL, \"audio_pcm_info_clear_buf: invalid bits %d\\n\",\n- info->bits);\n- break;\n- }\n- }\n-}\n-\n-/*\n- * Capture\n- */\n-static CaptureVoiceOut *audio_pcm_capture_find_specific(AudioMixengBackend *s,\n- struct audsettings *as)\n-{\n- CaptureVoiceOut *cap;\n-\n- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n- if (audio_pcm_info_eq (&cap->hw.info, as)) {\n- return cap;\n- }\n- }\n- return NULL;\n-}\n-\n-static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd)\n-{\n- struct capture_callback *cb;\n-\n-#ifdef DEBUG_CAPTURE\n- dolog (\"notification %d sent\\n\", cmd);\n-#endif\n- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n- cb->ops.notify (cb->opaque, cmd);\n- }\n-}\n-\n-static void audio_capture_maybe_changed(CaptureVoiceOut *cap, bool enabled)\n-{\n- if (cap->hw.enabled != enabled) {\n- audcnotification_e cmd;\n- cap->hw.enabled = enabled;\n- cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE;\n- audio_notify_capture (cap, cmd);\n- }\n-}\n-\n-static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap)\n-{\n- HWVoiceOut *hw = &cap->hw;\n- SWVoiceOut *sw;\n- bool enabled = false;\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (sw->active) {\n- enabled = true;\n- break;\n- }\n- }\n- audio_capture_maybe_changed (cap, enabled);\n-}\n-\n-static void audio_detach_capture (HWVoiceOut *hw)\n-{\n- SWVoiceCap *sc = hw->cap_head.lh_first;\n-\n- while (sc) {\n- SWVoiceCap *sc1 = sc->entries.le_next;\n- SWVoiceOut *sw = &sc->sw;\n- CaptureVoiceOut *cap = sc->cap;\n- int was_active = sw->active;\n-\n- if (sw->rate) {\n- st_rate_stop (sw->rate);\n- sw->rate = NULL;\n- }\n-\n- QLIST_REMOVE (sw, entries);\n- QLIST_REMOVE (sc, entries);\n- g_free (sc);\n- if (was_active) {\n- /* We have removed soft voice from the capture:\n- this might have changed the overall status of the capture\n- since this might have been the only active voice */\n- audio_recalc_and_notify_capture (cap);\n- }\n- sc = sc1;\n- }\n-}\n-\n-static int audio_attach_capture (HWVoiceOut *hw)\n-{\n- AudioMixengBackend *s = hw->s;\n- CaptureVoiceOut *cap;\n-\n- audio_detach_capture (hw);\n- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n- SWVoiceCap *sc;\n- SWVoiceOut *sw;\n- HWVoiceOut *hw_cap = &cap->hw;\n-\n- sc = g_malloc0(sizeof(*sc));\n-\n- sc->cap = cap;\n- sw = &sc->sw;\n- sw->hw = hw_cap;\n- sw->info = hw->info;\n- sw->empty = true;\n- sw->active = hw->enabled;\n- sw->vol = nominal_volume;\n- sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq);\n- QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries);\n- QLIST_INSERT_HEAD (&hw->cap_head, sc, entries);\n-#ifdef DEBUG_CAPTURE\n- sw->name = g_strdup_printf (\"for %p %d,%d,%d\",\n- hw, sw->info.freq, sw->info.bits,\n- sw->info.nchannels);\n- dolog (\"Added %s active = %d\\n\", sw->name, sw->active);\n-#endif\n- if (sw->active) {\n- audio_capture_maybe_changed (cap, 1);\n- }\n- }\n- return 0;\n-}\n-\n-/*\n- * Hard voice (capture)\n- */\n-static size_t audio_pcm_hw_find_min_in (HWVoiceIn *hw)\n-{\n- SWVoiceIn *sw;\n- size_t m = hw->total_samples_captured;\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (sw->active) {\n- m = MIN (m, sw->total_hw_samples_acquired);\n- }\n- }\n- return m;\n-}\n-\n-static size_t audio_pcm_hw_get_live_in(HWVoiceIn *hw)\n-{\n- size_t live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw);\n- if (audio_bug(__func__, live > hw->conv_buf.size)) {\n- dolog(\"live=%zu hw->conv_buf.size=%zu\\n\", live, hw->conv_buf.size);\n- return 0;\n- }\n- return live;\n-}\n-\n-static size_t audio_pcm_hw_conv_in(HWVoiceIn *hw, void *pcm_buf, size_t samples)\n-{\n- size_t conv = 0;\n- STSampleBuffer *conv_buf = &hw->conv_buf;\n-\n- while (samples) {\n- uint8_t *src = advance(pcm_buf, conv * hw->info.bytes_per_frame);\n- size_t proc = MIN(samples, conv_buf->size - conv_buf->pos);\n-\n- hw->conv(conv_buf->buffer + conv_buf->pos, src, proc);\n- conv_buf->pos = (conv_buf->pos + proc) % conv_buf->size;\n- samples -= proc;\n- conv += proc;\n- }\n-\n- return conv;\n-}\n-\n-/*\n- * Soft voice (capture)\n- */\n-static void audio_pcm_sw_resample_in(SWVoiceIn *sw,\n- size_t frames_in_max, size_t frames_out_max,\n- size_t *total_in, size_t *total_out)\n-{\n- HWVoiceIn *hw = sw->hw;\n- struct st_sample *src, *dst;\n- size_t live, rpos, frames_in, frames_out;\n-\n- live = hw->total_samples_captured - sw->total_hw_samples_acquired;\n- rpos = audio_ring_posb(hw->conv_buf.pos, live, hw->conv_buf.size);\n-\n- /* resample conv_buf from rpos to end of buffer */\n- src = hw->conv_buf.buffer + rpos;\n- frames_in = MIN(frames_in_max, hw->conv_buf.size - rpos);\n- dst = sw->resample_buf.buffer;\n- frames_out = frames_out_max;\n- st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);\n- rpos += frames_in;\n- *total_in = frames_in;\n- *total_out = frames_out;\n-\n- /* resample conv_buf from start of buffer if there are input frames left */\n- if (frames_in_max - frames_in && rpos == hw->conv_buf.size) {\n- src = hw->conv_buf.buffer;\n- frames_in = frames_in_max - frames_in;\n- dst += frames_out;\n- frames_out = frames_out_max - frames_out;\n- st_rate_flow(sw->rate, src, dst, &frames_in, &frames_out);\n- *total_in += frames_in;\n- *total_out += frames_out;\n- }\n-}\n-\n-static size_t audio_pcm_sw_read(SWVoiceIn *sw, void *buf, size_t buf_len)\n-{\n- HWVoiceIn *hw = sw->hw;\n- size_t live, frames_out_max, total_in, total_out;\n-\n- live = hw->total_samples_captured - sw->total_hw_samples_acquired;\n- if (!live) {\n- return 0;\n- }\n- if (audio_bug(__func__, live > hw->conv_buf.size)) {\n- dolog(\"live_in=%zu hw->conv_buf.size=%zu\\n\", live, hw->conv_buf.size);\n- return 0;\n- }\n-\n- frames_out_max = MIN(buf_len / sw->info.bytes_per_frame,\n- sw->resample_buf.size);\n-\n- audio_pcm_sw_resample_in(sw, live, frames_out_max, &total_in, &total_out);\n-\n- if (!hw->pcm_ops->volume_in) {\n- mixeng_volume(sw->resample_buf.buffer, total_out, &sw->vol);\n- }\n- sw->clip(buf, sw->resample_buf.buffer, total_out);\n-\n- sw->total_hw_samples_acquired += total_in;\n- return total_out * sw->info.bytes_per_frame;\n-}\n-\n-/*\n- * Hard voice (playback)\n- */\n-static size_t audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep)\n-{\n- SWVoiceOut *sw;\n- size_t m = SIZE_MAX;\n- int nb_live = 0;\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (sw->active || !sw->empty) {\n- m = MIN (m, sw->total_hw_samples_mixed);\n- nb_live += 1;\n- }\n- }\n-\n- *nb_livep = nb_live;\n- return m;\n-}\n-\n-static size_t audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live)\n-{\n- size_t smin;\n- int nb_live1;\n-\n- smin = audio_pcm_hw_find_min_out (hw, &nb_live1);\n- if (nb_live) {\n- *nb_live = nb_live1;\n- }\n-\n- if (nb_live1) {\n- size_t live = smin;\n-\n- if (audio_bug(__func__, live > hw->mix_buf.size)) {\n- dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n- return 0;\n- }\n- return live;\n- }\n- return 0;\n-}\n-\n-static size_t audio_pcm_hw_get_free(HWVoiceOut *hw)\n-{\n- return (hw->pcm_ops->buffer_get_free ? hw->pcm_ops->buffer_get_free(hw) :\n- INT_MAX) / hw->info.bytes_per_frame;\n-}\n-\n-static void audio_pcm_hw_clip_out(HWVoiceOut *hw, void *pcm_buf, size_t len)\n-{\n- size_t clipped = 0;\n- size_t pos = hw->mix_buf.pos;\n-\n- while (len) {\n- st_sample *src = hw->mix_buf.buffer + pos;\n- uint8_t *dst = advance(pcm_buf, clipped * hw->info.bytes_per_frame);\n- size_t samples_till_end_of_buf = hw->mix_buf.size - pos;\n- size_t samples_to_clip = MIN(len, samples_till_end_of_buf);\n-\n- hw->clip(dst, src, samples_to_clip);\n-\n- pos = (pos + samples_to_clip) % hw->mix_buf.size;\n- len -= samples_to_clip;\n- clipped += samples_to_clip;\n- }\n-}\n-\n-/*\n- * Soft voice (playback)\n- */\n-static void audio_pcm_sw_resample_out(SWVoiceOut *sw,\n- size_t frames_in_max, size_t frames_out_max,\n- size_t *total_in, size_t *total_out)\n-{\n- HWVoiceOut *hw = sw->hw;\n- struct st_sample *src, *dst;\n- size_t live, wpos, frames_in, frames_out;\n-\n- live = sw->total_hw_samples_mixed;\n- wpos = (hw->mix_buf.pos + live) % hw->mix_buf.size;\n-\n- /* write to mix_buf from wpos to end of buffer */\n- src = sw->resample_buf.buffer;\n- frames_in = frames_in_max;\n- dst = hw->mix_buf.buffer + wpos;\n- frames_out = MIN(frames_out_max, hw->mix_buf.size - wpos);\n- st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);\n- wpos += frames_out;\n- *total_in = frames_in;\n- *total_out = frames_out;\n-\n- /* write to mix_buf from start of buffer if there are input frames left */\n- if (frames_in_max - frames_in > 0 && wpos == hw->mix_buf.size) {\n- src += frames_in;\n- frames_in = frames_in_max - frames_in;\n- dst = hw->mix_buf.buffer;\n- frames_out = frames_out_max - frames_out;\n- st_rate_flow_mix(sw->rate, src, dst, &frames_in, &frames_out);\n- *total_in += frames_in;\n- *total_out += frames_out;\n- }\n-}\n-\n-static size_t audio_pcm_sw_write(SWVoiceOut *sw, void *buf, size_t buf_len)\n-{\n- HWVoiceOut *hw = sw->hw;\n- size_t live, dead, hw_free, sw_max, fe_max;\n- size_t frames_in_max, frames_out_max, total_in, total_out;\n-\n- live = sw->total_hw_samples_mixed;\n- if (audio_bug(__func__, live > hw->mix_buf.size)) {\n- dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n- return 0;\n- }\n-\n- if (live == hw->mix_buf.size) {\n-#ifdef DEBUG_OUT\n- dolog (\"%s is full %zu\\n\", sw->name, live);\n-#endif\n- return 0;\n- }\n-\n- dead = hw->mix_buf.size - live;\n- hw_free = audio_pcm_hw_get_free(hw);\n- hw_free = hw_free > live ? hw_free - live : 0;\n- frames_out_max = MIN(dead, hw_free);\n- sw_max = st_rate_frames_in(sw->rate, frames_out_max);\n- fe_max = MIN(buf_len / sw->info.bytes_per_frame + sw->resample_buf.pos,\n- sw->resample_buf.size);\n- frames_in_max = MIN(sw_max, fe_max);\n-\n- if (!frames_in_max) {\n- return 0;\n- }\n-\n- if (frames_in_max > sw->resample_buf.pos) {\n- sw->conv(sw->resample_buf.buffer + sw->resample_buf.pos,\n- buf, frames_in_max - sw->resample_buf.pos);\n- if (!sw->hw->pcm_ops->volume_out) {\n- mixeng_volume(sw->resample_buf.buffer + sw->resample_buf.pos,\n- frames_in_max - sw->resample_buf.pos, &sw->vol);\n- }\n- }\n-\n- audio_pcm_sw_resample_out(sw, frames_in_max, frames_out_max,\n- &total_in, &total_out);\n-\n- sw->total_hw_samples_mixed += total_out;\n- sw->empty = sw->total_hw_samples_mixed == 0;\n-\n- /*\n- * Upsampling may leave one audio frame in the resample buffer. Decrement\n- * total_in by one if there was a leftover frame from the previous resample\n- * pass in the resample buffer. Increment total_in by one if the current\n- * resample pass left one frame in the resample buffer.\n- */\n- if (frames_in_max - total_in == 1) {\n- /* copy one leftover audio frame to the beginning of the buffer */\n- *sw->resample_buf.buffer = *(sw->resample_buf.buffer + total_in);\n- total_in += 1 - sw->resample_buf.pos;\n- sw->resample_buf.pos = 1;\n- } else if (total_in >= sw->resample_buf.pos) {\n- total_in -= sw->resample_buf.pos;\n- sw->resample_buf.pos = 0;\n- }\n-\n-#ifdef DEBUG_OUT\n- dolog (\n- \"%s: write size %zu written %zu total mixed %zu\\n\",\n- SW_NAME(sw),\n- buf_len / sw->info.bytes_per_frame,\n- total_in,\n- sw->total_hw_samples_mixed\n- );\n-#endif\n-\n- return total_in * sw->info.bytes_per_frame;\n-}\n-\n-#ifdef DEBUG_AUDIO\n-static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info)\n-{\n- dolog(\"%s: bits %d, sign %d, float %d, freq %d, nchan %d\\n\",\n- cap, info->bits, info->is_signed, info->is_float, info->freq,\n- info->nchannels);\n-}\n-#endif\n-\n-#define DAC\n-#include \"audio_template.h\"\n-#undef DAC\n-#include \"audio_template.h\"\n-\n-/*\n- * Timer\n- */\n-static int audio_is_timer_needed(AudioMixengBackend *s)\n-{\n- HWVoiceIn *hwi = NULL;\n- HWVoiceOut *hwo = NULL;\n-\n- while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {\n- if (!hwo->poll_mode) {\n- return 1;\n- }\n- }\n- while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {\n- if (!hwi->poll_mode) {\n- return 1;\n- }\n- }\n- return 0;\n-}\n-\n-static void audio_reset_timer(AudioMixengBackend *s)\n-{\n- if (audio_is_timer_needed(s)) {\n- timer_mod_anticipate_ns(s->ts,\n- qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->period_ticks);\n- if (!s->timer_running) {\n- s->timer_running = true;\n- s->timer_last = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n- trace_audio_timer_start(s->period_ticks / SCALE_MS);\n- }\n- } else {\n- timer_del(s->ts);\n- if (s->timer_running) {\n- s->timer_running = false;\n- trace_audio_timer_stop();\n- }\n- }\n-}\n-\n-static void audio_timer (void *opaque)\n-{\n- int64_t now, diff;\n- AudioMixengBackend *s = opaque;\n-\n- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n- diff = now - s->timer_last;\n- if (diff > s->period_ticks * 3 / 2) {\n- trace_audio_timer_delayed(diff / SCALE_MS);\n- }\n- s->timer_last = now;\n-\n- audio_run(s, \"timer\");\n- audio_reset_timer(s);\n-}\n-\n-/*\n- * Public API\n- */\n-static size_t audio_mixeng_backend_write(AudioBackend *be, SWVoiceOut *sw,\n- void *buf, size_t size)\n-{\n- HWVoiceOut *hw;\n-\n- if (!sw) {\n- /* XXX: Consider options */\n- return size;\n- }\n- hw = sw->hw;\n-\n- if (!hw->enabled) {\n- dolog(\"Writing to disabled voice %s\\n\", SW_NAME(sw));\n- return 0;\n- }\n-\n- if (audio_get_pdo_out(hw->s->dev)->mixing_engine) {\n- return audio_pcm_sw_write(sw, buf, size);\n- } else {\n- return hw->pcm_ops->write(hw, buf, size);\n- }\n-}\n-\n-static size_t audio_mixeng_backend_read(AudioBackend *be,\n- SWVoiceIn *sw, void *buf, size_t size)\n-{\n- HWVoiceIn *hw;\n-\n- if (!sw) {\n- /* XXX: Consider options */\n- return size;\n- }\n- hw = sw->hw;\n-\n- if (!hw->enabled) {\n- dolog(\"Reading from disabled voice %s\\n\", SW_NAME(sw));\n- return 0;\n- }\n-\n- if (audio_get_pdo_in(hw->s->dev)->mixing_engine) {\n- return audio_pcm_sw_read(sw, buf, size);\n- } else {\n- return hw->pcm_ops->read(hw, buf, size);\n- }\n-\n-}\n-\n-static int audio_mixeng_backend_get_buffer_size_out(AudioBackend *be, SWVoiceOut *sw)\n-{\n- if (!sw) {\n- return 0;\n- }\n-\n- if (audio_get_pdo_out(sw->s->dev)->mixing_engine) {\n- return sw->resample_buf.size * sw->info.bytes_per_frame;\n- }\n-\n- return sw->hw->samples * sw->hw->info.bytes_per_frame;\n-}\n-\n-static void audio_mixeng_backend_set_active_out(AudioBackend *be, SWVoiceOut *sw,\n- bool on)\n-{\n- HWVoiceOut *hw;\n-\n- if (!sw) {\n- return;\n- }\n-\n- hw = sw->hw;\n- if (sw->active != on) {\n- AudioMixengBackend *s = sw->s;\n- SWVoiceOut *temp_sw;\n- SWVoiceCap *sc;\n-\n- if (on) {\n- hw->pending_disable = 0;\n- if (!hw->enabled) {\n- hw->enabled = true;\n- if (runstate_is_running()) {\n- if (hw->pcm_ops->enable_out) {\n- hw->pcm_ops->enable_out(hw, true);\n- }\n- audio_reset_timer (s);\n- }\n- }\n- } else {\n- if (hw->enabled) {\n- int nb_active = 0;\n-\n- for (temp_sw = hw->sw_head.lh_first; temp_sw;\n- temp_sw = temp_sw->entries.le_next) {\n- nb_active += temp_sw->active != 0;\n- }\n-\n- hw->pending_disable = nb_active == 1;\n- }\n- }\n-\n- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n- sc->sw.active = hw->enabled;\n- if (hw->enabled) {\n- audio_capture_maybe_changed (sc->cap, 1);\n- }\n- }\n- sw->active = on;\n- }\n-\n-}\n-\n-static void audio_mixeng_backend_set_active_in(AudioBackend *be, SWVoiceIn *sw, bool on)\n-{\n- HWVoiceIn *hw;\n-\n- if (!sw) {\n- return;\n- }\n-\n- hw = sw->hw;\n- if (sw->active != on) {\n- AudioMixengBackend *s = sw->s;\n- SWVoiceIn *temp_sw;\n-\n- if (on) {\n- if (!hw->enabled) {\n- hw->enabled = true;\n- if (runstate_is_running()) {\n- if (hw->pcm_ops->enable_in) {\n- hw->pcm_ops->enable_in(hw, true);\n- }\n- audio_reset_timer (s);\n- }\n- }\n- sw->total_hw_samples_acquired = hw->total_samples_captured;\n- } else {\n- if (hw->enabled) {\n- int nb_active = 0;\n-\n- for (temp_sw = hw->sw_head.lh_first; temp_sw;\n- temp_sw = temp_sw->entries.le_next) {\n- nb_active += temp_sw->active != 0;\n- }\n-\n- if (nb_active == 1) {\n- hw->enabled = false;\n- if (hw->pcm_ops->enable_in) {\n- hw->pcm_ops->enable_in(hw, false);\n- }\n- }\n- }\n- }\n- sw->active = on;\n- }\n-}\n-\n-static size_t audio_get_avail(SWVoiceIn *sw)\n-{\n- size_t live;\n-\n- if (!sw) {\n- return 0;\n- }\n-\n- live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired;\n- if (audio_bug(__func__, live > sw->hw->conv_buf.size)) {\n- dolog(\"live=%zu sw->hw->conv_buf.size=%zu\\n\", live,\n- sw->hw->conv_buf.size);\n- return 0;\n- }\n-\n- ldebug (\n- \"%s: get_avail live %zu frontend frames %u\\n\",\n- SW_NAME (sw),\n- live, st_rate_frames_out(sw->rate, live)\n- );\n-\n- return live;\n-}\n-\n-static size_t audio_get_free(SWVoiceOut *sw)\n-{\n- size_t live, dead;\n-\n- if (!sw) {\n- return 0;\n- }\n-\n- live = sw->total_hw_samples_mixed;\n-\n- if (audio_bug(__func__, live > sw->hw->mix_buf.size)) {\n- dolog(\"live=%zu sw->hw->mix_buf.size=%zu\\n\", live,\n- sw->hw->mix_buf.size);\n- return 0;\n- }\n-\n- dead = sw->hw->mix_buf.size - live;\n-\n-#ifdef DEBUG_OUT\n- dolog(\"%s: get_free live %zu dead %zu frontend frames %u\\n\",\n- SW_NAME(sw), live, dead, st_rate_frames_in(sw->rate, dead));\n-#endif\n-\n- return dead;\n-}\n-\n-static void audio_capture_mix_and_clear(HWVoiceOut *hw, size_t rpos,\n- size_t samples)\n-{\n- size_t n;\n-\n- if (hw->enabled) {\n- SWVoiceCap *sc;\n-\n- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n- SWVoiceOut *sw = &sc->sw;\n- size_t rpos2 = rpos;\n-\n- n = samples;\n- while (n) {\n- size_t till_end_of_hw = hw->mix_buf.size - rpos2;\n- size_t to_read = MIN(till_end_of_hw, n);\n- size_t live, frames_in, frames_out;\n-\n- sw->resample_buf.buffer = hw->mix_buf.buffer + rpos2;\n- sw->resample_buf.size = to_read;\n- live = sw->total_hw_samples_mixed;\n-\n- audio_pcm_sw_resample_out(sw,\n- to_read, sw->hw->mix_buf.size - live,\n- &frames_in, &frames_out);\n-\n- sw->total_hw_samples_mixed += frames_out;\n- sw->empty = sw->total_hw_samples_mixed == 0;\n-\n- if (to_read - frames_in) {\n- dolog(\"Could not mix %zu frames into a capture \"\n- \"buffer, mixed %zu\\n\",\n- to_read, frames_in);\n- break;\n- }\n- n -= to_read;\n- rpos2 = (rpos2 + to_read) % hw->mix_buf.size;\n- }\n- }\n- }\n-\n- n = MIN(samples, hw->mix_buf.size - rpos);\n- mixeng_clear(hw->mix_buf.buffer + rpos, n);\n- mixeng_clear(hw->mix_buf.buffer, samples - n);\n-}\n-\n-static size_t audio_pcm_hw_run_out(HWVoiceOut *hw, size_t live)\n-{\n- size_t clipped = 0;\n-\n- while (live) {\n- size_t size = live * hw->info.bytes_per_frame;\n- size_t decr, proc;\n- void *buf = hw->pcm_ops->get_buffer_out(hw, &size);\n-\n- if (size == 0) {\n- break;\n- }\n-\n- decr = MIN(size / hw->info.bytes_per_frame, live);\n- if (buf) {\n- audio_pcm_hw_clip_out(hw, buf, decr);\n- }\n- proc = hw->pcm_ops->put_buffer_out(hw, buf,\n- decr * hw->info.bytes_per_frame) /\n- hw->info.bytes_per_frame;\n-\n- live -= proc;\n- clipped += proc;\n- hw->mix_buf.pos = (hw->mix_buf.pos + proc) % hw->mix_buf.size;\n-\n- if (proc == 0 || proc < decr) {\n- break;\n- }\n- }\n-\n- if (hw->pcm_ops->run_buffer_out) {\n- hw->pcm_ops->run_buffer_out(hw);\n- }\n-\n- return clipped;\n-}\n-\n-static void audio_run_out(AudioMixengBackend *s)\n-{\n- HWVoiceOut *hw = NULL;\n- SWVoiceOut *sw;\n-\n- while ((hw = audio_pcm_hw_find_any_enabled_out(s, hw))) {\n- size_t played, live, prev_rpos;\n- size_t hw_free = audio_pcm_hw_get_free(hw);\n- int nb_live;\n-\n- if (!audio_get_pdo_out(s->dev)->mixing_engine) {\n- /* there is exactly 1 sw for each hw with no mixeng */\n- sw = hw->sw_head.lh_first;\n-\n- if (hw->pending_disable) {\n- hw->enabled = false;\n- hw->pending_disable = false;\n- if (hw->pcm_ops->enable_out) {\n- hw->pcm_ops->enable_out(hw, false);\n- }\n- }\n-\n- if (sw->active) {\n- sw->callback.fn(sw->callback.opaque,\n- hw_free * sw->info.bytes_per_frame);\n- }\n-\n- if (hw->pcm_ops->run_buffer_out) {\n- hw->pcm_ops->run_buffer_out(hw);\n- }\n-\n- continue;\n- }\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (sw->active) {\n- size_t sw_free = audio_get_free(sw);\n- size_t free;\n-\n- if (hw_free > sw->total_hw_samples_mixed) {\n- free = st_rate_frames_in(sw->rate,\n- MIN(sw_free, hw_free - sw->total_hw_samples_mixed));\n- } else {\n- free = 0;\n- }\n- if (free > sw->resample_buf.pos) {\n- free = MIN(free, sw->resample_buf.size)\n- - sw->resample_buf.pos;\n- sw->callback.fn(sw->callback.opaque,\n- free * sw->info.bytes_per_frame);\n- }\n- }\n- }\n-\n- live = audio_pcm_hw_get_live_out (hw, &nb_live);\n- if (!nb_live) {\n- live = 0;\n- }\n-\n- if (audio_bug(__func__, live > hw->mix_buf.size)) {\n- dolog(\"live=%zu hw->mix_buf.size=%zu\\n\", live, hw->mix_buf.size);\n- continue;\n- }\n-\n- if (hw->pending_disable && !nb_live) {\n- SWVoiceCap *sc;\n-#ifdef DEBUG_OUT\n- dolog (\"Disabling voice\\n\");\n-#endif\n- hw->enabled = false;\n- hw->pending_disable = false;\n- if (hw->pcm_ops->enable_out) {\n- hw->pcm_ops->enable_out(hw, false);\n- }\n- for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n- sc->sw.active = false;\n- audio_recalc_and_notify_capture (sc->cap);\n- }\n- continue;\n- }\n-\n- if (!live) {\n- if (hw->pcm_ops->run_buffer_out) {\n- hw->pcm_ops->run_buffer_out(hw);\n- }\n- continue;\n- }\n-\n- prev_rpos = hw->mix_buf.pos;\n- played = audio_pcm_hw_run_out(hw, live);\n- replay_audio_out(&played);\n- if (audio_bug(__func__, hw->mix_buf.pos >= hw->mix_buf.size)) {\n- dolog(\"hw->mix_buf.pos=%zu hw->mix_buf.size=%zu played=%zu\\n\",\n- hw->mix_buf.pos, hw->mix_buf.size, played);\n- hw->mix_buf.pos = 0;\n- }\n-\n-#ifdef DEBUG_OUT\n- dolog(\"played=%zu\\n\", played);\n-#endif\n-\n- if (played) {\n- audio_capture_mix_and_clear (hw, prev_rpos, played);\n- }\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (!sw->active && sw->empty) {\n- continue;\n- }\n-\n- if (audio_bug(__func__, played > sw->total_hw_samples_mixed)) {\n- dolog(\"played=%zu sw->total_hw_samples_mixed=%zu\\n\",\n- played, sw->total_hw_samples_mixed);\n- played = sw->total_hw_samples_mixed;\n- }\n-\n- sw->total_hw_samples_mixed -= played;\n-\n- if (!sw->total_hw_samples_mixed) {\n- sw->empty = true;\n- }\n- }\n- }\n-}\n-\n-static size_t audio_pcm_hw_run_in(HWVoiceIn *hw, size_t samples)\n-{\n- size_t conv = 0;\n-\n- if (hw->pcm_ops->run_buffer_in) {\n- hw->pcm_ops->run_buffer_in(hw);\n- }\n-\n- while (samples) {\n- size_t proc;\n- size_t size = samples * hw->info.bytes_per_frame;\n- void *buf = hw->pcm_ops->get_buffer_in(hw, &size);\n-\n- assert(size % hw->info.bytes_per_frame == 0);\n- if (size == 0) {\n- break;\n- }\n-\n- proc = audio_pcm_hw_conv_in(hw, buf, size / hw->info.bytes_per_frame);\n-\n- samples -= proc;\n- conv += proc;\n- hw->pcm_ops->put_buffer_in(hw, buf, proc * hw->info.bytes_per_frame);\n- }\n-\n- return conv;\n-}\n-\n-static void audio_run_in(AudioMixengBackend *s)\n-{\n- HWVoiceIn *hw = NULL;\n-\n- if (!audio_get_pdo_in(s->dev)->mixing_engine) {\n- while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {\n- /* there is exactly 1 sw for each hw with no mixeng */\n- SWVoiceIn *sw = hw->sw_head.lh_first;\n- if (sw->active) {\n- sw->callback.fn(sw->callback.opaque, INT_MAX);\n- }\n- }\n- return;\n- }\n-\n- while ((hw = audio_pcm_hw_find_any_enabled_in(s, hw))) {\n- SWVoiceIn *sw;\n- size_t captured = 0, min;\n- int pos;\n-\n- if (replay_mode != REPLAY_MODE_PLAY) {\n- captured = audio_pcm_hw_run_in(\n- hw, hw->conv_buf.size - audio_pcm_hw_get_live_in(hw));\n- }\n-\n- replay_audio_in_start(&captured);\n- assert(captured <= hw->conv_buf.size);\n- if (replay_mode == REPLAY_MODE_PLAY) {\n- hw->conv_buf.pos = (hw->conv_buf.pos + captured) % hw->conv_buf.size;\n- }\n- for (pos = (hw->conv_buf.pos - captured + hw->conv_buf.size) % hw->conv_buf.size;\n- pos != hw->conv_buf.pos;\n- pos = (pos + 1) % hw->conv_buf.size) {\n- uint64_t left, right;\n-\n- if (replay_mode == REPLAY_MODE_RECORD) {\n- audio_sample_to_uint64(hw->conv_buf.buffer, pos, &left, &right);\n- }\n- replay_audio_in_sample_lr(&left, &right);\n- if (replay_mode == REPLAY_MODE_PLAY) {\n- audio_sample_from_uint64(hw->conv_buf.buffer, pos, left, right);\n- }\n- }\n- replay_audio_in_finish();\n-\n- min = audio_pcm_hw_find_min_in (hw);\n- hw->total_samples_captured += captured - min;\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- sw->total_hw_samples_acquired -= min;\n-\n- if (sw->active) {\n- size_t sw_avail = audio_get_avail(sw);\n- size_t avail;\n-\n- avail = st_rate_frames_out(sw->rate, sw_avail);\n- if (avail > 0) {\n- avail = MIN(avail, sw->resample_buf.size);\n- sw->callback.fn(sw->callback.opaque,\n- avail * sw->info.bytes_per_frame);\n- }\n- }\n- }\n- }\n-}\n-\n-static void audio_run_capture(AudioMixengBackend *s)\n-{\n- CaptureVoiceOut *cap;\n-\n- for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) {\n- size_t live, rpos, captured;\n- HWVoiceOut *hw = &cap->hw;\n- SWVoiceOut *sw;\n-\n- captured = live = audio_pcm_hw_get_live_out (hw, NULL);\n- rpos = hw->mix_buf.pos;\n- while (live) {\n- size_t left = hw->mix_buf.size - rpos;\n- size_t to_capture = MIN(live, left);\n- struct st_sample *src;\n- struct capture_callback *cb;\n-\n- src = hw->mix_buf.buffer + rpos;\n- hw->clip (cap->buf, src, to_capture);\n- mixeng_clear (src, to_capture);\n-\n- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n- cb->ops.capture (cb->opaque, cap->buf,\n- to_capture * hw->info.bytes_per_frame);\n- }\n- rpos = (rpos + to_capture) % hw->mix_buf.size;\n- live -= to_capture;\n- }\n- hw->mix_buf.pos = rpos;\n-\n- for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) {\n- if (!sw->active && sw->empty) {\n- continue;\n- }\n-\n- if (audio_bug(__func__, captured > sw->total_hw_samples_mixed)) {\n- dolog(\"captured=%zu sw->total_hw_samples_mixed=%zu\\n\",\n- captured, sw->total_hw_samples_mixed);\n- captured = sw->total_hw_samples_mixed;\n- }\n-\n- sw->total_hw_samples_mixed -= captured;\n- sw->empty = sw->total_hw_samples_mixed == 0;\n- }\n- }\n-}\n-\n-void audio_run(AudioMixengBackend *s, const char *msg)\n-{\n- audio_run_out(s);\n- audio_run_in(s);\n- audio_run_capture(s);\n-\n-#ifdef DEBUG_POLL\n- {\n- static double prevtime;\n- double currtime;\n- struct timeval tv;\n-\n- if (gettimeofday (&tv, NULL)) {\n- perror (\"audio_run: gettimeofday\");\n- return;\n- }\n-\n- currtime = tv.tv_sec + tv.tv_usec * 1e-6;\n- dolog (\"Elapsed since last %s: %f\\n\", msg, currtime - prevtime);\n- prevtime = currtime;\n- }\n-#endif\n-}\n-\n-void audio_generic_run_buffer_in(HWVoiceIn *hw)\n-{\n- if (unlikely(!hw->buf_emul)) {\n- hw->size_emul = hw->samples * hw->info.bytes_per_frame;\n- hw->buf_emul = g_malloc(hw->size_emul);\n- hw->pos_emul = hw->pending_emul = 0;\n- }\n-\n- while (hw->pending_emul < hw->size_emul) {\n- size_t read_len = MIN(hw->size_emul - hw->pos_emul,\n- hw->size_emul - hw->pending_emul);\n- size_t read = hw->pcm_ops->read(hw, hw->buf_emul + hw->pos_emul,\n- read_len);\n- hw->pending_emul += read;\n- hw->pos_emul = (hw->pos_emul + read) % hw->size_emul;\n- if (read < read_len) {\n- break;\n- }\n- }\n-}\n-\n-void *audio_generic_get_buffer_in(HWVoiceIn *hw, size_t *size)\n-{\n- size_t start;\n-\n- start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);\n- assert(start < hw->size_emul);\n-\n- *size = MIN(*size, hw->pending_emul);\n- *size = MIN(*size, hw->size_emul - start);\n- return hw->buf_emul + start;\n-}\n-\n-void audio_generic_put_buffer_in(HWVoiceIn *hw, void *buf, size_t size)\n-{\n- assert(size <= hw->pending_emul);\n- hw->pending_emul -= size;\n-}\n-\n-size_t audio_generic_buffer_get_free(HWVoiceOut *hw)\n-{\n- if (hw->buf_emul) {\n- return hw->size_emul - hw->pending_emul;\n- } else {\n- return hw->samples * hw->info.bytes_per_frame;\n- }\n-}\n-\n-void audio_generic_run_buffer_out(HWVoiceOut *hw)\n-{\n- while (hw->pending_emul) {\n- size_t write_len, written, start;\n-\n- start = audio_ring_posb(hw->pos_emul, hw->pending_emul, hw->size_emul);\n- assert(start < hw->size_emul);\n-\n- write_len = MIN(hw->pending_emul, hw->size_emul - start);\n-\n- written = hw->pcm_ops->write(hw, hw->buf_emul + start, write_len);\n- hw->pending_emul -= written;\n-\n- if (written < write_len) {\n- break;\n- }\n- }\n-}\n-\n-void *audio_generic_get_buffer_out(HWVoiceOut *hw, size_t *size)\n-{\n- if (unlikely(!hw->buf_emul)) {\n- hw->size_emul = hw->samples * hw->info.bytes_per_frame;\n- hw->buf_emul = g_malloc(hw->size_emul);\n- hw->pos_emul = hw->pending_emul = 0;\n- }\n-\n- *size = MIN(hw->size_emul - hw->pending_emul,\n- hw->size_emul - hw->pos_emul);\n- return hw->buf_emul + hw->pos_emul;\n-}\n-\n-size_t audio_generic_put_buffer_out(HWVoiceOut *hw, void *buf, size_t size)\n-{\n- assert(buf == hw->buf_emul + hw->pos_emul &&\n- size + hw->pending_emul <= hw->size_emul);\n-\n- hw->pending_emul += size;\n- hw->pos_emul = (hw->pos_emul + size) % hw->size_emul;\n-\n- return size;\n-}\n-\n-size_t audio_generic_write(HWVoiceOut *hw, void *buf, size_t size)\n-{\n- size_t total = 0;\n-\n- if (hw->pcm_ops->buffer_get_free) {\n- size_t free = hw->pcm_ops->buffer_get_free(hw);\n-\n- size = MIN(size, free);\n- }\n-\n- while (total < size) {\n- size_t dst_size = size - total;\n- size_t copy_size, proc;\n- void *dst = hw->pcm_ops->get_buffer_out(hw, &dst_size);\n-\n- if (dst_size == 0) {\n- break;\n- }\n-\n- copy_size = MIN(size - total, dst_size);\n- if (dst) {\n- memcpy(dst, (char *)buf + total, copy_size);\n- }\n- proc = hw->pcm_ops->put_buffer_out(hw, dst, copy_size);\n- total += proc;\n-\n- if (proc == 0 || proc < copy_size) {\n- break;\n- }\n- }\n-\n- return total;\n-}\n-\n-size_t audio_generic_read(HWVoiceIn *hw, void *buf, size_t size)\n-{\n- size_t total = 0;\n-\n- if (hw->pcm_ops->run_buffer_in) {\n- hw->pcm_ops->run_buffer_in(hw);\n- }\n-\n- while (total < size) {\n- size_t src_size = size - total;\n- void *src = hw->pcm_ops->get_buffer_in(hw, &src_size);\n-\n- if (src_size == 0) {\n- break;\n- }\n-\n- memcpy((char *)buf + total, src, src_size);\n- hw->pcm_ops->put_buffer_in(hw, src, src_size);\n- total += src_size;\n- }\n-\n- return total;\n-}\n-\n-static bool audio_mixeng_backend_realize(AudioBackend *abe,\n- Audiodev *dev, Error **errp)\n-{\n- AudioMixengBackend *be = AUDIO_MIXENG_BACKEND(abe);\n- audio_driver *drv = AUDIO_MIXENG_BACKEND_GET_CLASS(be)->driver;\n-\n- be->dev = dev;\n- be->drv_opaque = drv->init(be->dev, errp);\n- if (!be->drv_opaque) {\n- return false;\n- }\n-\n- if (!drv->pcm_ops->get_buffer_in) {\n- drv->pcm_ops->get_buffer_in = audio_generic_get_buffer_in;\n- drv->pcm_ops->put_buffer_in = audio_generic_put_buffer_in;\n- }\n- if (!drv->pcm_ops->get_buffer_out) {\n- drv->pcm_ops->get_buffer_out = audio_generic_get_buffer_out;\n- drv->pcm_ops->put_buffer_out = audio_generic_put_buffer_out;\n- }\n-\n- audio_init_nb_voices_out(be, drv, 1);\n- audio_init_nb_voices_in(be, drv, 0);\n- be->drv = drv;\n-\n- if (be->dev->timer_period <= 0) {\n- be->period_ticks = 1;\n- } else {\n- be->period_ticks = be->dev->timer_period * (int64_t)SCALE_US;\n- }\n-\n- return true;\n-}\n-\n-static void audio_vm_change_state_handler (void *opaque, bool running,\n- RunState state)\n-{\n- AudioMixengBackend *s = opaque;\n- HWVoiceOut *hwo = NULL;\n- HWVoiceIn *hwi = NULL;\n-\n- while ((hwo = audio_pcm_hw_find_any_enabled_out(s, hwo))) {\n- if (hwo->pcm_ops->enable_out) {\n- hwo->pcm_ops->enable_out(hwo, running);\n- }\n- }\n-\n- while ((hwi = audio_pcm_hw_find_any_enabled_in(s, hwi))) {\n- if (hwi->pcm_ops->enable_in) {\n- hwi->pcm_ops->enable_in(hwi, running);\n- }\n- }\n- audio_reset_timer (s);\n-}\n-\n-static const VMStateDescription vmstate_audio;\n-\n-static const char *audio_mixeng_backend_get_id(AudioBackend *be)\n-{\n- return AUDIO_MIXENG_BACKEND(be)->dev->id;\n-}\n-\n-static CaptureVoiceOut *audio_mixeng_backend_add_capture(\n- AudioBackend *be,\n- struct audsettings *as,\n- struct audio_capture_ops *ops,\n- void *cb_opaque);\n-\n-static void audio_mixeng_backend_del_capture(\n- AudioBackend *be,\n- CaptureVoiceOut *cap,\n- void *cb_opaque);\n-\n-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,\n- Volume *vol);\n-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,\n- Volume *vol);\n-\n-static void audio_mixeng_backend_class_init(ObjectClass *klass, const void *data)\n-{\n- AudioBackendClass *be = AUDIO_BACKEND_CLASS(klass);\n-\n- be->realize = audio_mixeng_backend_realize;\n- be->get_id = audio_mixeng_backend_get_id;\n- be->open_in = audio_mixeng_backend_open_in;\n- be->open_out = audio_mixeng_backend_open_out;\n- be->close_in = audio_mixeng_backend_close_in;\n- be->close_out = audio_mixeng_backend_close_out;\n- be->is_active_out = audio_mixeng_backend_is_active_out;\n- be->is_active_in = audio_mixeng_backend_is_active_in;\n- be->set_active_out = audio_mixeng_backend_set_active_out;\n- be->set_active_in = audio_mixeng_backend_set_active_in;\n- be->set_volume_out = audio_mixeng_backend_set_volume_out;\n- be->set_volume_in = audio_mixeng_backend_set_volume_in;\n- be->read = audio_mixeng_backend_read;\n- be->write = audio_mixeng_backend_write;\n- be->get_buffer_size_out = audio_mixeng_backend_get_buffer_size_out;\n- be->add_capture = audio_mixeng_backend_add_capture;\n- be->del_capture = audio_mixeng_backend_del_capture;\n-}\n-\n-static void audio_mixeng_backend_init(Object *obj)\n-{\n- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);\n-\n- QLIST_INIT(&s->hw_head_out);\n- QLIST_INIT(&s->hw_head_in);\n- QLIST_INIT(&s->cap_head);\n- s->ts = timer_new_ns(QEMU_CLOCK_VIRTUAL, audio_timer, s);\n-\n- s->vmse = qemu_add_vm_change_state_handler(audio_vm_change_state_handler, s);\n- assert(s->vmse != NULL);\n-\n- vmstate_register_any(NULL, &vmstate_audio, s);\n-}\n-\n-static void audio_mixeng_backend_finalize(Object *obj)\n-{\n- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(obj);\n- HWVoiceOut *hwo, *hwon;\n- HWVoiceIn *hwi, *hwin;\n-\n- QLIST_FOREACH_SAFE(hwo, &s->hw_head_out, entries, hwon) {\n- SWVoiceCap *sc;\n+/* SPDX-License-Identifier: MIT */\n \n- if (hwo->enabled && hwo->pcm_ops->enable_out) {\n- hwo->pcm_ops->enable_out(hwo, false);\n- }\n- hwo->pcm_ops->fini_out (hwo);\n-\n- for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) {\n- CaptureVoiceOut *cap = sc->cap;\n- struct capture_callback *cb;\n+#include \"qemu/osdep.h\"\n+#include \"qemu/audio.h\"\n+#include \"qemu/help_option.h\"\n+#include \"qapi/clone-visitor.h\"\n+#include \"qapi/qobject-input-visitor.h\"\n+#include \"qapi/qapi-visit-audio.h\"\n+#include \"qapi/qapi-commands-audio.h\"\n+#include \"qobject/qdict.h\"\n+#include \"system/system.h\"\n \n- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n- cb->ops.destroy (cb->opaque);\n- }\n- }\n- QLIST_REMOVE(hwo, entries);\n- }\n+/* Order of CONFIG_AUDIO_DRIVERS is import.\n+ The 1st one is the one used by default, that is the reason\n+ that we generate the list.\n+*/\n+const char *audio_prio_list[] = {\n+#ifdef CONFIG_GIO\n+ \"dbus\",\n+#endif\n+ \"spice\",\n+ CONFIG_AUDIO_DRIVERS\n+ \"none\",\n+ NULL\n+};\n \n- QLIST_FOREACH_SAFE(hwi, &s->hw_head_in, entries, hwin) {\n- if (hwi->enabled && hwi->pcm_ops->enable_in) {\n- hwi->pcm_ops->enable_in(hwi, false);\n- }\n- hwi->pcm_ops->fini_in (hwi);\n- QLIST_REMOVE(hwi, entries);\n- }\n+typedef struct AudiodevListEntry {\n+ Audiodev *dev;\n+ QSIMPLEQ_ENTRY(AudiodevListEntry) next;\n+} AudiodevListEntry;\n \n- if (s->drv) {\n- s->drv->fini (s->drv_opaque);\n- s->drv = NULL;\n- }\n+typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;\n \n- if (s->dev) {\n- qapi_free_Audiodev(s->dev);\n- s->dev = NULL;\n- }\n+static AudiodevListHead audiodevs =\n+ QSIMPLEQ_HEAD_INITIALIZER(audiodevs);\n+static AudiodevListHead default_audiodevs =\n+ QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);\n \n- if (s->ts) {\n- timer_free(s->ts);\n- s->ts = NULL;\n- }\n+static AudioBackendClass *audio_be_class_by_name(const char *name)\n+{\n+ g_autofree char *tname = g_strconcat(\"audio-\", name, NULL);\n+ ObjectClass *oc = module_object_class_by_name(tname);\n \n- if (s->vmse) {\n- qemu_del_vm_change_state_handler(s->vmse);\n- s->vmse = NULL;\n+ if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {\n+ return NULL;\n }\n \n- vmstate_unregister(NULL, &vmstate_audio, s);\n+ return AUDIO_BACKEND_CLASS(oc);\n }\n \n+static AudioBackend *default_audio_be;\n+\n static Object *get_audiodevs_root(void)\n {\n return object_get_container(\"audiodevs\");\n@@ -1758,25 +62,6 @@ void audio_cleanup(void)\n object_unparent(get_audiodevs_root());\n }\n \n-static bool vmstate_audio_needed(void *opaque)\n-{\n- /*\n- * Never needed, this vmstate only exists in case\n- * an old qemu sends it to us.\n- */\n- return false;\n-}\n-\n-static const VMStateDescription vmstate_audio = {\n- .name = \"audio\",\n- .version_id = 1,\n- .minimum_version_id = 1,\n- .needed = vmstate_audio_needed,\n- .fields = (const VMStateField[]) {\n- VMSTATE_END_OF_LIST()\n- }\n-};\n-\n void audio_create_default_audiodevs(void)\n {\n for (int i = 0; audio_prio_list[i]; i++) {\n@@ -1817,15 +102,13 @@ static AudioBackend *audio_init(Audiodev *dev, Error **errp)\n assert(!default_audio_be);\n for (;;) {\n AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);\n-\n if (!e) {\n error_setg(errp, \"no default audio driver available\");\n return NULL;\n }\n- dev = e->dev;\n QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);\n+ be = audio_be_new(e->dev, NULL);\n g_free(e);\n- be = audio_be_new(dev, NULL);\n if (be) {\n break;\n }\n@@ -1856,156 +139,35 @@ AudioBackend *audio_get_default_audio_be(Error **errp)\n return default_audio_be;\n }\n \n-static struct audio_pcm_ops capture_pcm_ops;\n-\n-static CaptureVoiceOut *audio_mixeng_backend_add_capture(\n- AudioBackend *be,\n- struct audsettings *as,\n- struct audio_capture_ops *ops,\n- void *cb_opaque)\n-{\n- AudioMixengBackend *s = AUDIO_MIXENG_BACKEND(be);\n- CaptureVoiceOut *cap;\n- struct capture_callback *cb;\n-\n- if (!s) {\n- error_report(\"Capturing without setting an audiodev is not supported\");\n- abort();\n- }\n-\n- if (!audio_get_pdo_out(s->dev)->mixing_engine) {\n- dolog(\"Can't capture with mixeng disabled\\n\");\n- return NULL;\n- }\n-\n- if (audio_validate_settings (as)) {\n- dolog (\"Invalid settings were passed when trying to add capture\\n\");\n- audio_print_settings (as);\n- return NULL;\n- }\n-\n- cb = g_malloc0(sizeof(*cb));\n- cb->ops = *ops;\n- cb->opaque = cb_opaque;\n-\n- cap = audio_pcm_capture_find_specific(s, as);\n- if (cap) {\n- QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);\n- } else {\n- HWVoiceOut *hw;\n-\n- cap = g_malloc0(sizeof(*cap));\n-\n- hw = &cap->hw;\n- hw->s = s;\n- hw->pcm_ops = &capture_pcm_ops;\n- QLIST_INIT (&hw->sw_head);\n- QLIST_INIT (&cap->cb_head);\n-\n- /* XXX find a more elegant way */\n- hw->samples = 4096 * 4;\n- audio_pcm_hw_alloc_resources_out(hw);\n-\n- audio_pcm_init_info (&hw->info, as);\n-\n- cap->buf = g_malloc0_n(hw->mix_buf.size, hw->info.bytes_per_frame);\n-\n- if (hw->info.is_float) {\n- hw->clip = mixeng_clip_float[hw->info.nchannels == 2]\n- [hw->info.swap_endianness];\n- } else {\n- hw->clip = mixeng_clip\n- [hw->info.nchannels == 2]\n- [hw->info.is_signed]\n- [hw->info.swap_endianness]\n- [audio_bits_to_index(hw->info.bits)];\n- }\n-\n- QLIST_INSERT_HEAD (&s->cap_head, cap, entries);\n- QLIST_INSERT_HEAD (&cap->cb_head, cb, entries);\n-\n- QLIST_FOREACH(hw, &s->hw_head_out, entries) {\n- audio_attach_capture (hw);\n- }\n- }\n-\n- return cap;\n-}\n-\n-static void audio_mixeng_backend_del_capture(\n- AudioBackend *be,\n- CaptureVoiceOut *cap,\n- void *cb_opaque)\n+void audio_help(void)\n {\n- struct capture_callback *cb;\n-\n- for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) {\n- if (cb->opaque == cb_opaque) {\n- cb->ops.destroy (cb_opaque);\n- QLIST_REMOVE (cb, entries);\n- g_free (cb);\n+ int i;\n \n- if (!cap->cb_head.lh_first) {\n- SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1;\n+ printf(\"Available audio drivers:\\n\");\n \n- while (sw) {\n- SWVoiceCap *sc = (SWVoiceCap *) sw;\n-#ifdef DEBUG_CAPTURE\n- dolog (\"freeing %s\\n\", sw->name);\n-#endif\n+ for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {\n+ const char *name = AudiodevDriver_str(i);\n+ AudioBackendClass *be = audio_be_class_by_name(name);\n \n- sw1 = sw->entries.le_next;\n- if (sw->rate) {\n- st_rate_stop (sw->rate);\n- sw->rate = NULL;\n- }\n- QLIST_REMOVE (sw, entries);\n- QLIST_REMOVE (sc, entries);\n- g_free (sc);\n- sw = sw1;\n- }\n- QLIST_REMOVE (cap, entries);\n- g_free(cap->hw.mix_buf.buffer);\n- g_free (cap->buf);\n- g_free (cap);\n- }\n- return;\n+ if (be) {\n+ printf(\"%s\\n\", name);\n }\n }\n }\n \n-static void audio_mixeng_backend_set_volume_out(AudioBackend *be, SWVoiceOut *sw,\n- Volume *vol)\n+void audio_parse_option(const char *opt)\n {\n- if (sw) {\n- HWVoiceOut *hw = sw->hw;\n-\n- sw->vol.mute = vol->mute;\n- sw->vol.l = nominal_volume.l * vol->vol[0] / 255;\n- sw->vol.r = nominal_volume.l * vol->vol[vol->channels > 1 ? 1 : 0] /\n- 255;\n+ Audiodev *dev = NULL;\n \n- if (hw->pcm_ops->volume_out) {\n- hw->pcm_ops->volume_out(hw, vol);\n- }\n+ if (is_help_option(opt)) {\n+ audio_help();\n+ exit(EXIT_SUCCESS);\n }\n-}\n-\n-static void audio_mixeng_backend_set_volume_in(AudioBackend *be, SWVoiceIn *sw,\n- Volume *vol)\n-{\n- if (sw) {\n- HWVoiceIn *hw = sw->hw;\n-\n- sw->vol.mute = vol->mute;\n- sw->vol.l = nominal_volume.l * vol->vol[0] / 255;\n- sw->vol.r = nominal_volume.r * vol->vol[vol->channels > 1 ? 1 : 0] /\n- 255;\n+ Visitor *v = qobject_input_visitor_new_str(opt, \"driver\", &error_fatal);\n+ visit_type_Audiodev(v, NULL, &dev, &error_fatal);\n+ visit_free(v);\n \n- if (hw->pcm_ops->volume_in) {\n- hw->pcm_ops->volume_in(hw, vol);\n- }\n- }\n+ audio_add_audiodev(dev);\n }\n \n static void audio_create_pdos(Audiodev *dev)\n@@ -2104,6 +266,124 @@ static void audio_validate_per_direction_opts(\n }\n }\n \n+static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)\n+{\n+ switch (dev->driver) {\n+ case AUDIODEV_DRIVER_NONE:\n+ return dev->u.none.out;\n+#ifdef CONFIG_AUDIO_ALSA\n+ case AUDIODEV_DRIVER_ALSA:\n+ return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);\n+#endif\n+#ifdef CONFIG_AUDIO_COREAUDIO\n+ case AUDIODEV_DRIVER_COREAUDIO:\n+ return qapi_AudiodevCoreaudioPerDirectionOptions_base(\n+ dev->u.coreaudio.out);\n+#endif\n+#ifdef CONFIG_DBUS_DISPLAY\n+ case AUDIODEV_DRIVER_DBUS:\n+ return dev->u.dbus.out;\n+#endif\n+#ifdef CONFIG_AUDIO_DSOUND\n+ case AUDIODEV_DRIVER_DSOUND:\n+ return dev->u.dsound.out;\n+#endif\n+#ifdef CONFIG_AUDIO_JACK\n+ case AUDIODEV_DRIVER_JACK:\n+ return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);\n+#endif\n+#ifdef CONFIG_AUDIO_OSS\n+ case AUDIODEV_DRIVER_OSS:\n+ return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);\n+#endif\n+#ifdef CONFIG_AUDIO_PA\n+ case AUDIODEV_DRIVER_PA:\n+ return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);\n+#endif\n+#ifdef CONFIG_AUDIO_PIPEWIRE\n+ case AUDIODEV_DRIVER_PIPEWIRE:\n+ return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);\n+#endif\n+#ifdef CONFIG_AUDIO_SDL\n+ case AUDIODEV_DRIVER_SDL:\n+ return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);\n+#endif\n+#ifdef CONFIG_AUDIO_SNDIO\n+ case AUDIODEV_DRIVER_SNDIO:\n+ return dev->u.sndio.out;\n+#endif\n+#ifdef CONFIG_SPICE\n+ case AUDIODEV_DRIVER_SPICE:\n+ return dev->u.spice.out;\n+#endif\n+ case AUDIODEV_DRIVER_WAV:\n+ return dev->u.wav.out;\n+\n+ case AUDIODEV_DRIVER__MAX:\n+ break;\n+ }\n+ abort();\n+}\n+\n+static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)\n+{\n+ switch (dev->driver) {\n+ case AUDIODEV_DRIVER_NONE:\n+ return dev->u.none.in;\n+#ifdef CONFIG_AUDIO_ALSA\n+ case AUDIODEV_DRIVER_ALSA:\n+ return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);\n+#endif\n+#ifdef CONFIG_AUDIO_COREAUDIO\n+ case AUDIODEV_DRIVER_COREAUDIO:\n+ return qapi_AudiodevCoreaudioPerDirectionOptions_base(\n+ dev->u.coreaudio.in);\n+#endif\n+#ifdef CONFIG_DBUS_DISPLAY\n+ case AUDIODEV_DRIVER_DBUS:\n+ return dev->u.dbus.in;\n+#endif\n+#ifdef CONFIG_AUDIO_DSOUND\n+ case AUDIODEV_DRIVER_DSOUND:\n+ return dev->u.dsound.in;\n+#endif\n+#ifdef CONFIG_AUDIO_JACK\n+ case AUDIODEV_DRIVER_JACK:\n+ return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);\n+#endif\n+#ifdef CONFIG_AUDIO_OSS\n+ case AUDIODEV_DRIVER_OSS:\n+ return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);\n+#endif\n+#ifdef CONFIG_AUDIO_PA\n+ case AUDIODEV_DRIVER_PA:\n+ return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);\n+#endif\n+#ifdef CONFIG_AUDIO_PIPEWIRE\n+ case AUDIODEV_DRIVER_PIPEWIRE:\n+ return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);\n+#endif\n+#ifdef CONFIG_AUDIO_SDL\n+ case AUDIODEV_DRIVER_SDL:\n+ return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);\n+#endif\n+#ifdef CONFIG_AUDIO_SNDIO\n+ case AUDIODEV_DRIVER_SNDIO:\n+ return dev->u.sndio.in;\n+#endif\n+#ifdef CONFIG_SPICE\n+ case AUDIODEV_DRIVER_SPICE:\n+ return dev->u.spice.in;\n+#endif\n+ case AUDIODEV_DRIVER_WAV:\n+ return dev->u.wav.in;\n+\n+ case AUDIODEV_DRIVER__MAX:\n+ break;\n+ }\n+ abort();\n+}\n+\n static void audio_validate_opts(Audiodev *dev, Error **errp)\n {\n Error *err = NULL;\n@@ -2128,37 +408,6 @@ static void audio_validate_opts(Audiodev *dev, Error **errp)\n }\n }\n \n-void audio_help(void)\n-{\n- int i;\n-\n- printf(\"Available audio drivers:\\n\");\n-\n- for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {\n- const char *name = AudiodevDriver_str(i);\n- AudioBackendClass *be = audio_be_class_by_name(name);\n-\n- if (be) {\n- printf(\"%s\\n\", name);\n- }\n- }\n-}\n-\n-void audio_parse_option(const char *opt)\n-{\n- Audiodev *dev = NULL;\n-\n- if (is_help_option(opt)) {\n- audio_help();\n- exit(EXIT_SUCCESS);\n- }\n- Visitor *v = qobject_input_visitor_new_str(opt, \"driver\", &error_fatal);\n- visit_type_Audiodev(v, NULL, &dev, &error_fatal);\n- visit_free(v);\n-\n- audio_add_audiodev(dev);\n-}\n-\n void audio_add_audiodev(Audiodev *dev)\n {\n AudiodevListEntry *e;\n@@ -2190,65 +439,6 @@ void audio_init_audiodevs(void)\n }\n }\n \n-audsettings audiodev_to_audsettings(AudiodevPerDirectionOptions *pdo)\n-{\n- return (audsettings) {\n- .freq = pdo->frequency,\n- .nchannels = pdo->channels,\n- .fmt = pdo->format,\n- .endianness = HOST_BIG_ENDIAN,\n- };\n-}\n-\n-int audioformat_bytes_per_sample(AudioFormat fmt)\n-{\n- switch (fmt) {\n- case AUDIO_FORMAT_U8:\n- case AUDIO_FORMAT_S8:\n- return 1;\n-\n- case AUDIO_FORMAT_U16:\n- case AUDIO_FORMAT_S16:\n- return 2;\n-\n- case AUDIO_FORMAT_U32:\n- case AUDIO_FORMAT_S32:\n- case AUDIO_FORMAT_F32:\n- return 4;\n-\n- case AUDIO_FORMAT__MAX:\n- ;\n- }\n- abort();\n-}\n-\n-\n-/* frames = freq * usec / 1e6 */\n-int audio_buffer_frames(AudiodevPerDirectionOptions *pdo,\n- audsettings *as, int def_usecs)\n-{\n- uint64_t usecs = pdo->has_buffer_length ? pdo->buffer_length : def_usecs;\n- return (as->freq * usecs + 500000) / 1000000;\n-}\n-\n-/* samples = channels * frames = channels * freq * usec / 1e6 */\n-int audio_buffer_samples(AudiodevPerDirectionOptions *pdo,\n- audsettings *as, int def_usecs)\n-{\n- return as->nchannels * audio_buffer_frames(pdo, as, def_usecs);\n-}\n-\n-/*\n- * bytes = bytes_per_sample * samples =\n- * bytes_per_sample * channels * freq * usec / 1e6\n- */\n-int audio_buffer_bytes(AudiodevPerDirectionOptions *pdo,\n- audsettings *as, int def_usecs)\n-{\n- return audio_buffer_samples(pdo, as, def_usecs) *\n- audioformat_bytes_per_sample(as->fmt);\n-}\n-\n AudioBackend *audio_be_by_name(const char *name, Error **errp)\n {\n Object *obj = object_resolve_path_component(get_audiodevs_root(), name);\n@@ -2269,51 +459,6 @@ const char *audio_application_name(void)\n return vm_name ? vm_name : \"qemu\";\n }\n \n-void audio_rate_start(RateCtl *rate)\n-{\n- memset(rate, 0, sizeof(RateCtl));\n- rate->start_ticks = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n-}\n-\n-size_t audio_rate_peek_bytes(RateCtl *rate, struct audio_pcm_info *info)\n-{\n- int64_t now;\n- int64_t ticks;\n- int64_t bytes;\n- int64_t frames;\n-\n- now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);\n- ticks = now - rate->start_ticks;\n- bytes = muldiv64(ticks, info->bytes_per_second, NANOSECONDS_PER_SECOND);\n- frames = (bytes - rate->bytes_sent) / info->bytes_per_frame;\n- rate->peeked_frames = frames;\n-\n- return frames < 0 ? 0 : frames * info->bytes_per_frame;\n-}\n-\n-void audio_rate_add_bytes(RateCtl *rate, size_t bytes_used)\n-{\n- if (rate->peeked_frames < 0 || rate->peeked_frames > 65536) {\n- AUD_log(NULL, \"Resetting rate control (%\" PRId64 \" frames)\\n\",\n- rate->peeked_frames);\n- audio_rate_start(rate);\n- }\n-\n- rate->bytes_sent += bytes_used;\n-}\n-\n-size_t audio_rate_get_bytes(RateCtl *rate, struct audio_pcm_info *info,\n- size_t bytes_avail)\n-{\n- size_t bytes;\n-\n- bytes = audio_rate_peek_bytes(rate, info);\n- bytes = MIN(bytes, bytes_avail);\n- audio_rate_add_bytes(rate, bytes);\n-\n- return bytes;\n-}\n-\n AudiodevList *qmp_query_audiodevs(Error **errp)\n {\n AudiodevList *ret = NULL;\n@@ -2323,17 +468,3 @@ AudiodevList *qmp_query_audiodevs(Error **errp)\n }\n return ret;\n }\n-\n-static const TypeInfo audio_types[] = {\n- {\n- .name = TYPE_AUDIO_MIXENG_BACKEND,\n- .parent = TYPE_AUDIO_BACKEND,\n- .instance_size = sizeof(AudioMixengBackend),\n- .instance_init = audio_mixeng_backend_init,\n- .instance_finalize = audio_mixeng_backend_finalize,\n- .class_size = sizeof(AudioMixengBackendClass),\n- .class_init = audio_mixeng_backend_class_init,\n- }\n-};\n-\n-DEFINE_TYPES(audio_types)\ndiff --git a/audio/meson.build b/audio/meson.build\nindex 17f58369c20..0e33b6f9836 100644\n--- a/audio/meson.build\n+++ b/audio/meson.build\n@@ -2,6 +2,7 @@ audio_ss = ss.source_set()\n audio_ss.add(files(\n 'audio.c',\n 'audio-be.c',\n+ 'audio-mixeng-be.c',\n 'mixeng.c',\n 'noaudio.c',\n 'wavaudio.c',\n", "prefixes": [ "40/85" ] }