diff options
Diffstat (limited to 'audio/out/ao_pipewire.c')
-rw-r--r-- | audio/out/ao_pipewire.c | 425 |
1 files changed, 299 insertions, 126 deletions
diff --git a/audio/out/ao_pipewire.c b/audio/out/ao_pipewire.c index b9144f9921..7dbbe7004e 100644 --- a/audio/out/ao_pipewire.c +++ b/audio/out/ao_pipewire.c @@ -24,41 +24,64 @@ #include <pipewire/global.h> #include <spa/param/audio/format-utils.h> #include <spa/param/props.h> +#include <spa/utils/result.h> #include <math.h> +#include "common/common.h" #include "common/msg.h" #include "options/m_config.h" #include "options/m_option.h" #include "ao.h" #include "audio/format.h" -#include "config.h" -#include "generated/version.h" #include "internal.h" #include "osdep/timer.h" -// Added in Pipewire 0.3.33 -// remove the fallback when we require a newer version -#ifndef PW_KEY_NODE_RATE -#define PW_KEY_NODE_RATE "node.rate" -#endif - #if !PW_CHECK_VERSION(0, 3, 50) static inline int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size) { - return pw_stream_get_time(stream, time); + return pw_stream_get_time(stream, time); +} +#endif + +#if !PW_CHECK_VERSION(0, 3, 57) +// Earlier versions segfault on zeroed hooks +#define spa_hook_remove(hook) if ((hook)->link.prev) spa_hook_remove(hook) +#endif + +#if !PW_CHECK_VERSION(1, 0, 4) +static uint64_t pw_stream_get_nsec(struct pw_stream *stream) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return SPA_TIMESPEC_TO_NSEC(&ts); } #endif +enum init_state { + INIT_STATE_NONE, + INIT_STATE_SUCCESS, + INIT_STATE_ERROR, +}; + +enum { + VOLUME_MODE_CHANNEL, + VOLUME_MODE_GLOBAL, +}; + struct priv { struct pw_thread_loop *loop; struct pw_stream *stream; struct pw_core *core; struct spa_hook stream_listener; + struct spa_hook core_listener; + enum init_state init_state; bool muted; - float volume[2]; + float volume; struct { int buffer_msec; + char *remote; + int volume_mode; } options; struct { @@ -73,7 +96,7 @@ struct id_list { struct spa_list node; }; -static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format) +static enum spa_audio_format af_fmt_to_pw(enum af_format format) { switch (format) { case AF_FORMAT_U8: return SPA_AUDIO_FORMAT_U8; @@ -86,9 +109,21 @@ static enum spa_audio_format af_fmt_to_pw(struct ao *ao, enum af_format format) case AF_FORMAT_S32P: return SPA_AUDIO_FORMAT_S32P; case AF_FORMAT_FLOATP: return SPA_AUDIO_FORMAT_F32P; case AF_FORMAT_DOUBLEP: return SPA_AUDIO_FORMAT_F64P; - default: - MP_WARN(ao, "Unhandled format %d\n", format); - return SPA_AUDIO_FORMAT_UNKNOWN; + default: return SPA_AUDIO_FORMAT_UNKNOWN; + } +} + +static enum spa_audio_iec958_codec af_fmt_to_codec(enum af_format format) +{ + switch (format) { + case AF_FORMAT_S_AAC: return SPA_AUDIO_IEC958_CODEC_MPEG2_AAC; + case AF_FORMAT_S_AC3: return SPA_AUDIO_IEC958_CODEC_AC3; + case AF_FORMAT_S_DTS: return SPA_AUDIO_IEC958_CODEC_DTS; + case AF_FORMAT_S_DTSHD: return SPA_AUDIO_IEC958_CODEC_DTSHD; + case AF_FORMAT_S_EAC3: return SPA_AUDIO_IEC958_CODEC_EAC3; + case AF_FORMAT_S_MP3: return SPA_AUDIO_IEC958_CODEC_MPEG; + case AF_FORMAT_S_TRUEHD: return SPA_AUDIO_IEC958_CODEC_TRUEHD; + default: return SPA_AUDIO_IEC958_CODEC_UNKNOWN; } } @@ -120,6 +155,11 @@ static enum spa_audio_channel mp_speaker_id_to_spa(struct ao *ao, enum mp_speake case MP_SPEAKER_ID_SDL: return SPA_AUDIO_CHANNEL_SL; case MP_SPEAKER_ID_SDR: return SPA_AUDIO_CHANNEL_SL; case MP_SPEAKER_ID_LFE2: return SPA_AUDIO_CHANNEL_LFE2; + case MP_SPEAKER_ID_TSL: return SPA_AUDIO_CHANNEL_TSL; + case MP_SPEAKER_ID_TSR: return SPA_AUDIO_CHANNEL_TSR; + case MP_SPEAKER_ID_BFC: return SPA_AUDIO_CHANNEL_BC; + case MP_SPEAKER_ID_BFL: return SPA_AUDIO_CHANNEL_BLC; + case MP_SPEAKER_ID_BFR: return SPA_AUDIO_CHANNEL_BRC; case MP_SPEAKER_ID_NA: return SPA_AUDIO_CHANNEL_NA; default: MP_WARN(ao, "Unhandled channel %d\n", mp_speaker_id); @@ -127,7 +167,6 @@ static enum spa_audio_channel mp_speaker_id_to_spa(struct ao *ao, enum mp_speake }; } - static void on_process(void *userdata) { struct ao *ao = userdata; @@ -137,14 +176,13 @@ static void on_process(void *userdata) void *data[MP_NUM_CHANNELS]; if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) { - MP_WARN(ao, "out of buffers: %s\n", strerror(errno)); + MP_WARN(ao, "out of buffers: %s\n", mp_strerror(errno)); return; } struct spa_buffer *buf = b->buffer; - int bytes_per_channel = buf->datas[0].maxsize / ao->channels.num; - int nframes = bytes_per_channel / ao->sstride; + int nframes = buf->datas[0].maxsize / ao->sstride; #if PW_CHECK_VERSION(0, 3, 49) if (b->requested != 0) nframes = MPMIN(b->requested, nframes); @@ -159,12 +197,16 @@ static void on_process(void *userdata) if (time.rate.num == 0) time.rate.num = 1; - int64_t end_time = mp_time_us(); - /* time.queued is always going to be 0, so we don't need to care */ - end_time += (nframes * 1e6 / ao->samplerate) + - ((float) time.delay * SPA_USEC_PER_SEC * time.rate.num / time.rate.denom); + int64_t end_time = mp_time_ns(); + end_time += MP_TIME_S_TO_NS(nframes) / ao->samplerate; + end_time += MP_TIME_S_TO_NS(time.delay) * time.rate.num / time.rate.denom; + end_time += MP_TIME_S_TO_NS(time.queued) / ao->samplerate; +#if PW_CHECK_VERSION(0, 3, 50) + end_time += MP_TIME_S_TO_NS(time.buffered) / ao->samplerate; +#endif + end_time -= pw_stream_get_nsec(p->stream) - time.now; - int samples = ao_read_data(ao, data, nframes, end_time); + int samples = ao_read_data_nonblocking(ao, data, nframes, end_time); b->size = samples; for (int i = 0; i < buf->n_datas; i++) { @@ -174,6 +216,8 @@ static void on_process(void *userdata) } pw_stream_queue_buffer(p->stream, b); + + MP_TRACE(ao, "queued %d of %d samples\n", samples, nframes); } static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) @@ -184,15 +228,24 @@ static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod * uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + /* We want to know when our node is linked. + * As there is no proper callback for this we use the Latency param for this + */ + if (id == SPA_PARAM_Latency) { + p->init_state = INIT_STATE_SUCCESS; + pw_thread_loop_signal(p->loop, false); + } + if (param == NULL || id != SPA_PARAM_Format) return; - int buffer_size = ao->device_buffer * af_fmt_to_bytes(ao->format) * ao->channels.num; + int buffer_size = ao->device_buffer * ao->sstride; params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(ao->num_planes), - SPA_PARAM_BUFFERS_size, SPA_POD_Int(buffer_size), + SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( + buffer_size, 0, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(ao->sstride)); if (!params[0]) { MP_ERR(ao, "Could not build parameter pod\n"); @@ -208,10 +261,19 @@ static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod * static void on_state_changed(void *userdata, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct ao *ao = userdata; - MP_DBG(ao, "Stream state changed: old_state=%d state=%d error=%s\n", old, state, error); + struct priv *p = ao->priv; + MP_DBG(ao, "Stream state changed: old_state=%s state=%s error=%s\n", + pw_stream_state_as_string(old), pw_stream_state_as_string(state), error); if (state == PW_STREAM_STATE_ERROR) { MP_WARN(ao, "Stream in error state, trying to reload...\n"); + p->init_state = INIT_STATE_ERROR; + pw_thread_loop_signal(p->loop, false); + ao_request_reload(ao); + } + + if (state == PW_STREAM_STATE_UNCONNECTED && old != PW_STREAM_STATE_UNCONNECTED) { + MP_WARN(ao, "Stream disconnected, trying to reload...\n"); ao_request_reload(ao); } } @@ -246,14 +308,16 @@ static void on_control_info(void *userdata, uint32_t id, p->muted = control->values[0] >= 0.5; break; case SPA_PROP_channelVolumes: - if (control->n_values == 2) { - p->volume[0] = control->values[0]; - p->volume[1] = control->values[1]; - } else if (control->n_values > 0) { - float volume = volume_avg(control->values, control->n_values); - p->volume[0] = volume; - p->volume[1] = volume; - } + if (p->options.volume_mode != VOLUME_MODE_CHANNEL) + break; + if (control->n_values > 0) + p->volume = volume_avg(control->values, control->n_values); + break; + case SPA_PROP_volume: + if (p->options.volume_mode != VOLUME_MODE_GLOBAL) + break; + if (control->n_values > 0) + p->volume = control->values[0]; break; } } @@ -271,12 +335,13 @@ static void uninit(struct ao *ao) struct priv *p = ao->priv; if (p->loop) pw_thread_loop_stop(p->loop); + spa_hook_remove(&p->stream_listener); + spa_zero(p->stream_listener); if (p->stream) pw_stream_destroy(p->stream); p->stream = NULL; - if (p->core) { + if (p->core) pw_context_destroy(pw_core_get_context(p->core)); - } p->core = NULL; if (p->loop) pw_thread_loop_destroy(p->loop); @@ -319,6 +384,11 @@ static void for_each_sink_registry_event_global(void *data, uint32_t id, } +struct for_each_done_ctx { + struct pw_thread_loop *loop; + bool done; +}; + static const struct pw_registry_events for_each_sink_registry_events = { .version = PW_VERSION_REGISTRY_EVENTS, .global = for_each_sink_registry_event_global, @@ -326,8 +396,9 @@ static const struct pw_registry_events for_each_sink_registry_events = { static void for_each_sink_done(void *data, uint32_t it, int seq) { - struct pw_thread_loop *loop = data; - pw_thread_loop_signal(loop, false); + struct for_each_done_ctx *ctx = data; + ctx->done = true; + pw_thread_loop_signal(ctx->loop, false); } static const struct pw_core_events for_each_sink_core_events = { @@ -341,12 +412,16 @@ static int for_each_sink(struct ao *ao, void (cb) (struct ao *ao, uint32_t id, struct priv *priv = ao->priv; struct pw_registry *registry; struct spa_hook core_listener; + struct for_each_done_ctx done_ctx = { + .loop = priv->loop, + .done = false, + }; int ret = -1; pw_thread_loop_lock(priv->loop); spa_zero(core_listener); - if (pw_core_add_listener(priv->core, &core_listener, &for_each_sink_core_events, priv->loop) < 0) + if (pw_core_add_listener(priv->core, &core_listener, &for_each_sink_core_events, &done_ctx) < 0) goto unlock_loop; registry = pw_core_get_registry(priv->core, PW_VERSION_REGISTRY, 0); @@ -365,7 +440,8 @@ static int for_each_sink(struct ao *ao, void (cb) (struct ao *ao, uint32_t id, if (pw_registry_add_listener(registry, ®istry_listener, &for_each_sink_registry_events, &revents_ctx) < 0) goto destroy_registry; - pw_thread_loop_wait(priv->loop); + while (!done_ctx.done) + pw_thread_loop_wait(priv->loop); spa_hook_remove(®istry_listener); @@ -383,16 +459,56 @@ unlock_loop: return ret; } +static void have_sink(struct ao *ao, uint32_t id, const struct spa_dict *props, void *ctx) +{ + bool *b = ctx; + *b = true; +} + +static bool session_has_sinks(struct ao *ao) +{ + bool b = false; + + if (for_each_sink(ao, have_sink, &b) < 0) + MP_WARN(ao, "Could not list devices, sink detection may be wrong\n"); + + return b; +} + +static void on_error(void *data, uint32_t id, int seq, int res, const char *message) +{ + struct ao *ao = data; + + MP_WARN(ao, "Error during playback: %s, %s\n", spa_strerror(res), message); +} + +static void on_core_info(void *data, const struct pw_core_info *info) +{ + struct ao *ao = data; + + MP_VERBOSE(ao, "Core user: %s\n", info->user_name); + MP_VERBOSE(ao, "Core host: %s\n", info->host_name); + MP_VERBOSE(ao, "Core version: %s\n", info->version); + MP_VERBOSE(ao, "Core name: %s\n", info->name); +} + +static const struct pw_core_events core_events = { + .version = PW_VERSION_CORE_EVENTS, + .error = on_error, + .info = on_core_info, +}; + static int pipewire_init_boilerplate(struct ao *ao) { struct priv *p = ao->priv; struct pw_context *context; - int ret; pw_init(NULL, NULL); + MP_VERBOSE(ao, "Headers version: %s\n", pw_get_headers_version()); + MP_VERBOSE(ao, "Library version: %s\n", pw_get_library_version()); - p->loop = pw_thread_loop_new("ao-pipewire", NULL); + p->loop = pw_thread_loop_new("mpv/ao/pipewire", NULL); if (p->loop == NULL) return -1; @@ -401,25 +517,62 @@ static int pipewire_init_boilerplate(struct ao *ao) if (pw_thread_loop_start(p->loop) < 0) goto error; - context = pw_context_new(pw_thread_loop_get_loop(p->loop), NULL, 0); + context = pw_context_new( + pw_thread_loop_get_loop(p->loop), + pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", NULL), + 0); if (!context) goto error; - p->core = pw_context_connect(context, NULL, 0); - if (!p->core) + p->core = pw_context_connect( + context, + pw_properties_new(PW_KEY_REMOTE_NAME, p->options.remote, NULL), + 0); + if (!p->core) { + MP_MSG(ao, ao->probing ? MSGL_V : MSGL_ERR, + "Could not connect to context '%s': %s\n", + p->options.remote, mp_strerror(errno)); + pw_context_destroy(context); goto error; + } - ret = 0; + if (pw_core_add_listener(p->core, &p->core_listener, &core_events, ao) < 0) + goto error; -out: pw_thread_loop_unlock(p->loop); - return ret; + + if (!session_has_sinks(ao)) { + MP_VERBOSE(ao, "PipeWire does not have any audio sinks, skipping\n"); + return -1; + } + + return 0; error: - ret = -1; - goto out; + pw_thread_loop_unlock(p->loop); + return -1; } +static void wait_for_init_done(struct ao *ao) +{ + struct priv *p = ao->priv; + struct timespec abstime; + int r; + + r = pw_thread_loop_get_time(p->loop, &abstime, 50 * SPA_NSEC_PER_MSEC); + if (r < 0) { + MP_WARN(ao, "Could not get timeout for initialization: %s\n", spa_strerror(r)); + return; + } + + while (p->init_state == INIT_STATE_NONE) { + r = pw_thread_loop_timed_wait_full(p->loop, &abstime); + if (r < 0) { + MP_WARN(ao, "Could not wait for initialization: %s\n", spa_strerror(r)); + return; + } + } +} static int init(struct ao *ao) { @@ -430,6 +583,7 @@ static int init(struct ao *ao) struct pw_properties *props = pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", + PW_KEY_MEDIA_ROLE, ao->init_flags & AO_INIT_MEDIA_ROLE_MUSIC ? "Music" : "Movie", PW_KEY_NODE_NAME, ao->client_name, PW_KEY_NODE_DESCRIPTION, ao->client_name, PW_KEY_APP_NAME, ao->client_name, @@ -441,31 +595,51 @@ static int init(struct ao *ao) ); if (pipewire_init_boilerplate(ao) < 0) - goto error; + goto error_props; + + if (p->options.buffer_msec) { + ao->device_buffer = p->options.buffer_msec * ao->samplerate / 1000; - ao->device_buffer = p->options.buffer_msec * ao->samplerate / 1000; + pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", ao->device_buffer, ao->samplerate); + } - pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", ao->device_buffer, ao->samplerate); pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", ao->samplerate); - enum spa_audio_format spa_format = af_fmt_to_pw(ao, ao->format); - if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) { - ao->format = AF_FORMAT_FLOATP; - spa_format = SPA_AUDIO_FORMAT_F32P; - } + if (af_fmt_is_spdif(ao->format)) { + enum spa_audio_iec958_codec spa_codec = af_fmt_to_codec(ao->format); + if (spa_codec == SPA_AUDIO_IEC958_CODEC_UNKNOWN) { + MP_ERR(ao, "Unhandled codec %d\n", ao->format); + goto error_props; + } - struct spa_audio_info_raw audio_info = { - .format = spa_format, - .rate = ao->samplerate, - .channels = ao->channels.num, - }; + struct spa_audio_info_iec958 audio_info = { + .codec = spa_codec, + .rate = ao->samplerate, + }; - for (int i = 0; i < ao->channels.num; i++) - audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]); + params[0] = spa_format_audio_iec958_build(&b, SPA_PARAM_EnumFormat, &audio_info); + if (!params[0]) + goto error_props; + } else { + enum spa_audio_format spa_format = af_fmt_to_pw(ao->format); + if (spa_format == SPA_AUDIO_FORMAT_UNKNOWN) { + MP_ERR(ao, "Unhandled format %d\n", ao->format); + goto error_props; + } - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info); - if (!params[0]) - goto error; + struct spa_audio_info_raw audio_info = { + .format = spa_format, + .rate = ao->samplerate, + .channels = ao->channels.num, + }; + + for (int i = 0; i < ao->channels.num; i++) + audio_info.position[i] = mp_speaker_id_to_spa(ao, ao->channels.speaker[i]); + + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &audio_info); + if (!params[0]) + goto error_props; + } if (af_fmt_is_planar(ao->format)) { ao->num_planes = ao->channels.num; @@ -477,34 +651,39 @@ static int init(struct ao *ao) pw_thread_loop_lock(p->loop); - p->stream = pw_stream_new( - p->core, - "audio-src", - props); + p->stream = pw_stream_new(p->core, "audio-src", props); if (p->stream == NULL) { pw_thread_loop_unlock(p->loop); goto error; } - pw_stream_add_listener(p->stream, - &p->stream_listener, - &stream_events, ao); + pw_stream_add_listener(p->stream, &p->stream_listener, &stream_events, ao); + + enum pw_stream_flags flags = PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_INACTIVE | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS; + + if (ao->init_flags & AO_INIT_EXCLUSIVE) + flags |= PW_STREAM_FLAG_EXCLUSIVE; if (pw_stream_connect(p->stream, - PW_DIRECTION_OUTPUT, PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_INACTIVE | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - params, 1) < 0) { + PW_DIRECTION_OUTPUT, PW_ID_ANY, flags, params, 1) < 0) { pw_thread_loop_unlock(p->loop); goto error; } + wait_for_init_done(ao); + pw_thread_loop_unlock(p->loop); + if (p->init_state == INIT_STATE_ERROR) + goto error; + return 0; +error_props: + pw_properties_free(props); error: uninit(ao); return -1; @@ -527,6 +706,15 @@ static void start(struct ao *ao) pw_thread_loop_unlock(p->loop); } +static bool set_pause(struct ao *ao, bool paused) +{ + struct priv *p = ao->priv; + pw_thread_loop_lock(p->loop); + pw_stream_set_active(p->stream, !paused); + pw_thread_loop_unlock(p->loop); + return true; +} + #define CONTROL_RET(r) (!r ? CONTROL_OK : CONTROL_ERROR) static int control(struct ao *ao, enum aocontrol cmd, void *arg) @@ -535,9 +723,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) switch (cmd) { case AOCONTROL_GET_VOLUME: { - struct ao_control_vol *vol = arg; - vol->left = spa_volume_to_mp_volume(p->volume[0]); - vol->right = spa_volume_to_mp_volume(p->volume[1]); + float *vol = arg; + *vol = spa_volume_to_mp_volume(p->volume); return CONTROL_OK; } case AOCONTROL_GET_MUTE: { @@ -547,26 +734,27 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) } case AOCONTROL_SET_VOLUME: case AOCONTROL_SET_MUTE: - case AOCONTROL_UPDATE_STREAM_TITLE: - case AOCONTROL_UPDATE_MEDIA_ROLE: { + case AOCONTROL_UPDATE_STREAM_TITLE: { int ret; pw_thread_loop_lock(p->loop); switch (cmd) { case AOCONTROL_SET_VOLUME: { - struct ao_control_vol *vol = arg; + float *vol = arg; uint8_t n = ao->channels.num; - float values[MP_NUM_CHANNELS] = {0}; - if (n == 2) { - values[0] = mp_volume_to_spa_volume(vol->left); - values[1] = mp_volume_to_spa_volume(vol->right); - } else { + if (p->options.volume_mode == VOLUME_MODE_CHANNEL) { + float values[MP_NUM_CHANNELS] = {0}; for (int i = 0; i < n; i++) - values[i] = mp_volume_to_spa_volume(vol->left); + values[i] = mp_volume_to_spa_volume(*vol); + ret = CONTROL_RET(pw_stream_set_control( + p->stream, SPA_PROP_channelVolumes, n, values, 0)); + } else { + float value = mp_volume_to_spa_volume(*vol); + ret = CONTROL_RET(pw_stream_set_control( + p->stream, SPA_PROP_volume, 1, &value, 0)); } - ret = CONTROL_RET(pw_stream_set_control(p->stream, SPA_PROP_channelVolumes, n, values, 0)); break; - } + } case AOCONTROL_SET_MUTE: { bool *muted = arg; float value = *muted ? 1.f : 0.f; @@ -580,26 +768,6 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg) ret = CONTROL_RET(pw_stream_update_properties(p->stream, &SPA_DICT_INIT(items, MP_ARRAY_SIZE(items)))); break; } - case AOCONTROL_UPDATE_MEDIA_ROLE: { - enum aocontrol_media_role *role = arg; - struct spa_dict_item items[1]; - const char *role_str; - switch (*role) { - case AOCONTROL_MEDIA_ROLE_MOVIE: - role_str = "Movie"; - break; - case AOCONTROL_MEDIA_ROLE_MUSIC: - role_str = "Music"; - break; - default: - MP_WARN(ao, "Unknown media role %d\n", *role); - role_str = ""; - break; - } - items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_ROLE, role_str); - ret = CONTROL_RET(pw_stream_update_properties(p->stream, &SPA_DICT_INIT(items, MP_ARRAY_SIZE(items)))); - break; - } default: ret = CONTROL_NA; } @@ -635,7 +803,7 @@ static void hotplug_registry_global_cb(void *data, uint32_t id, return; pw_thread_loop_lock(priv->loop); - struct id_list *item = talloc_size(ao, sizeof(*item)); + struct id_list *item = talloc(ao, struct id_list); item->id = id; spa_list_init(&item->node); spa_list_append(&priv->hotplug.sinks, &item->node); @@ -658,10 +826,10 @@ static void hotplug_registry_global_remove_cb(void *data, uint32_t id) removed_sink = true; spa_list_remove(&e->node); talloc_free(e); - goto done; + break; } } -done: + pw_thread_loop_unlock(priv->loop); if (removed_sink) @@ -674,24 +842,22 @@ static const struct pw_registry_events hotplug_registry_events = { .global_remove = hotplug_registry_global_remove_cb, }; - static int hotplug_init(struct ao *ao) { struct priv *priv = ao->priv; int res = pipewire_init_boilerplate(ao); if (res) - return res; + goto error_no_unlock; pw_thread_loop_lock(priv->loop); - spa_memzero(&priv->hotplug, sizeof(priv->hotplug)); + spa_zero(priv->hotplug); spa_list_init(&priv->hotplug.sinks); priv->hotplug.registry = pw_core_get_registry(priv->core, PW_VERSION_REGISTRY, 0); - if (!priv->hotplug.registry) { + if (!priv->hotplug.registry) goto error; - } if (pw_registry_add_listener(priv->hotplug.registry, &priv->hotplug.registry_listener, &hotplug_registry_events, ao) < 0) { pw_proxy_destroy((struct pw_proxy *)priv->hotplug.registry); @@ -704,6 +870,7 @@ static int hotplug_init(struct ao *ao) error: pw_thread_loop_unlock(priv->loop); +error_no_unlock: uninit(ao); return -1; } @@ -739,7 +906,7 @@ const struct ao_driver audio_out_pipewire = { .uninit = uninit, .reset = reset, .start = start, - + .set_pause = set_pause, .control = control, .hotplug_init = hotplug_init, @@ -751,11 +918,17 @@ const struct ao_driver audio_out_pipewire = { { .loop = NULL, .stream = NULL, - .options.buffer_msec = 20, + .init_state = INIT_STATE_NONE, + .options.buffer_msec = 0, + .options.volume_mode = VOLUME_MODE_CHANNEL, }, .options_prefix = "pipewire", .options = (const struct m_option[]) { - {"buffer", OPT_INT(options.buffer_msec), M_RANGE(1, 2000)}, + {"buffer", OPT_CHOICE(options.buffer_msec, {"native", 0}), + M_RANGE(1, 2000)}, + {"remote", OPT_STRING(options.remote) }, + {"volume-mode", OPT_CHOICE(options.volume_mode, + {"channel", VOLUME_MODE_CHANNEL}, {"global", VOLUME_MODE_GLOBAL})}, {0} }, }; |