From 5d35375aa758fde7d9f3d6467e2506aca9784597 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 17 May 2011 21:56:10 +0100 Subject: capture: Add the passthrough format negotiation to capture streams. This helps to keep the API more symmetrical and also potentially allows support for passthrough monitor sources at some point in the future. --- PROTOCOL | 13 ++ configure.ac | 2 +- src/modules/echo-cancel/module-echo-cancel.c | 2 +- src/modules/module-device-manager.c | 7 +- src/modules/module-intended-roles.c | 6 +- src/modules/module-loopback.c | 2 +- src/modules/module-stream-restore.c | 5 +- src/modules/module-virtual-source.c | 2 +- src/modules/rtp/module-rtp-send.c | 2 +- src/pulse/stream.c | 30 ++--- src/pulsecore/protocol-esound.c | 2 +- src/pulsecore/protocol-http.c | 2 +- src/pulsecore/protocol-native.c | 129 ++++++++++++++------ src/pulsecore/protocol-simple.c | 2 +- src/pulsecore/source-output.c | 175 +++++++++++++++++++++++---- src/pulsecore/source-output.h | 13 +- src/pulsecore/source.c | 92 ++++++++++++++ src/pulsecore/source.h | 24 +++- 18 files changed, 406 insertions(+), 104 deletions(-) diff --git a/PROTOCOL b/PROTOCOL index 4178274f..1097414d 100644 --- a/PROTOCOL +++ b/PROTOCOL @@ -241,3 +241,16 @@ One new field in reply from PA_COMMAND_GET_SINK_INPUT_INFO (and thus PA_COMMAND_GET_SINK_INPUT_INFO_LIST) format_info format + +## v22, implemented by >= 1.0 + +New fields PA_COMMAND_CREATE_RECORD_STREAM: + + uint8_t n_formats + format_info format1 + ... + format_info formatn + +One new field in reply from PA_COMMAND_CREATE_RECORD_STREAM: + + format_info format diff --git a/configure.ac b/configure.ac index 88ad875d..f95cbee9 100644 --- a/configure.ac +++ b/configure.ac @@ -36,7 +36,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, 21) +AC_SUBST(PA_PROTOCOL_VERSION, 22) # The stable ABI for client applications, for the version info x:y:z # always will hold y=z diff --git a/src/modules/echo-cancel/module-echo-cancel.c b/src/modules/echo-cancel/module-echo-cancel.c index 96eb9ef1..3d22ef84 100644 --- a/src/modules/echo-cancel/module-echo-cancel.c +++ b/src/modules/echo-cancel/module-echo-cancel.c @@ -1599,7 +1599,7 @@ int pa__init(pa_module*m) { pa_source_output_new_data_init(&source_output_data); source_output_data.driver = __FILE__; source_output_data.module = m; - source_output_data.source = source_master; + pa_source_output_new_data_set_source(&source_output_data, source_master, FALSE); source_output_data.destination_source = u->source; /* FIXME source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */ diff --git a/src/modules/module-device-manager.c b/src/modules/module-device-manager.c index 4138794c..272fad2b 100644 --- a/src/modules/module-device-manager.c +++ b/src/modules/module-device-manager.c @@ -871,10 +871,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou if (PA_INVALID_INDEX != device_index) { pa_source *source; - if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) { - new_data->source = source; - new_data->save_source = FALSE; - } + if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) + if (!pa_source_output_new_data_set_source(new_data, source, FALSE)) + pa_log_debug("Not restoring device for stream because no supported format was found"); } } } diff --git a/src/modules/module-intended-roles.c b/src/modules/module-intended-roles.c index 90385622..2f9bba4b 100644 --- a/src/modules/module-intended-roles.c +++ b/src/modules/module-intended-roles.c @@ -162,8 +162,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou /* Prefer the default source over any other source, just in case... */ if ((def = pa_namereg_get_default_source(c))) if (role_match(def->proplist, role)) { - new_data->source = def; - new_data->save_source = FALSE; + pa_source_output_new_data_set_source(new_data, def, FALSE); return PA_HOOK_OK; } @@ -179,8 +178,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou /* @todo: favour the highest priority device, not the first one we find? */ if (role_match(s->proplist, role)) { - new_data->source = s; - new_data->save_source = FALSE; + pa_source_output_new_data_set_source(new_data, s, FALSE); return PA_HOOK_OK; } } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index a5b08bd5..936133fc 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -755,7 +755,7 @@ int pa__init(pa_module *m) { pa_source_output_new_data_init(&source_output_data); source_output_data.driver = __FILE__; source_output_data.module = m; - source_output_data.source = source; + pa_source_output_new_data_set_source(&source_output_data, source, FALSE); if ((n = pa_modargs_get_value(ma, "source_output_name", NULL))) pa_proplist_sets(source_output_data.proplist, PA_PROP_MEDIA_NAME, n); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index fa2c0210..617de5bb 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -1217,7 +1217,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (source_output->save_source) { pa_strlcpy(entry.device, source_output->source->name, sizeof(entry.device)); - entry.device_valid = source_output->save_source; + entry.device_valid = TRUE; device_updated = !created_new_entry && (!old->device_valid || !pa_streq(entry.device, old->device)); @@ -1399,8 +1399,7 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou interfere with that */ if (s && PA_SOURCE_IS_LINKED(pa_source_get_state(s))) { pa_log_info("Restoring device for stream %s.", name); - new_data->source = s; - new_data->save_source = TRUE; + pa_source_output_new_data_set_source(new_data, s, TRUE); } pa_xfree(e); diff --git a/src/modules/module-virtual-source.c b/src/modules/module-virtual-source.c index 835cf3ce..170fa4e0 100644 --- a/src/modules/module-virtual-source.c +++ b/src/modules/module-virtual-source.c @@ -630,7 +630,7 @@ int pa__init(pa_module*m) { pa_source_output_new_data_init(&source_output_data); source_output_data.driver = __FILE__; source_output_data.module = m; - source_output_data.source = master; + pa_source_output_new_data_set_source(&source_output_data, master, FALSE); source_output_data.destination_source = u->source; /* FIXME source_output_data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; */ diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index f53020d1..e0fed997 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -325,7 +325,7 @@ int pa__init(pa_module*m) { pa_proplist_setf(data.proplist, "rtp.ttl", "%lu", (unsigned long) ttl); data.driver = __FILE__; data.module = m; - data.source = s; + pa_source_output_new_data_set_source(&data, s, FALSE); pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); data.flags = PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND; diff --git a/src/pulse/stream.c b/src/pulse/stream.c index aeb897c2..32936848 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -1092,7 +1092,9 @@ void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, s->timing_info.configured_sink_usec = usec; } - if (s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) { + if ((s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) + || s->context->version >= 22) { + pa_format_info *f = pa_format_info_new(); pa_tagstruct_get_format_info(t, f); @@ -1319,26 +1321,18 @@ static int create_stream( pa_tagstruct_put_boolean(t, flags & PA_STREAM_FAIL_ON_SUSPEND); } - if (s->context->version >= 17) { - - if (s->direction == PA_STREAM_PLAYBACK) - pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME); - - } - - if (s->context->version >= 18) { + if (s->context->version >= 17 && s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & PA_STREAM_RELATIVE_VOLUME); - if (s->direction == PA_STREAM_PLAYBACK) - pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); - } + if (s->context->version >= 18 && s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put_boolean(t, flags & (PA_STREAM_PASSTHROUGH)); - if (s->context->version >= 21) { + if ((s->context->version >= 21 && s->direction == PA_STREAM_PLAYBACK) + || s->context->version >= 22) { - if (s->direction == PA_STREAM_PLAYBACK) { - pa_tagstruct_putu8(t, s->n_formats); - for (i = 0; i < s->n_formats; i++) - pa_tagstruct_put_format_info(t, s->req_formats[i]); - } + pa_tagstruct_putu8(t, s->n_formats); + for (i = 0; i < s->n_formats; i++) + pa_tagstruct_put_format_info(t, s->req_formats[i]); } pa_pstream_send_tagstruct(s->context->pstream, t); diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c index c2af77c2..edca96a1 100644 --- a/src/pulsecore/protocol-esound.c +++ b/src/pulsecore/protocol-esound.c @@ -524,7 +524,7 @@ static int esd_proto_stream_record(connection *c, esd_proto_t request, const voi sdata.driver = __FILE__; sdata.module = c->options->module; sdata.client = c->client; - sdata.source = source; + pa_source_output_new_data_set_source(&sdata, source, FALSE); pa_source_output_new_data_set_sample_spec(&sdata, &ss); pa_source_output_new(&c->source_output, c->protocol->core, &sdata); diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c index 83067f85..1de04345 100644 --- a/src/pulsecore/protocol-http.c +++ b/src/pulsecore/protocol-http.c @@ -560,7 +560,7 @@ static void handle_listen_prefix(struct connection *c, const char *source_name) data.driver = __FILE__; data.module = c->module; data.client = c->client; - data.source = source; + pa_source_output_new_data_set_source(&data, source, FALSE); pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index b16793d9..501ff378 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -629,13 +629,14 @@ static record_stream* record_stream_new( pa_source *source, pa_sample_spec *ss, pa_channel_map *map, - pa_bool_t peak_detect, + pa_idxset *formats, pa_buffer_attr *attr, pa_source_output_flags_t flags, pa_proplist *p, pa_bool_t adjust_latency, - pa_sink_input *direct_on_input, pa_bool_t early_requests, + pa_bool_t peak_detect, + pa_sink_input *direct_on_input, int *ret) { record_stream *s; @@ -653,10 +654,15 @@ static record_stream* record_stream_new( data.driver = __FILE__; data.module = c->options->module; data.client = c->client; - data.source = source; + if (source) + pa_source_output_new_data_set_source(&data, source, TRUE); + if (pa_sample_spec_valid(ss)) + pa_source_output_new_data_set_sample_spec(&data, ss); + if (pa_channel_map_valid(map)) + pa_source_output_new_data_set_channel_map(&data, map); + if (formats) + pa_source_output_new_data_set_formats(&data, formats); data.direct_on_input = direct_on_input; - pa_source_output_new_data_set_sample_spec(&data, ss); - pa_source_output_new_data_set_channel_map(&data, map); if (peak_detect) data.resample_method = PA_RESAMPLER_PEAKS; data.flags = flags; @@ -1028,13 +1034,13 @@ static playback_stream* playback_stream_new( pa_cvolume *volume, pa_bool_t muted, pa_bool_t muted_set, - uint32_t syncid, - uint32_t *missing, pa_sink_input_flags_t flags, pa_proplist *p, pa_bool_t adjust_latency, pa_bool_t early_requests, pa_bool_t relative_volume, + uint32_t syncid, + uint32_t *missing, int *ret) { /* Note: This function takes ownership of the 'formats' param, so we need @@ -1900,6 +1906,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u adjust_latency = FALSE, early_requests = FALSE, dont_inhibit_auto_suspend = FALSE, + volume_set = TRUE, muted_set = FALSE, fail_on_suspend = FALSE, relative_volume = FALSE, @@ -1907,7 +1914,6 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u pa_sink_input_flags_t flags = 0; pa_proplist *p = NULL; - pa_bool_t volume_set = TRUE; int ret = PA_ERR_INVALID; uint8_t n_formats = 0; pa_format_info *format; @@ -2081,7 +2087,7 @@ static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, u * flag. For older versions we synthesize it here */ muted_set = muted_set || muted; - s = playback_stream_new(c, sink, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, syncid, &missing, flags, p, adjust_latency, early_requests, relative_volume, &ret); + s = playback_stream_new(c, sink, &ss, &map, formats, &attr, volume_set ? &volume : NULL, muted, muted_set, flags, p, adjust_latency, early_requests, relative_volume, syncid, &missing, &ret); /* We no longer own the formats idxset */ formats = NULL; @@ -2222,12 +2228,18 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin peak_detect = FALSE, early_requests = FALSE, dont_inhibit_auto_suspend = FALSE, - fail_on_suspend = FALSE; + fail_on_suspend = FALSE, + passthrough = FALSE; + pa_source_output_flags_t flags = 0; - pa_proplist *p; + pa_proplist *p = NULL; uint32_t direct_on_input_idx = PA_INVALID_INDEX; pa_sink_input *direct_on_input = NULL; int ret = PA_ERR_INVALID; + uint8_t n_formats = 0; + pa_format_info *format; + pa_idxset *formats = NULL; + uint32_t i; pa_native_connection_assert_ref(c); pa_assert(t); @@ -2242,17 +2254,15 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin pa_tagstruct_getu32(t, &attr.maxlength) < 0 || pa_tagstruct_get_boolean(t, &corked) < 0 || pa_tagstruct_getu32(t, &attr.fragsize) < 0) { + protocol_error(c); - return; + goto finish; } - CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); - CHECK_VALIDITY(c->pstream, !source_name || pa_namereg_is_valid_name_or_wildcard(source_name, PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); - CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY_GOTO(c->pstream, c->authorized, tag, PA_ERR_ACCESS, finish); + CHECK_VALIDITY_GOTO(c->pstream, !source_name || pa_namereg_is_valid_name_or_wildcard(source_name, PA_NAMEREG_SOURCE), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, source_index == PA_INVALID_INDEX || !source_name, tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, !source_name || source_index == PA_INVALID_INDEX, tag, PA_ERR_INVALID, finish); p = pa_proplist_new(); @@ -2271,8 +2281,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin pa_tagstruct_get_boolean(t, &variable_rate) < 0) { protocol_error(c); - pa_proplist_free(p); - return; + goto finish; } } @@ -2282,9 +2291,9 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin pa_tagstruct_get_boolean(t, &adjust_latency) < 0 || pa_tagstruct_get_proplist(t, p) < 0 || pa_tagstruct_getu32(t, &direct_on_input_idx) < 0) { + protocol_error(c); - pa_proplist_free(p); - return; + goto finish; } } @@ -2292,8 +2301,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin if (pa_tagstruct_get_boolean(t, &early_requests) < 0) { protocol_error(c); - pa_proplist_free(p); - return; + goto finish; } } @@ -2301,32 +2309,59 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin if (pa_tagstruct_get_boolean(t, &dont_inhibit_auto_suspend) < 0 || pa_tagstruct_get_boolean(t, &fail_on_suspend) < 0) { + protocol_error(c); - pa_proplist_free(p); - return; + goto finish; + } + } + + if (c->version >= 22) { + + if (pa_tagstruct_getu8(t, &n_formats) < 0) { + protocol_error(c); + goto finish; + } + + if (n_formats) + formats = pa_idxset_new(NULL, NULL); + + for (i = 0; i < n_formats; i++) { + format = pa_format_info_new(); + if (pa_tagstruct_get_format_info(t, format) < 0) { + protocol_error(c); + goto finish; + } + pa_idxset_put(formats, format, NULL); } } + if (n_formats == 0) { + CHECK_VALIDITY_GOTO(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID, finish); + CHECK_VALIDITY_GOTO(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID, finish); + } else { + PA_IDXSET_FOREACH(format, formats, i) { + CHECK_VALIDITY_GOTO(c->pstream, pa_format_info_valid(format), tag, PA_ERR_INVALID, finish); + } + } + + if (!pa_tagstruct_eof(t)) { protocol_error(c); - pa_proplist_free(p); - return; + goto finish; } if (source_index != PA_INVALID_INDEX) { if (!(source = pa_idxset_get_by_index(c->protocol->core->sources, source_index))) { pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); - pa_proplist_free(p); - return; + goto finish; } } else if (source_name) { if (!(source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE))) { pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); - pa_proplist_free(p); - return; + goto finish; } } @@ -2334,8 +2369,7 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin if (!(direct_on_input = pa_idxset_get_by_index(c->protocol->core->sink_inputs, direct_on_input_idx))) { pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); - pa_proplist_free(p); - return; + goto finish; } } @@ -2349,12 +2383,12 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin (no_move ? PA_SOURCE_OUTPUT_DONT_MOVE : 0) | (variable_rate ? PA_SOURCE_OUTPUT_VARIABLE_RATE : 0) | (dont_inhibit_auto_suspend ? PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND : 0) | - (fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0); + (fail_on_suspend ? PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND|PA_SOURCE_OUTPUT_KILL_ON_SUSPEND : 0) | + (passthrough ? PA_SOURCE_OUTPUT_PASSTHROUGH : 0); - s = record_stream_new(c, source, &ss, &map, peak_detect, &attr, flags, p, adjust_latency, direct_on_input, early_requests, &ret); - pa_proplist_free(p); + s = record_stream_new(c, source, &ss, &map, formats, &attr, flags, p, adjust_latency, early_requests, peak_detect, direct_on_input, &ret); - CHECK_VALIDITY(c->pstream, s, tag, ret); + CHECK_VALIDITY_GOTO(c->pstream, s, tag, ret, finish); reply = reply_new(tag); pa_tagstruct_putu32(reply, s->index); @@ -2385,7 +2419,24 @@ static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uin if (c->version >= 13) pa_tagstruct_put_usec(reply, s->configured_source_latency); + if (c->version >= 22) { + /* Send back the format we negotiated */ + if (s->source_output->format) + pa_tagstruct_put_format_info(reply, s->source_output->format); + else { + pa_format_info *f = pa_format_info_new(); + pa_tagstruct_put_format_info(reply, f); + pa_format_info_free(f); + } + } + pa_pstream_send_tagstruct(c->pstream, reply); + +finish: + if (p) + pa_proplist_free(p); + if (formats) + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); } static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c index c1aaa813..41a3cc5c 100644 --- a/src/pulsecore/protocol-simple.c +++ b/src/pulsecore/protocol-simple.c @@ -593,7 +593,7 @@ void pa_simple_protocol_connect(pa_simple_protocol *p, pa_iochannel *io, pa_simp data.driver = __FILE__; data.module = o->module; data.client = c->client; - data.source = source; + pa_source_output_new_data_set_source(&data, source, FALSE); pa_proplist_update(data.proplist, PA_UPDATE_MERGE, c->client->proplist); pa_source_output_new_data_set_sample_spec(&data, &o->sample_spec); diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 963ef069..61e06954 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -69,9 +70,80 @@ void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, data->channel_map = *map; } +pa_bool_t pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data) { + pa_assert(data); + + if (PA_LIKELY(data->format) && PA_UNLIKELY(!pa_format_info_is_pcm(data->format))) + return TRUE; + + if (PA_UNLIKELY(data->flags & PA_SOURCE_OUTPUT_PASSTHROUGH)) + return TRUE; + + return FALSE; +} + +pa_bool_t pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, pa_bool_t save) { + pa_bool_t ret = TRUE; + pa_idxset *formats = NULL; + + pa_assert(data); + pa_assert(s); + + if (!data->req_formats) { + /* We're not working with the extended API */ + data->source = s; + data->save_source = save; + } else { + /* Extended API: let's see if this source supports the formats the client would like */ + formats = pa_source_check_formats(s, data->req_formats); + + if (formats && !pa_idxset_isempty(formats)) { + /* Source supports at least one of the requested formats */ + data->source = s; + data->save_source = save; + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + data->nego_formats = formats; + } else { + /* Source doesn't support any of the formats requested by the client */ + if (formats) + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + ret = FALSE; + } + } + + return ret; +} + +pa_bool_t pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats) { + pa_assert(data); + pa_assert(formats); + + if (data->req_formats) + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + + data->req_formats = formats; + + if (data->source) { + /* Trigger format negotiation */ + return pa_source_output_new_data_set_source(data, data->source, data->save_source); + } + + return TRUE; +} + void pa_source_output_new_data_done(pa_source_output_new_data *data) { pa_assert(data); + if (data->req_formats) + pa_idxset_free(data->req_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + + if (data->nego_formats) + pa_idxset_free(data->nego_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + + if (data->format) + pa_format_info_free(data->format); + pa_proplist_free(data->proplist); } @@ -106,8 +178,11 @@ int pa_source_output_new( pa_source_output *o; pa_resampler *resampler = NULL; char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_channel_map original_cm; int r; char *pt; + pa_sample_spec ss; + pa_channel_map map; pa_assert(_o); pa_assert(core); @@ -117,14 +192,44 @@ int pa_source_output_new( if (data->client) pa_proplist_update(data->proplist, PA_UPDATE_MERGE, data->client->proplist); + if (!data->req_formats) { + /* From this point on, we want to work only with formats, and get back + * to using the sample spec and channel map after all decisions w.r.t. + * routing are complete. */ + pa_idxset *tmp = pa_idxset_new(NULL, NULL); + pa_format_info *f = pa_format_info_from_sample_spec(&data->sample_spec, &data->channel_map); + pa_idxset_put(tmp, f, NULL); + pa_source_output_new_data_set_formats(data, tmp); + } + if ((r = pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data)) < 0) return r; pa_return_val_if_fail(!data->driver || pa_utf8_valid(data->driver), -PA_ERR_INVALID); - if (!data->source) { - data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE); - data->save_source = FALSE; + if (!data->source) + pa_source_output_new_data_set_source(data, pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE), FALSE); + + /* Routing's done, we have a source. Now let's fix the format and set up the + * sample spec */ + + /* If something didn't pick a format for us, pick the top-most format since + * we assume this is sorted in priority order */ + if (!data->format && data->nego_formats && !pa_idxset_isempty(data->nego_formats)) + data->format = pa_format_info_copy(pa_idxset_first(data->nego_formats, NULL)); + + pa_return_val_if_fail(data->format, -PA_ERR_NOTSUPPORTED); + + /* Now populate the sample spec and format according to the final + * format that we've negotiated */ + if (PA_LIKELY(data->format->encoding == PA_ENCODING_PCM)) { + pa_return_val_if_fail(pa_format_info_to_sample_spec(data->format, &ss, &map), -PA_ERR_INVALID); + pa_source_output_new_data_set_sample_spec(data, &ss); + if (pa_channel_map_valid(&map)) + pa_source_output_new_data_set_channel_map(data, &map); + } else { + pa_return_val_if_fail(pa_format_info_to_sample_spec_fake(data->format, &ss), -PA_ERR_INVALID); + pa_source_output_new_data_set_sample_spec(data, &ss); } pa_return_val_if_fail(data->source, -PA_ERR_NOENTITY); @@ -143,7 +248,6 @@ int pa_source_output_new( pa_channel_map_init_extend(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT); } - pa_return_val_if_fail(pa_channel_map_valid(&data->channel_map), -PA_ERR_INVALID); pa_return_val_if_fail(pa_channel_map_compatible(&data->channel_map, &data->sample_spec), -PA_ERR_INVALID); if (data->flags & PA_SOURCE_OUTPUT_FIX_FORMAT) @@ -152,6 +256,8 @@ int pa_source_output_new( if (data->flags & PA_SOURCE_OUTPUT_FIX_RATE) data->sample_spec.rate = data->source->sample_spec.rate; + original_cm = data->channel_map; + if (data->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { data->sample_spec.channels = data->source->sample_spec.channels; data->channel_map = data->source->channel_map; @@ -183,18 +289,19 @@ int pa_source_output_new( !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) { - if (!(resampler = pa_resampler_new( - core->mempool, - &data->source->sample_spec, &data->source->channel_map, - &data->sample_spec, &data->channel_map, - data->resample_method, - ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | - ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | - (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | - (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { - pa_log_warn("Unsupported resampling operation."); - return -PA_ERR_NOTSUPPORTED; - } + if (!pa_source_output_new_data_is_passthrough(data)) /* no resampler for passthrough content */ + if (!(resampler = pa_resampler_new( + core->mempool, + &data->source->sample_spec, &data->source->channel_map, + &data->sample_spec, &data->channel_map, + data->resample_method, + ((data->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((data->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (data->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0) | + (core->disable_lfe_remixing ? PA_RESAMPLER_NO_LFE : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -PA_ERR_NOTSUPPORTED; + } } o = pa_msgobject_new(pa_source_output); @@ -211,15 +318,14 @@ int pa_source_output_new( o->destination_source = data->destination_source; o->client = data->client; - o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; o->requested_resample_method = data->resample_method; + o->actual_resample_method = resampler ? pa_resampler_get_method(resampler) : PA_RESAMPLER_INVALID; o->sample_spec = data->sample_spec; o->channel_map = data->channel_map; + o->format = pa_format_info_copy(data->format); o->direct_on_input = data->direct_on_input; - o->save_source = data->save_source; - reset_callbacks(o); o->userdata = NULL; @@ -373,6 +479,9 @@ static void source_output_free(pa_object* mo) { if (o->thread_info.resampler) pa_resampler_free(o->thread_info.resampler); + if (o->format) + pa_format_info_free(o->format); + if (o->proplist) pa_proplist_free(o->proplist); @@ -677,6 +786,19 @@ void pa_source_output_set_name(pa_source_output *o, const char *name) { } } +/* Called from main or I/O context */ +pa_bool_t pa_source_output_is_passthrough(pa_source_output *o) { + pa_source_output_assert_ref(o); + + if (PA_UNLIKELY(!pa_format_info_is_pcm(o->format))) + return TRUE; + + if (PA_UNLIKELY(o->flags & PA_SOURCE_OUTPUT_PASSTHROUGH)) + return TRUE; + + return FALSE; +} + /* Called from main thread */ void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p) { pa_source_output_assert_ref(o); @@ -782,7 +904,18 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t pa_source_assert_ref(dest); if (!pa_source_output_may_move_to(o, dest)) - return -1; + return -PA_ERR_NOTSUPPORTED; + + if (pa_source_output_is_passthrough(o) && !pa_source_check_format(dest, o->format)) { + pa_proplist *p = pa_proplist_new(); + pa_log_debug("New source doesn't support stream format, sending format-changed and killing"); + /* Tell the client what device we want to be on if it is going to + * reconnect */ + pa_proplist_sets(p, "device", dest->name); + pa_source_output_send_event(o, PA_STREAM_EVENT_FORMAT_LOST, p); + pa_proplist_free(p); + return -PA_ERR_NOTSUPPORTED; + } if (o->thread_info.resampler && pa_sample_spec_equal(pa_resampler_input_sample_spec(o->thread_info.resampler), &dest->sample_spec) && @@ -795,7 +928,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec) || !pa_channel_map_equal(&o->channel_map, &dest->channel_map)) { - /* Okey, we need a new resampler for the new source */ + /* Okay, we need a new resampler for the new source */ if (!(new_resampler = pa_resampler_new( o->core->mempool, diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index f16f9520..88de4ae6 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -28,6 +28,7 @@ typedef struct pa_source_output pa_source_output; #include #include +#include #include #include #include @@ -56,7 +57,8 @@ typedef enum pa_source_output_flags { PA_SOURCE_OUTPUT_FIX_CHANNELS = 128, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND = 256, PA_SOURCE_OUTPUT_NO_CREATE_ON_SUSPEND = 512, - PA_SOURCE_OUTPUT_KILL_ON_SUSPEND = 1024 + PA_SOURCE_OUTPUT_KILL_ON_SUSPEND = 1024, + PA_SOURCE_OUTPUT_PASSTHROUGH = 2048 } pa_source_output_flags_t; struct pa_source_output { @@ -82,6 +84,7 @@ struct pa_source_output { pa_sample_spec sample_spec; pa_channel_map channel_map; + pa_format_info *format; /* if TRUE then the source we are connected to is worth * remembering, i.e. was explicitly chosen by the user and not @@ -218,6 +221,9 @@ typedef struct pa_source_output_new_data { pa_sample_spec sample_spec; pa_channel_map channel_map; + pa_format_info *format; + pa_idxset *req_formats; + pa_idxset *nego_formats; pa_bool_t sample_spec_is_set:1; pa_bool_t channel_map_is_set:1; @@ -228,6 +234,9 @@ typedef struct pa_source_output_new_data { pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data); void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec); void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map); +pa_bool_t pa_source_output_new_data_is_passthrough(pa_source_output_new_data *data); +pa_bool_t pa_source_output_new_data_set_source(pa_source_output_new_data *data, pa_source *s, pa_bool_t save); +pa_bool_t pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats); void pa_source_output_new_data_done(pa_source_output_new_data *data); /* To be called by the implementing module only */ @@ -257,6 +266,8 @@ void pa_source_output_kill(pa_source_output*o); pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_latency); +pa_bool_t pa_source_output_is_passthrough(pa_source_output *o); + void pa_source_output_update_proplist(pa_source_output *o, pa_update_mode_t mode, pa_proplist *p); pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o); diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 15a5b8d9..587291ad 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -130,6 +131,7 @@ static void reset_callbacks(pa_source *s) { s->set_mute = NULL; s->update_requested_latency = NULL; s->set_port = NULL; + s->get_formats = NULL; } /* Called from main context */ @@ -754,6 +756,15 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) { return usec; } +/* Called from main context */ +pa_bool_t pa_source_is_passthrough(pa_source *s) { + + pa_source_assert_ref(s); + + /* NB Currently only monitor sources support passthrough mode */ + return (s->monitor_of && pa_sink_is_passthrough(s->monitor_of)); +} + /* Called from main thread */ void pa_source_set_volume( pa_source *s, @@ -1575,3 +1586,84 @@ int pa_source_set_port(pa_source *s, const char *name, pa_bool_t save) { return 0; } + +/* Called from the main thread */ +/* Gets the list of formats supported by the source. The members and idxset must + * be freed by the caller. */ +pa_idxset* pa_source_get_formats(pa_source *s) { + pa_idxset *ret; + + pa_assert(s); + + if (s->get_formats) { + /* Source supports format query, all is good */ + ret = s->get_formats(s); + } else { + /* Source doesn't support format query, so assume it does PCM */ + pa_format_info *f = pa_format_info_new(); + f->encoding = PA_ENCODING_PCM; + + ret = pa_idxset_new(NULL, NULL); + pa_idxset_put(ret, f, NULL); + } + + return ret; +} + +/* Called from the main thread */ +/* Checks if the source can accept this format */ +pa_bool_t pa_source_check_format(pa_source *s, pa_format_info *f) +{ + pa_idxset *formats = NULL; + pa_bool_t ret = FALSE; + + pa_assert(s); + pa_assert(f); + + formats = pa_source_get_formats(s); + + if (formats) { + pa_format_info *finfo_device; + uint32_t i; + + PA_IDXSET_FOREACH(finfo_device, formats, i) { + if (pa_format_info_is_compatible(finfo_device, f)) { + ret = TRUE; + break; + } + } + + pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + } + + return ret; +} + +/* Called from the main thread */ +/* Calculates the intersection between formats supported by the source and + * in_formats, and returns these, in the order of the source's formats. */ +pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats) { + pa_idxset *out_formats = pa_idxset_new(NULL, NULL), *source_formats = NULL; + pa_format_info *f_source, *f_in; + uint32_t i, j; + + pa_assert(s); + + if (!in_formats || pa_idxset_isempty(in_formats)) + goto done; + + source_formats = pa_source_get_formats(s); + + PA_IDXSET_FOREACH(f_source, source_formats, i) { + PA_IDXSET_FOREACH(f_in, in_formats, j) { + if (pa_format_info_is_compatible(f_source, f_in)) + pa_idxset_put(out_formats, pa_format_info_copy(f_in), NULL); + } + } + +done: + if (source_formats) + pa_idxset_free(source_formats, (pa_free2_cb_t) pa_format_info_free2, NULL); + + return out_formats; +} diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 9e8e2ada..3cdc77ae 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -108,31 +108,35 @@ struct pa_source { * context. If this is NULL a PA_SOURCE_MESSAGE_GET_VOLUME message * will be sent to the IO thread instead. If refresh_volume is * FALSE neither this function is called nor a message is sent. */ - void (*get_volume)(pa_source *s); /* dito */ + void (*get_volume)(pa_source *s); /* ditto */ /* Called when the volume shall be changed. Called from main loop * context. If this is NULL a PA_SOURCE_MESSAGE_SET_VOLUME message * will be sent to the IO thread instead. */ - void (*set_volume)(pa_source *s); /* dito */ + void (*set_volume)(pa_source *s); /* ditto */ /* Called when the mute setting is queried. Called from main loop * context. If this is NULL a PA_SOURCE_MESSAGE_GET_MUTE message * will be sent to the IO thread instead. If refresh_mute is * FALSE neither this function is called nor a message is sent.*/ - void (*get_mute)(pa_source *s); /* dito */ + void (*get_mute)(pa_source *s); /* ditto */ /* Called when the mute setting shall be changed. Called from main * loop context. If this is NULL a PA_SOURCE_MESSAGE_SET_MUTE * message will be sent to the IO thread instead. */ - void (*set_mute)(pa_source *s); /* dito */ + void (*set_mute)(pa_source *s); /* ditto */ /* Called when a the requested latency is changed. Called from IO * thread context. */ - void (*update_requested_latency)(pa_source *s); /* dito */ + void (*update_requested_latency)(pa_source *s); /* ditto */ /* Called whenever the port shall be changed. Called from main * thread. */ - int (*set_port)(pa_source *s, pa_device_port *port); /*dito */ + int (*set_port)(pa_source *s, pa_device_port *port); /*ditto */ + + /* Called to get the list of formats supported by the source, sorted + * in descending order of preference. */ + pa_idxset* (*get_formats)(pa_source *s); /* ditto */ /* Contains copies of the above data so that the real-time worker * thread can work without access locking */ @@ -265,6 +269,10 @@ int pa_source_update_status(pa_source*s); int pa_source_suspend(pa_source *s, pa_bool_t suspend, pa_suspend_cause_t cause); int pa_source_suspend_all(pa_core *c, pa_bool_t suspend, pa_suspend_cause_t cause); +/* Is the source in passthrough mode? (that is, is this a monitor source for a sink + * that has a passthrough sink input connected to it. */ +pa_bool_t pa_source_is_passthrough(pa_source *s); + void pa_source_set_volume(pa_source *source, const pa_cvolume *volume, pa_bool_t save); const pa_cvolume *pa_source_get_volume(pa_source *source, pa_bool_t force_refresh); @@ -285,6 +293,10 @@ pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q); void pa_source_move_all_finish(pa_source *s, pa_queue *q, pa_bool_t save); void pa_source_move_all_fail(pa_queue *q); +pa_idxset* pa_source_get_formats(pa_source *s); +pa_bool_t pa_source_check_format(pa_source *s, pa_format_info *f); +pa_idxset* pa_source_check_formats(pa_source *s, pa_idxset *in_formats); + /*** To be called exclusively by the source driver, from IO context */ void pa_source_post(pa_source*s, const pa_memchunk *chunk); -- cgit