From: Arun Raghavan <git@xxxxxxxxxxxxxxxx> To allow the API to be synchronous, we keep a copy of the current volume on the stream at all times. --- PROTOCOL | 16 ++++++++++++ configure.ac | 2 +- src/map-file | 1 + src/pulse/context.c | 2 ++ src/pulse/internal.h | 1 + src/pulse/stream.c | 55 +++++++++++++++++++++++++++++++++++++++++ src/pulse/stream.h | 7 ++++++ src/pulsecore/native-common.h | 5 ++++ src/pulsecore/protocol-native.c | 54 ++++++++++++++++++++++++++++++++++++++++ src/tests/api-test.c | 3 +++ 10 files changed, 145 insertions(+), 1 deletion(-) diff --git a/PROTOCOL b/PROTOCOL index 3c08fea..45e3c96 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -371,6 +371,22 @@ PA_COMMAND_DISABLE_SRBCHANNEL Tells the client to stop listening on the additional SHM ringbuffer channel. Acked by client by sending PA_COMMAND_DISABLE_SRBCHANNEL back. +## v31, implemented by >= 8.0 + +Added return values to PA_COMMAND_CREATE_PLAYBACK_STREAM and +PA_COMMAND_CREATE_RECORD_STREAM to provide the stream volume after creation. + + cvolume + +A couple of new server->client messages to allow clients to track stream volume +changes. + +PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED +PA_COMMAND_RECORD_STREAM_VOLUME_CHANGED + + index + cvolume + #### If you just changed the protocol, read this ## module-tunnel depends on the sink/source/sink-input/source-input protocol ## internals, so if you changed these, you might have broken module-tunnel. diff --git a/configure.ac b/configure.ac index 2dbf7f2..a4e3c21 100644 --- a/configure.ac +++ b/configure.ac @@ -40,7 +40,7 @@ AC_SUBST(PA_MINOR, pa_minor) AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor) AC_SUBST(PA_API_VERSION, 12) -AC_SUBST(PA_PROTOCOL_VERSION, 30) +AC_SUBST(PA_PROTOCOL_VERSION, 31) # The stable ABI for client applications, for the version info x:y:z # always will hold y=z diff --git a/src/map-file b/src/map-file index 93f7178..8e61f53 100644 --- a/src/map-file +++ b/src/map-file @@ -302,6 +302,7 @@ pa_stream_get_state; pa_stream_get_time; pa_stream_get_timing_info; pa_stream_get_underflow_index; +pa_stream_get_volume; pa_stream_is_corked; pa_stream_is_suspended; pa_stream_new; diff --git a/src/pulse/context.c b/src/pulse/context.c index 4f084e8..63a0fdb 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -90,6 +90,8 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_RECORD_BUFFER_ATTR_CHANGED] = pa_command_stream_buffer_attr, [PA_COMMAND_ENABLE_SRBCHANNEL] = pa_command_enable_srbchannel, [PA_COMMAND_DISABLE_SRBCHANNEL] = pa_command_disable_srbchannel, + [PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED] = pa_command_stream_volume_changed, + [PA_COMMAND_RECORD_STREAM_VOLUME_CHANGED] = pa_command_stream_volume_changed, }; static void context_free(pa_context *c); diff --git a/src/pulse/internal.h b/src/pulse/internal.h index 74b2b32..ac9d07e 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -254,6 +254,7 @@ void pa_command_stream_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, void pa_command_stream_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); void pa_command_client_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); void pa_command_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_volume_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t callback, void *userdata); void pa_operation_done(pa_operation *o); diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 0f5dd73..bc49cbb 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -914,6 +914,42 @@ finish: pa_context_unref(c); } +void pa_command_stream_volume_changed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t index; + pa_cvolume volume; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED || command == PA_COMMAND_RECORD_STREAM_VOLUME_CHANGED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 31) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &index) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_hashmap_get(command == PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED ? c->playback_streams : c->record_streams, + PA_UINT32_TO_PTR(index)))) + goto finish; + + s->volume = volume; + +finish: + pa_context_unref(c); +} + static void invalidate_indexes(pa_stream *s, bool r, bool w) { pa_assert(s); pa_assert(PA_REFCNT_VALUE(s) >= 1); @@ -1154,6 +1190,9 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, } } + if (s->context->version >= 31) + pa_tagstruct_get_cvolume(t, &s->volume); + if (!pa_tagstruct_eof(t)) { pa_context_fail(s->context, PA_ERR_PROTOCOL); goto finish; @@ -2968,3 +3007,19 @@ int pa_stream_set_volume(pa_stream *s, pa_cvolume *v, pa_stream_success_cb_t cb, return 0; } + +int pa_stream_get_volume(pa_stream *s, pa_cvolume *v) { + pa_assert(s); + pa_assert(v); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + PA_CHECK_VALIDITY(s->context, s->state != PA_STREAM_FAILED && s->state != PA_STREAM_TERMINATED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY || s->volume_set, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_INVALID); /* TODO: do we want to support this? */ + PA_CHECK_VALIDITY(s->context, s->context->version >= 31, PA_ERR_NOTSUPPORTED); + + *v = s->volume; + + return 0; +} diff --git a/src/pulse/stream.h b/src/pulse/stream.h index cc74629..b4e6ff9 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -828,6 +828,13 @@ uint32_t pa_stream_get_monitor_stream(pa_stream *s); * \since 9.0 */ int pa_stream_set_volume(pa_stream *s, pa_cvolume *v, pa_stream_success_cb_t cb, void *userdata); +/** Get the volume on the given stream. + * + * Returns 0 on success, negative error value on failure. + * + * \since 9.0 */ +int pa_stream_get_volume(pa_stream *s, pa_cvolume *v); + PA_C_DECL_END #endif diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h index dc62895..73aded1 100644 --- a/src/pulsecore/native-common.h +++ b/src/pulsecore/native-common.h @@ -179,6 +179,11 @@ enum { PA_COMMAND_ENABLE_SRBCHANNEL, PA_COMMAND_DISABLE_SRBCHANNEL, + /* Supported since protocol v31 (8.0) */ + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED, + PA_COMMAND_RECORD_STREAM_VOLUME_CHANGED, + PA_COMMAND_MAX }; diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 145db04..69264f6 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -239,6 +239,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes); static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes); static void sink_input_send_event_cb(pa_sink_input *i, const char *event, pa_proplist *pl); +static void sink_input_volume_changed_cb(pa_sink_input *o); static void native_connection_send_memblock(pa_native_connection *c); static void playback_stream_request_bytes(struct playback_stream*s); @@ -249,6 +250,7 @@ static void source_output_suspend_cb(pa_source_output *o, bool suspend); static void source_output_moving_cb(pa_source_output *o, pa_source *dest); static pa_usec_t source_output_get_latency_cb(pa_source_output *o); static void source_output_send_event_cb(pa_source_output *o, const char *event, pa_proplist *pl); +static void source_output_volume_changed_cb(pa_source_output *o); static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); static int source_output_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); @@ -719,6 +721,7 @@ static record_stream* record_stream_new( s->source_output->moving = source_output_moving_cb; s->source_output->suspend = source_output_suspend_cb; s->source_output->send_event = source_output_send_event_cb; + s->source_output->volume_changed = source_output_volume_changed_cb; s->source_output->userdata = s; fix_record_buffer_attr_pre(s); @@ -1201,6 +1204,7 @@ static playback_stream* playback_stream_new( s->sink_input->moving = sink_input_moving_cb; s->sink_input->suspend = sink_input_suspend_cb; s->sink_input->send_event = sink_input_send_event_cb; + s->sink_input->volume_changed = sink_input_volume_changed_cb; s->sink_input->userdata = s; start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; @@ -1815,6 +1819,34 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) { pa_pstream_send_tagstruct(s->connection->pstream, t); } +static void send_volume_changed(pa_pstream *pstream, bool playback, int32_t index, pa_cvolume *volume) { + pa_tagstruct *t; + + t = pa_tagstruct_new(); + pa_tagstruct_putu32(t, playback ? PA_COMMAND_PLAYBACK_STREAM_VOLUME_CHANGED : PA_COMMAND_RECORD_STREAM_VOLUME_CHANGED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, index); + pa_tagstruct_put_cvolume(t, volume); + + pa_pstream_send_tagstruct(pstream, t); +} + +/* Called from main context */ +static void sink_input_volume_changed_cb(pa_sink_input *i) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (s->connection->version < 31) { + pa_log_debug("Skipping volume update because client protocol version is old"); + return; + } + + send_volume_changed(s->connection->pstream, true, s->index, &i->volume); +} + /*** source_output callbacks ***/ /* Called from thread context */ @@ -1952,6 +1984,22 @@ static void source_output_moving_cb(pa_source_output *o, pa_source *dest) { pa_pstream_send_tagstruct(s->connection->pstream, t); } +/* Called from main context */ +static void source_output_volume_changed_cb(pa_source_output *o) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (s->connection->version < 31) { + pa_log_debug("Skipping volume update because client protocol version is old"); + return; + } + + send_volume_changed(s->connection->pstream, false, s->index, &o->volume); +} + /*** pdispatch callbacks ***/ static void protocol_error(pa_native_connection *c) { @@ -2240,6 +2288,9 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u } } + if (c->version >= 31) + pa_tagstruct_put_cvolume(reply, &s->sink_input->volume); + pa_pstream_send_tagstruct(c->pstream, reply); finish: @@ -2554,6 +2605,9 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin } } + if (c->version >= 31) + pa_tagstruct_put_cvolume(reply, &s->source_output->volume); + pa_pstream_send_tagstruct(c->pstream, reply); finish: diff --git a/src/tests/api-test.c b/src/tests/api-test.c index 5d75607..e7ae3df 100644 --- a/src/tests/api-test.c +++ b/src/tests/api-test.c @@ -63,6 +63,9 @@ static void stream_state_callback(pa_stream *s, void *userdata) { break; case PA_STREAM_READY: + fail_unless(pa_stream_get_volume(s, &v) == 0); + fail_unless(pa_cvolume_avg(&v) == (PA_VOLUME_NORM / 2)); + pa_cvolume_set(&v, CHANNELS, PA_VOLUME_NORM / 3); fail_unless(pa_stream_set_volume(s, &v, stream_volume_callback, PA_UINT_TO_PTR(true)) == 0); break; -- 2.5.0