Allow to build the audio/ base classes without the resampling/mixing/queuing code. Reviewed-by: Mark Cave-Ayland <mark.caveayland@nutanix.com> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
471 lines
12 KiB
C
471 lines
12 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/audio.h"
|
|
#include "qemu/help_option.h"
|
|
#include "qapi/clone-visitor.h"
|
|
#include "qapi/qobject-input-visitor.h"
|
|
#include "qapi/qapi-visit-audio.h"
|
|
#include "qapi/qapi-commands-audio.h"
|
|
#include "qobject/qdict.h"
|
|
#include "system/system.h"
|
|
|
|
/* Order of CONFIG_AUDIO_DRIVERS is import.
|
|
The 1st one is the one used by default, that is the reason
|
|
that we generate the list.
|
|
*/
|
|
const char *audio_prio_list[] = {
|
|
#ifdef CONFIG_GIO
|
|
"dbus",
|
|
#endif
|
|
"spice",
|
|
CONFIG_AUDIO_DRIVERS
|
|
"none",
|
|
NULL
|
|
};
|
|
|
|
typedef struct AudiodevListEntry {
|
|
Audiodev *dev;
|
|
QSIMPLEQ_ENTRY(AudiodevListEntry) next;
|
|
} AudiodevListEntry;
|
|
|
|
typedef QSIMPLEQ_HEAD(, AudiodevListEntry) AudiodevListHead;
|
|
|
|
static AudiodevListHead audiodevs =
|
|
QSIMPLEQ_HEAD_INITIALIZER(audiodevs);
|
|
static AudiodevListHead default_audiodevs =
|
|
QSIMPLEQ_HEAD_INITIALIZER(default_audiodevs);
|
|
|
|
static AudioBackendClass *audio_be_class_by_name(const char *name)
|
|
{
|
|
g_autofree char *tname = g_strconcat("audio-", name, NULL);
|
|
ObjectClass *oc = module_object_class_by_name(tname);
|
|
|
|
if (!oc || !object_class_dynamic_cast(oc, TYPE_AUDIO_BACKEND)) {
|
|
return NULL;
|
|
}
|
|
|
|
return AUDIO_BACKEND_CLASS(oc);
|
|
}
|
|
|
|
static AudioBackend *default_audio_be;
|
|
|
|
static Object *get_audiodevs_root(void)
|
|
{
|
|
return object_get_container("audiodevs");
|
|
}
|
|
|
|
void audio_cleanup(void)
|
|
{
|
|
default_audio_be = NULL;
|
|
|
|
object_unparent(get_audiodevs_root());
|
|
}
|
|
|
|
void audio_create_default_audiodevs(void)
|
|
{
|
|
for (int i = 0; audio_prio_list[i]; i++) {
|
|
if (audio_be_class_by_name(audio_prio_list[i]) != NULL) {
|
|
QDict *dict = qdict_new();
|
|
Audiodev *dev = NULL;
|
|
Visitor *v;
|
|
|
|
qdict_put_str(dict, "driver", audio_prio_list[i]);
|
|
qdict_put_str(dict, "id", "#default");
|
|
|
|
v = qobject_input_visitor_new_keyval(QOBJECT(dict));
|
|
qobject_unref(dict);
|
|
visit_type_Audiodev(v, NULL, &dev, &error_fatal);
|
|
visit_free(v);
|
|
|
|
audio_add_default_audiodev(dev, &error_abort);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* if we have dev, this function was called because of an -audiodev argument =>
|
|
* initialize a new state with it
|
|
* if dev == NULL => legacy implicit initialization, return the already created
|
|
* state or create a new one
|
|
*/
|
|
static AudioBackend *audio_init(Audiodev *dev, Error **errp)
|
|
{
|
|
AudioBackend *be;
|
|
|
|
if (dev) {
|
|
be = audio_be_new(dev, errp);
|
|
if (!be) {
|
|
return NULL;
|
|
}
|
|
} else {
|
|
assert(!default_audio_be);
|
|
for (;;) {
|
|
AudiodevListEntry *e = QSIMPLEQ_FIRST(&default_audiodevs);
|
|
if (!e) {
|
|
error_setg(errp, "no default audio driver available");
|
|
return NULL;
|
|
}
|
|
QSIMPLEQ_REMOVE_HEAD(&default_audiodevs, next);
|
|
be = audio_be_new(e->dev, NULL);
|
|
g_free(e);
|
|
if (be) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!object_property_try_add_child(get_audiodevs_root(),
|
|
audio_be_get_id(be), OBJECT(be), errp)) {
|
|
object_unref(be);
|
|
return NULL;
|
|
}
|
|
object_unref(be);
|
|
return be;
|
|
}
|
|
|
|
AudioBackend *audio_get_default_audio_be(Error **errp)
|
|
{
|
|
if (!default_audio_be) {
|
|
default_audio_be = audio_init(NULL, errp);
|
|
if (!default_audio_be) {
|
|
if (!QSIMPLEQ_EMPTY(&audiodevs)) {
|
|
error_append_hint(errp, "Perhaps you wanted to use -audio or set audiodev=%s?\n",
|
|
QSIMPLEQ_FIRST(&audiodevs)->dev->id);
|
|
}
|
|
}
|
|
}
|
|
|
|
return default_audio_be;
|
|
}
|
|
|
|
void audio_help(void)
|
|
{
|
|
int i;
|
|
|
|
printf("Available audio drivers:\n");
|
|
|
|
for (i = 0; i < AUDIODEV_DRIVER__MAX; i++) {
|
|
const char *name = AudiodevDriver_str(i);
|
|
AudioBackendClass *be = audio_be_class_by_name(name);
|
|
|
|
if (be) {
|
|
printf("%s\n", name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void audio_parse_option(const char *opt)
|
|
{
|
|
Audiodev *dev = NULL;
|
|
|
|
if (is_help_option(opt)) {
|
|
audio_help();
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
Visitor *v = qobject_input_visitor_new_str(opt, "driver", &error_fatal);
|
|
visit_type_Audiodev(v, NULL, &dev, &error_fatal);
|
|
visit_free(v);
|
|
|
|
audio_add_audiodev(dev);
|
|
}
|
|
|
|
static void audio_create_pdos(Audiodev *dev)
|
|
{
|
|
switch (dev->driver) {
|
|
#define CASE(DRIVER, driver, pdo_name) \
|
|
case AUDIODEV_DRIVER_##DRIVER: \
|
|
if (!dev->u.driver.in) { \
|
|
dev->u.driver.in = g_malloc0( \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
} \
|
|
if (!dev->u.driver.out) { \
|
|
dev->u.driver.out = g_malloc0( \
|
|
sizeof(Audiodev##pdo_name##PerDirectionOptions)); \
|
|
} \
|
|
break
|
|
|
|
CASE(NONE, none, );
|
|
#ifdef CONFIG_AUDIO_ALSA
|
|
CASE(ALSA, alsa, Alsa);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_COREAUDIO
|
|
CASE(COREAUDIO, coreaudio, Coreaudio);
|
|
#endif
|
|
#ifdef CONFIG_DBUS_DISPLAY
|
|
CASE(DBUS, dbus, );
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_DSOUND
|
|
CASE(DSOUND, dsound, );
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_JACK
|
|
CASE(JACK, jack, Jack);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_OSS
|
|
CASE(OSS, oss, Oss);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PA
|
|
CASE(PA, pa, Pa);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PIPEWIRE
|
|
CASE(PIPEWIRE, pipewire, Pipewire);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SDL
|
|
CASE(SDL, sdl, Sdl);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SNDIO
|
|
CASE(SNDIO, sndio, );
|
|
#endif
|
|
#ifdef CONFIG_SPICE
|
|
CASE(SPICE, spice, );
|
|
#endif
|
|
CASE(WAV, wav, );
|
|
|
|
case AUDIODEV_DRIVER__MAX:
|
|
abort();
|
|
};
|
|
}
|
|
|
|
static void audio_validate_per_direction_opts(
|
|
AudiodevPerDirectionOptions *pdo, Error **errp)
|
|
{
|
|
if (!pdo->has_mixing_engine) {
|
|
pdo->has_mixing_engine = true;
|
|
pdo->mixing_engine = true;
|
|
}
|
|
if (!pdo->has_fixed_settings) {
|
|
pdo->has_fixed_settings = true;
|
|
pdo->fixed_settings = pdo->mixing_engine;
|
|
}
|
|
if (!pdo->fixed_settings &&
|
|
(pdo->has_frequency || pdo->has_channels || pdo->has_format)) {
|
|
error_setg(errp,
|
|
"You can't use frequency, channels or format with fixed-settings=off");
|
|
return;
|
|
}
|
|
if (!pdo->mixing_engine && pdo->fixed_settings) {
|
|
error_setg(errp, "You can't use fixed-settings without mixeng");
|
|
return;
|
|
}
|
|
|
|
if (!pdo->has_frequency) {
|
|
pdo->has_frequency = true;
|
|
pdo->frequency = 44100;
|
|
}
|
|
if (!pdo->has_channels) {
|
|
pdo->has_channels = true;
|
|
pdo->channels = 2;
|
|
}
|
|
if (!pdo->has_voices) {
|
|
pdo->has_voices = true;
|
|
pdo->voices = pdo->mixing_engine ? 1 : INT_MAX;
|
|
}
|
|
if (!pdo->has_format) {
|
|
pdo->has_format = true;
|
|
pdo->format = AUDIO_FORMAT_S16;
|
|
}
|
|
}
|
|
|
|
static AudiodevPerDirectionOptions *audio_get_pdo_out(Audiodev *dev)
|
|
{
|
|
switch (dev->driver) {
|
|
case AUDIODEV_DRIVER_NONE:
|
|
return dev->u.none.out;
|
|
#ifdef CONFIG_AUDIO_ALSA
|
|
case AUDIODEV_DRIVER_ALSA:
|
|
return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_COREAUDIO
|
|
case AUDIODEV_DRIVER_COREAUDIO:
|
|
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
|
|
dev->u.coreaudio.out);
|
|
#endif
|
|
#ifdef CONFIG_DBUS_DISPLAY
|
|
case AUDIODEV_DRIVER_DBUS:
|
|
return dev->u.dbus.out;
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_DSOUND
|
|
case AUDIODEV_DRIVER_DSOUND:
|
|
return dev->u.dsound.out;
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_JACK
|
|
case AUDIODEV_DRIVER_JACK:
|
|
return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_OSS
|
|
case AUDIODEV_DRIVER_OSS:
|
|
return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PA
|
|
case AUDIODEV_DRIVER_PA:
|
|
return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PIPEWIRE
|
|
case AUDIODEV_DRIVER_PIPEWIRE:
|
|
return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SDL
|
|
case AUDIODEV_DRIVER_SDL:
|
|
return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.out);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SNDIO
|
|
case AUDIODEV_DRIVER_SNDIO:
|
|
return dev->u.sndio.out;
|
|
#endif
|
|
#ifdef CONFIG_SPICE
|
|
case AUDIODEV_DRIVER_SPICE:
|
|
return dev->u.spice.out;
|
|
#endif
|
|
case AUDIODEV_DRIVER_WAV:
|
|
return dev->u.wav.out;
|
|
|
|
case AUDIODEV_DRIVER__MAX:
|
|
break;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
static AudiodevPerDirectionOptions *audio_get_pdo_in(Audiodev *dev)
|
|
{
|
|
switch (dev->driver) {
|
|
case AUDIODEV_DRIVER_NONE:
|
|
return dev->u.none.in;
|
|
#ifdef CONFIG_AUDIO_ALSA
|
|
case AUDIODEV_DRIVER_ALSA:
|
|
return qapi_AudiodevAlsaPerDirectionOptions_base(dev->u.alsa.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_COREAUDIO
|
|
case AUDIODEV_DRIVER_COREAUDIO:
|
|
return qapi_AudiodevCoreaudioPerDirectionOptions_base(
|
|
dev->u.coreaudio.in);
|
|
#endif
|
|
#ifdef CONFIG_DBUS_DISPLAY
|
|
case AUDIODEV_DRIVER_DBUS:
|
|
return dev->u.dbus.in;
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_DSOUND
|
|
case AUDIODEV_DRIVER_DSOUND:
|
|
return dev->u.dsound.in;
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_JACK
|
|
case AUDIODEV_DRIVER_JACK:
|
|
return qapi_AudiodevJackPerDirectionOptions_base(dev->u.jack.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_OSS
|
|
case AUDIODEV_DRIVER_OSS:
|
|
return qapi_AudiodevOssPerDirectionOptions_base(dev->u.oss.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PA
|
|
case AUDIODEV_DRIVER_PA:
|
|
return qapi_AudiodevPaPerDirectionOptions_base(dev->u.pa.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_PIPEWIRE
|
|
case AUDIODEV_DRIVER_PIPEWIRE:
|
|
return qapi_AudiodevPipewirePerDirectionOptions_base(dev->u.pipewire.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SDL
|
|
case AUDIODEV_DRIVER_SDL:
|
|
return qapi_AudiodevSdlPerDirectionOptions_base(dev->u.sdl.in);
|
|
#endif
|
|
#ifdef CONFIG_AUDIO_SNDIO
|
|
case AUDIODEV_DRIVER_SNDIO:
|
|
return dev->u.sndio.in;
|
|
#endif
|
|
#ifdef CONFIG_SPICE
|
|
case AUDIODEV_DRIVER_SPICE:
|
|
return dev->u.spice.in;
|
|
#endif
|
|
case AUDIODEV_DRIVER_WAV:
|
|
return dev->u.wav.in;
|
|
|
|
case AUDIODEV_DRIVER__MAX:
|
|
break;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
static void audio_validate_opts(Audiodev *dev, Error **errp)
|
|
{
|
|
Error *err = NULL;
|
|
|
|
audio_create_pdos(dev);
|
|
|
|
audio_validate_per_direction_opts(audio_get_pdo_in(dev), &err);
|
|
if (err) {
|
|
error_propagate(errp, err);
|
|
return;
|
|
}
|
|
|
|
audio_validate_per_direction_opts(audio_get_pdo_out(dev), &err);
|
|
if (err) {
|
|
error_propagate(errp, err);
|
|
return;
|
|
}
|
|
|
|
if (!dev->has_timer_period) {
|
|
dev->has_timer_period = true;
|
|
dev->timer_period = 10000; /* 100Hz -> 10ms */
|
|
}
|
|
}
|
|
|
|
void audio_add_audiodev(Audiodev *dev)
|
|
{
|
|
AudiodevListEntry *e;
|
|
|
|
audio_validate_opts(dev, &error_fatal);
|
|
|
|
e = g_new0(AudiodevListEntry, 1);
|
|
e->dev = dev;
|
|
QSIMPLEQ_INSERT_TAIL(&audiodevs, e, next);
|
|
}
|
|
|
|
void audio_add_default_audiodev(Audiodev *dev, Error **errp)
|
|
{
|
|
AudiodevListEntry *e;
|
|
|
|
audio_validate_opts(dev, errp);
|
|
|
|
e = g_new0(AudiodevListEntry, 1);
|
|
e->dev = dev;
|
|
QSIMPLEQ_INSERT_TAIL(&default_audiodevs, e, next);
|
|
}
|
|
|
|
void audio_init_audiodevs(void)
|
|
{
|
|
AudiodevListEntry *e;
|
|
|
|
QSIMPLEQ_FOREACH(e, &audiodevs, next) {
|
|
audio_init(QAPI_CLONE(Audiodev, e->dev), &error_fatal);
|
|
}
|
|
}
|
|
|
|
AudioBackend *audio_be_by_name(const char *name, Error **errp)
|
|
{
|
|
Object *obj = object_resolve_path_component(get_audiodevs_root(), name);
|
|
|
|
if (!obj) {
|
|
error_setg(errp, "audiodev '%s' not found", name);
|
|
return NULL;
|
|
} else {
|
|
return AUDIO_BACKEND(obj);
|
|
}
|
|
}
|
|
|
|
const char *audio_application_name(void)
|
|
{
|
|
const char *vm_name;
|
|
|
|
vm_name = qemu_get_vm_name();
|
|
return vm_name ? vm_name : "qemu";
|
|
}
|
|
|
|
AudiodevList *qmp_query_audiodevs(Error **errp)
|
|
{
|
|
AudiodevList *ret = NULL;
|
|
AudiodevListEntry *e;
|
|
QSIMPLEQ_FOREACH(e, &audiodevs, next) {
|
|
QAPI_LIST_PREPEND(ret, QAPI_CLONE(Audiodev, e->dev));
|
|
}
|
|
return ret;
|
|
}
|