| /* 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; | |
| } |