blob: 6f10ee470ee48cb610e3796d1c575df44724fc77 [file] [log] [blame]
/* 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;
}