mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-10-29 05:40:23 -04:00 
			
		
		
		
	Merge branch 'codec-switch-without-reroute' into 'master'
Draft: bluetooth: Switch codecs without tearing down sink/source/thread Closes #1113 See merge request pulseaudio/pulseaudio!489
This commit is contained in:
		
						commit
						6d65f7ad37
					
				
					 6 changed files with 194 additions and 71 deletions
				
			
		|  | @ -52,13 +52,13 @@ typedef struct pa_a2dp_endpoint_conf { | |||
|      * (const char *endpoint -> const pa_a2dp_codec_capabilities *capability) | ||||
|      * and returns corresponding endpoint key (or NULL when there is no valid), | ||||
|      * for_encoder is true when capabilities hash map is used for encoding */ | ||||
|     const char *(*choose_remote_endpoint)(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding); | ||||
|     const char *(*choose_remote_endpoint)(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *spec, bool for_encoding); | ||||
|     /* Fill codec capabilities, returns size of filled buffer */ | ||||
|     uint8_t (*fill_capabilities)(uint8_t capabilities_buffer[MAX_A2DP_CAPS_SIZE]); | ||||
|     /* Validate codec configuration, returns true on success */ | ||||
|     bool (*is_configuration_valid)(const uint8_t *config_buffer, uint8_t config_size); | ||||
|     /* Fill preferred codec configuration, returns size of filled buffer or 0 on failure */ | ||||
|     uint8_t (*fill_preferred_configuration)(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]); | ||||
|     uint8_t (*fill_preferred_configuration)(const pa_sample_spec *spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]); | ||||
| 
 | ||||
|     /* Bluetooth codec */ | ||||
|     pa_bt_codec bt_codec; | ||||
|  |  | |||
|  | @ -89,7 +89,7 @@ static bool can_accept_capabilities_hd(const uint8_t *capabilities_buffer, uint8 | |||
|     return can_accept_capabilities_common(&capabilities->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID); | ||||
| } | ||||
| 
 | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) { | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *spec, bool for_encoding) { | ||||
|     const pa_a2dp_codec_capabilities *a2dp_capabilities; | ||||
|     const char *key; | ||||
|     void *state; | ||||
|  | @ -103,7 +103,7 @@ static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap | |||
|     return NULL; | ||||
| } | ||||
| 
 | ||||
| static const char *choose_remote_endpoint_hd(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) { | ||||
| static const char *choose_remote_endpoint_hd(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *spec, bool for_encoding) { | ||||
|     const pa_a2dp_codec_capabilities *a2dp_capabilities; | ||||
|     const char *key; | ||||
|     void *state; | ||||
|  | @ -182,7 +182,7 @@ static bool is_configuration_valid_hd(const uint8_t *config_buffer, uint8_t conf | |||
|     return is_configuration_valid_common(&config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID); | ||||
| } | ||||
| 
 | ||||
| static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_aptx_t *capabilities, a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) { | ||||
| static int fill_preferred_configuration_common(const pa_sample_spec *spec, const a2dp_aptx_t *capabilities, a2dp_aptx_t *config, uint32_t vendor_id, uint16_t codec_id) { | ||||
|     int i; | ||||
| 
 | ||||
|     static const struct { | ||||
|  | @ -211,7 +211,7 @@ static int fill_preferred_configuration_common(const pa_sample_spec *default_sam | |||
| 
 | ||||
|     /* Find the lowest freq that is at least as high as the requested sampling rate */ | ||||
|     for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) { | ||||
|         if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|         if (freq_table[i].rate >= spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|             config->frequency = freq_table[i].cap; | ||||
|             break; | ||||
|         } | ||||
|  | @ -234,7 +234,7 @@ static int fill_preferred_configuration_common(const pa_sample_spec *default_sam | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
|     a2dp_aptx_t *config = (a2dp_aptx_t *) config_buffer; | ||||
|     const a2dp_aptx_t *capabilities = (const a2dp_aptx_t *) capabilities_buffer; | ||||
| 
 | ||||
|  | @ -245,13 +245,13 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample | |||
| 
 | ||||
|     pa_zero(*config); | ||||
| 
 | ||||
|     if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, APTX_VENDOR_ID, APTX_CODEC_ID) < 0) | ||||
|     if (fill_preferred_configuration_common(spec, capabilities, config, APTX_VENDOR_ID, APTX_CODEC_ID) < 0) | ||||
|         return 0; | ||||
| 
 | ||||
|     return sizeof(*config); | ||||
| } | ||||
| 
 | ||||
| static uint8_t fill_preferred_configuration_hd(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
| static uint8_t fill_preferred_configuration_hd(const pa_sample_spec *spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
|     a2dp_aptx_hd_t *config = (a2dp_aptx_hd_t *) config_buffer; | ||||
|     const a2dp_aptx_hd_t *capabilities = (const a2dp_aptx_hd_t *) capabilities_buffer; | ||||
| 
 | ||||
|  | @ -262,7 +262,7 @@ static uint8_t fill_preferred_configuration_hd(const pa_sample_spec *default_sam | |||
| 
 | ||||
|     pa_zero(*config); | ||||
| 
 | ||||
|     if (fill_preferred_configuration_common(default_sample_spec, &capabilities->aptx, &config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID) < 0) | ||||
|     if (fill_preferred_configuration_common(spec, &capabilities->aptx, &config->aptx, APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID) < 0) | ||||
|         return 0; | ||||
| 
 | ||||
|     return sizeof(*config); | ||||
|  |  | |||
|  | @ -72,7 +72,7 @@ static bool can_accept_capabilities(const uint8_t *capabilities_buffer, uint8_t | |||
|     return can_accept_capabilities_common(capabilities, LDAC_VENDOR_ID, LDAC_CODEC_ID); | ||||
| } | ||||
| 
 | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) { | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *spec, bool for_encoding) { | ||||
|     const pa_a2dp_codec_capabilities *a2dp_capabilities; | ||||
|     const char *key; | ||||
|     void *state; | ||||
|  | @ -128,7 +128,7 @@ static bool is_configuration_valid(const uint8_t *config_buffer, uint8_t config_ | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static int fill_preferred_configuration_common(const pa_sample_spec *default_sample_spec, const a2dp_ldac_t *capabilities, a2dp_ldac_t *config, uint32_t vendor_id, uint16_t codec_id) { | ||||
| static int fill_preferred_configuration_common(const pa_sample_spec *spec, const a2dp_ldac_t *capabilities, a2dp_ldac_t *config, uint32_t vendor_id, uint16_t codec_id) { | ||||
|     int i; | ||||
| 
 | ||||
|     static const struct { | ||||
|  | @ -157,7 +157,7 @@ static int fill_preferred_configuration_common(const pa_sample_spec *default_sam | |||
| 
 | ||||
|     /* Find the lowest freq that is at least as high as the requested sampling rate */ | ||||
|     for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) { | ||||
|         if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|         if (freq_table[i].rate >= spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|             config->frequency = freq_table[i].cap; | ||||
|             break; | ||||
|         } | ||||
|  | @ -180,7 +180,7 @@ static int fill_preferred_configuration_common(const pa_sample_spec *default_sam | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
|     a2dp_ldac_t *config = (a2dp_ldac_t *) config_buffer; | ||||
|     const a2dp_ldac_t *capabilities = (const a2dp_ldac_t *) capabilities_buffer; | ||||
| 
 | ||||
|  | @ -191,7 +191,7 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample | |||
| 
 | ||||
|     pa_zero(*config); | ||||
| 
 | ||||
|     if (fill_preferred_configuration_common(default_sample_spec, capabilities, config, LDAC_VENDOR_ID, LDAC_CODEC_ID) < 0) | ||||
|     if (fill_preferred_configuration_common(spec, capabilities, config, LDAC_VENDOR_ID, LDAC_CODEC_ID) < 0) | ||||
|         return 0; | ||||
| 
 | ||||
|     return sizeof(*config); | ||||
|  |  | |||
|  | @ -134,7 +134,7 @@ static bool can_accept_capabilities_faststream(const uint8_t *capabilities_buffe | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *default_sample_spec, bool for_encoding) { | ||||
| static const char *choose_remote_endpoint(const pa_hashmap *capabilities_hashmap, const pa_sample_spec *spec, bool for_encoding) { | ||||
|     const pa_a2dp_codec_capabilities *a2dp_capabilities; | ||||
|     const char *key; | ||||
|     void *state; | ||||
|  | @ -495,7 +495,7 @@ static uint8_t default_bitpool(uint8_t freq, uint8_t mode) { | |||
|     pa_assert_not_reached(); | ||||
| } | ||||
| 
 | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample_spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
| static uint8_t fill_preferred_configuration(const pa_sample_spec *spec, const uint8_t *capabilities_buffer, uint8_t capabilities_size, uint8_t config_buffer[MAX_A2DP_CAPS_SIZE]) { | ||||
|     a2dp_sbc_t *config = (a2dp_sbc_t *) config_buffer; | ||||
|     const a2dp_sbc_t *capabilities = (const a2dp_sbc_t *) capabilities_buffer; | ||||
|     int i; | ||||
|  | @ -519,7 +519,7 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample | |||
| 
 | ||||
|     /* Find the lowest freq that is at least as high as the requested sampling rate */ | ||||
|     for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++) | ||||
|         if (freq_table[i].rate >= default_sample_spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|         if (freq_table[i].rate >= spec->rate && (capabilities->frequency & freq_table[i].cap)) { | ||||
|             config->frequency = freq_table[i].cap; | ||||
|             break; | ||||
|         } | ||||
|  | @ -540,7 +540,7 @@ static uint8_t fill_preferred_configuration(const pa_sample_spec *default_sample | |||
| 
 | ||||
|     pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table)); | ||||
| 
 | ||||
|     if (default_sample_spec->channels <= 1) { | ||||
|     if (spec->channels <= 1) { | ||||
|         if (capabilities->channel_mode & SBC_CHANNEL_MODE_MONO) | ||||
|             config->channel_mode = SBC_CHANNEL_MODE_MONO; | ||||
|         else if (capabilities->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) | ||||
|  |  | |||
|  | @ -447,7 +447,6 @@ bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_ | |||
|     uint8_t config[MAX_A2DP_CAPS_SIZE]; | ||||
|     uint8_t config_size; | ||||
|     bool is_a2dp_sink; | ||||
|     pa_hashmap *all_endpoints; | ||||
|     char *pa_endpoint; | ||||
|     const char *endpoint; | ||||
| 
 | ||||
|  | @ -462,13 +461,8 @@ bool pa_bluetooth_device_switch_codec(pa_bluetooth_device *device, pa_bluetooth_ | |||
| 
 | ||||
|     is_a2dp_sink = profile == PA_BLUETOOTH_PROFILE_A2DP_SINK; | ||||
| 
 | ||||
|     all_endpoints = NULL; | ||||
|     all_endpoints = pa_hashmap_get(is_a2dp_sink ? device->a2dp_sink_endpoints : device->a2dp_source_endpoints, | ||||
|             &endpoint_conf->id); | ||||
|     pa_assert(all_endpoints); | ||||
| 
 | ||||
|     pa_assert_se(endpoint = endpoint_conf->choose_remote_endpoint(capabilities_hashmap, &device->discovery->core->default_sample_spec, is_a2dp_sink)); | ||||
|     pa_assert_se(capabilities = pa_hashmap_get(all_endpoints, endpoint)); | ||||
|     pa_assert_se(capabilities = pa_hashmap_get(capabilities_hashmap, endpoint)); | ||||
| 
 | ||||
|     config_size = endpoint_conf->fill_preferred_configuration(&device->discovery->core->default_sample_spec, | ||||
|             capabilities->buffer, capabilities->size, config); | ||||
|  |  | |||
|  | @ -893,6 +893,13 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_ | |||
|             if (!PA_SOURCE_IS_OPENED(s->thread_info.state)) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Ignore the transition if the source is suspended for internal reasons
 | ||||
|              * such as external source-output changes resulting in pa_source_reconfigure. | ||||
|              * Logic in our handler decides whether to release the transport or not. | ||||
|              */ | ||||
|             if (new_suspend_cause & PA_SUSPEND_INTERNAL) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Stop the device if the sink is suspended as well */ | ||||
|             if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) | ||||
|                 transport_release(u); | ||||
|  | @ -910,6 +917,9 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_ | |||
|             if (s->thread_info.state != PA_SOURCE_SUSPENDED) | ||||
|                 break; | ||||
| 
 | ||||
|             if (s->suspend_cause & PA_SUSPEND_INTERNAL) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Resume the device if the sink was suspended as well */ | ||||
|             if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) | ||||
|                 if (!setup_transport_and_stream(u)) | ||||
|  | @ -929,6 +939,24 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_ | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
| static void source_reconfigure_cb(pa_source *s, pa_sample_spec *spec, bool passthrough) { | ||||
|     struct userdata *u; | ||||
| 
 | ||||
|     pa_assert(s); | ||||
|     pa_assert_se(u = s->userdata); | ||||
| 
 | ||||
|     if (!pa_sample_spec_equal(spec, &u->decoder_sample_spec)) { | ||||
|         pa_log_warn("Reconfiguration not yet supported"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     pa_source_set_sample_format(u->source, spec->format); | ||||
|     pa_source_set_sample_rate(u->source, spec->rate); | ||||
|     // Does not allow reconfiguring channels yet.
 | ||||
|     pa_assert(u->source->sample_spec.channels == spec->channels); | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
| static void source_set_volume_cb(pa_source *s) { | ||||
|     pa_volume_t volume; | ||||
|  | @ -1032,6 +1060,7 @@ static int add_source(struct userdata *u) { | |||
|     if (u->bt_codec) | ||||
|         pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name); | ||||
|     pa_source_new_data_set_sample_spec(&data, &u->decoder_sample_spec); | ||||
|     pa_source_new_data_set_avoid_resampling(&data, true); | ||||
|     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS | ||||
|         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) | ||||
|         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); | ||||
|  | @ -1075,6 +1104,7 @@ static int add_source(struct userdata *u) { | |||
|     u->source->userdata = u; | ||||
|     u->source->parent.process_msg = source_process_msg; | ||||
|     u->source->set_state_in_io_thread = source_set_state_in_io_thread_cb; | ||||
|     u->source->reconfigure = source_reconfigure_cb; | ||||
| 
 | ||||
|     source_setup_volume_callback(u->source); | ||||
| 
 | ||||
|  | @ -1086,7 +1116,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse | |||
|     struct userdata *u = PA_SINK(o)->userdata; | ||||
| 
 | ||||
|     pa_assert(u->sink == PA_SINK(o)); | ||||
|     pa_assert(u->transport); | ||||
| 
 | ||||
|     switch (code) { | ||||
| 
 | ||||
|  | @ -1116,6 +1145,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse | |||
|         } | ||||
| 
 | ||||
|         case PA_SINK_MESSAGE_SETUP_STREAM: | ||||
|             pa_assert(u->transport); | ||||
| 
 | ||||
|             /* Skip stream setup if stream_fd has been invalidated.
 | ||||
|                This can occur if the stream has already been set up and | ||||
|                then immediately received POLLHUP. If the stream has | ||||
|  | @ -1147,6 +1178,13 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, | |||
|             if (!PA_SINK_IS_OPENED(s->thread_info.state)) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Ignore the transition if the sink is suspended for internal reasons
 | ||||
|              * such as external sink-input changes resulting in pa_sink_reconfigure. | ||||
|              * Logic in our handler decides whether to release the transport or not. | ||||
|              */ | ||||
|             if (new_suspend_cause & PA_SUSPEND_INTERNAL) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Stop the device if the source is suspended as well */ | ||||
|             if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) | ||||
|                 /* We deliberately ignore whether stopping
 | ||||
|  | @ -1161,6 +1199,9 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, | |||
|             if (s->thread_info.state != PA_SINK_SUSPENDED) | ||||
|                 break; | ||||
| 
 | ||||
|             if (s->suspend_cause & PA_SUSPEND_INTERNAL) | ||||
|                 break; | ||||
| 
 | ||||
|             /* Resume the device if the source was suspended as well */ | ||||
|             if (!u->source || !PA_SOURCE_IS_OPENED(u->source->thread_info.state)) | ||||
|                 if (!setup_transport_and_stream(u)) | ||||
|  | @ -1177,6 +1218,24 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state, | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
| static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrough) { | ||||
|     struct userdata *u; | ||||
| 
 | ||||
|     pa_assert(s); | ||||
|     pa_assert_se(u = s->userdata); | ||||
| 
 | ||||
|     if (!pa_sample_spec_equal(spec, &u->encoder_sample_spec)) { | ||||
|         pa_log_warn("Reconfiguration not yet supported"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     pa_sink_set_sample_format(u->sink, spec->format); | ||||
|     pa_sink_set_sample_rate(u->sink, spec->rate); | ||||
|     // Does not allow reconfiguring channels yet.
 | ||||
|     pa_assert(u->sink->sample_spec.channels == spec->channels); | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
| static void sink_set_volume_cb(pa_sink *s) { | ||||
|     pa_volume_t volume; | ||||
|  | @ -1282,6 +1341,7 @@ static int add_sink(struct userdata *u) { | |||
|     if (u->bt_codec) | ||||
|         pa_proplist_sets(data.proplist, PA_PROP_BLUETOOTH_CODEC, u->bt_codec->name); | ||||
|     pa_sink_new_data_set_sample_spec(&data, &u->encoder_sample_spec); | ||||
|     pa_sink_new_data_set_avoid_resampling(&data, true); | ||||
|     if (u->profile == PA_BLUETOOTH_PROFILE_HSP_HS | ||||
|         || u->profile == PA_BLUETOOTH_PROFILE_HFP_HF) | ||||
|         pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone"); | ||||
|  | @ -1321,6 +1381,7 @@ static int add_sink(struct userdata *u) { | |||
|     u->sink->userdata = u; | ||||
|     u->sink->parent.process_msg = sink_process_msg; | ||||
|     u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb; | ||||
|     u->sink->reconfigure = sink_reconfigure_cb; | ||||
| 
 | ||||
|     sink_setup_volume_callback(u->sink); | ||||
| 
 | ||||
|  | @ -1815,6 +1876,37 @@ static int start_thread(struct userdata *u) { | |||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static void release_codec(struct userdata *u) { | ||||
|     if (u->bt_codec) { | ||||
|         if (u->encoder_info) { | ||||
|             u->bt_codec->deinit(u->encoder_info); | ||||
|             u->encoder_info = NULL; | ||||
|         } | ||||
| 
 | ||||
|         if (u->decoder_info) { | ||||
|             u->bt_codec->deinit(u->decoder_info); | ||||
|             u->decoder_info = NULL; | ||||
|         } | ||||
| 
 | ||||
|         u->bt_codec = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (u->encoder_buffer) { | ||||
|         pa_xfree(u->encoder_buffer); | ||||
|         u->encoder_buffer = NULL; | ||||
|     } | ||||
| 
 | ||||
|     u->encoder_buffer_size = 0; | ||||
|     u->encoder_buffer_used = 0; | ||||
| 
 | ||||
|     if (u->decoder_buffer) { | ||||
|         pa_xfree(u->decoder_buffer); | ||||
|         u->decoder_buffer = NULL; | ||||
|     } | ||||
| 
 | ||||
|     u->decoder_buffer_size = 0; | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
| static void stop_thread(struct userdata *u) { | ||||
|     pa_assert(u); | ||||
|  | @ -1879,34 +1971,7 @@ static void stop_thread(struct userdata *u) { | |||
|         u->read_smoother = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (u->bt_codec) { | ||||
|         if (u->encoder_info) { | ||||
|             u->bt_codec->deinit(u->encoder_info); | ||||
|             u->encoder_info = NULL; | ||||
|         } | ||||
| 
 | ||||
|         if (u->decoder_info) { | ||||
|             u->bt_codec->deinit(u->decoder_info); | ||||
|             u->decoder_info = NULL; | ||||
|         } | ||||
| 
 | ||||
|         u->bt_codec = NULL; | ||||
|     } | ||||
| 
 | ||||
|     if (u->encoder_buffer) { | ||||
|         pa_xfree(u->encoder_buffer); | ||||
|         u->encoder_buffer = NULL; | ||||
|     } | ||||
| 
 | ||||
|     u->encoder_buffer_size = 0; | ||||
|     u->encoder_buffer_used = 0; | ||||
| 
 | ||||
|     if (u->decoder_buffer) { | ||||
|         pa_xfree(u->decoder_buffer); | ||||
|         u->decoder_buffer = NULL; | ||||
|     } | ||||
| 
 | ||||
|     u->decoder_buffer_size = 0; | ||||
|     release_codec(u); | ||||
| } | ||||
| 
 | ||||
| /* Run from main thread */ | ||||
|  | @ -2332,11 +2397,13 @@ static void handle_transport_state_change(struct userdata *u, struct pa_bluetoot | |||
| 
 | ||||
|     oldavail = cp->available; | ||||
|     /*
 | ||||
|      * If codec switching is in progress, transport state change should not | ||||
|      * make profile unavailable. | ||||
|      * If codec switching is in progress, do not acquire the transport until our | ||||
|      * "codec switch complete" callback is called, which calls transport_config | ||||
|      * in addition to just acquiring it (through setup_transport). That means it | ||||
|      * also has to apply any eventual format changes back to the pa_sink/source. | ||||
|      */ | ||||
|     if (!t->device->codec_switching_in_progress) | ||||
|         pa_card_profile_set_available(cp, transport_state_to_availability(t->state)); | ||||
|     if (t->device->codec_switching_in_progress) | ||||
|         return; | ||||
| 
 | ||||
|     /* Update port availability */ | ||||
|     pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name)); | ||||
|  | @ -2439,12 +2506,15 @@ static pa_hook_result_t transport_state_changed_cb(pa_bluetooth_discovery *y, pa | |||
|     pa_assert(t); | ||||
|     pa_assert(u); | ||||
| 
 | ||||
|     if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) | ||||
|     if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED && !t->device->codec_switching_in_progress) | ||||
|         pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); | ||||
| 
 | ||||
|     if (t->device == u->device) | ||||
|         handle_transport_state_change(u, t); | ||||
| 
 | ||||
|     if (t == u->transport && t->state <= PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED) | ||||
|         u->transport = NULL; | ||||
| 
 | ||||
|     return PA_HOOK_OK; | ||||
| } | ||||
| 
 | ||||
|  | @ -2509,24 +2579,73 @@ static char* make_message_handler_path(const char *name) { | |||
|     return pa_sprintf_malloc("/card/%s/bluez", name); | ||||
| } | ||||
| 
 | ||||
| static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile, void *userdata) | ||||
| { | ||||
| static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile, void *userdata) { | ||||
|     struct userdata *u = (struct userdata *) userdata; | ||||
|     pa_sample_spec *spec; | ||||
|     bool is_a2dp_sink; | ||||
|     int r; | ||||
|     pa_sink_input *i; | ||||
|     pa_source_output *o; | ||||
|     uint32_t idx; | ||||
| 
 | ||||
|     if (!success) | ||||
|         goto off; | ||||
| 
 | ||||
|     u->profile = profile; | ||||
|     is_a2dp_sink = u->profile == PA_BLUETOOTH_PROFILE_A2DP_SINK; | ||||
| 
 | ||||
|     if (init_profile(u) < 0) { | ||||
|         pa_log_info("Failed to initialise profile after codec switching"); | ||||
|         goto off; | ||||
|     // Reacquire the transport and configure the codec
 | ||||
|     r = setup_transport(u); | ||||
|     pa_assert(!r); | ||||
|     // if (r == -EINPROGRESS)
 | ||||
|     //     return 0;
 | ||||
|     // else if (r < 0)
 | ||||
|     //     return -1;
 | ||||
| 
 | ||||
|     spec = is_a2dp_sink ? &u->encoder_sample_spec : &u->decoder_sample_spec; | ||||
| 
 | ||||
|     // Finish off by reconfiguring
 | ||||
| 
 | ||||
|     if (is_a2dp_sink) { | ||||
|         PA_IDXSET_FOREACH(i, u->sink->inputs, idx) | ||||
|             pa_sink_input_cork(i, true); | ||||
|         if (u->sink->monitor_source) { | ||||
|             pa_source_suspend(u->sink->monitor_source, true, PA_SUSPEND_INTERNAL); | ||||
|             PA_IDXSET_FOREACH(o, u->sink->monitor_source->outputs, idx) | ||||
|                 pa_source_output_cork(o, true); | ||||
|         } | ||||
|         pa_sink_reconfigure(u->sink, spec, false); | ||||
|         if (u->sink->monitor_source) { | ||||
|             PA_IDXSET_FOREACH(o, u->sink->monitor_source->outputs, idx) | ||||
|                 pa_source_output_cork(o, false); | ||||
|             pa_source_suspend(u->sink->monitor_source, false, PA_SUSPEND_INTERNAL); | ||||
|         } | ||||
|         PA_IDXSET_FOREACH(i, u->sink->inputs, idx) | ||||
|             pa_sink_input_cork(i, false); | ||||
| 
 | ||||
|     if (u->sink || u->source) | ||||
|         if (start_thread(u) < 0) { | ||||
|             pa_log_info("Failed to start thread after codec switching"); | ||||
|             goto off; | ||||
|         pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_SETUP_STREAM, NULL, 0, NULL); | ||||
| 
 | ||||
|         pa_sink_suspend(u->sink, false, PA_SUSPEND_UNAVAILABLE); | ||||
|     } else { | ||||
|         PA_IDXSET_FOREACH(o, u->source->outputs, idx) | ||||
|             pa_source_output_cork(o, true); | ||||
|         if (u->source->monitor_of) { | ||||
|             pa_sink_suspend(u->source->monitor_of, true, PA_SUSPEND_INTERNAL); | ||||
|             PA_IDXSET_FOREACH(i, u->source->monitor_of->inputs, idx) | ||||
|                 pa_sink_input_cork(i, true); | ||||
|         } | ||||
|         pa_source_reconfigure(u->source, spec, false); | ||||
|         if (u->source->monitor_of) { | ||||
|             PA_IDXSET_FOREACH(i, u->source->monitor_of->inputs, idx) | ||||
|                 pa_sink_input_cork(i, false); | ||||
|             pa_sink_suspend(u->source->monitor_of, false, PA_SUSPEND_INTERNAL); | ||||
|         } | ||||
|         PA_IDXSET_FOREACH(o, u->source->outputs, idx) | ||||
|             pa_source_output_cork(o, false); | ||||
| 
 | ||||
|         pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_SETUP_STREAM, NULL, 0, NULL); | ||||
| 
 | ||||
|         pa_source_suspend(u->source, false, PA_SUSPEND_UNAVAILABLE); | ||||
|     } | ||||
| 
 | ||||
|     pa_log_info("Codec successfully switched to %s with profile: %s", | ||||
|  | @ -2535,6 +2654,7 @@ static void switch_codec_cb_handler(bool success, pa_bluetooth_profile_t profile | |||
|     return; | ||||
| 
 | ||||
| off: | ||||
|     stop_thread(u); | ||||
|     pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); | ||||
| } | ||||
| 
 | ||||
|  | @ -2698,7 +2818,15 @@ static int bluez5_device_message_handler(const char *object_path, const char *me | |||
|          */ | ||||
|         profile = u->profile; | ||||
| 
 | ||||
|         stop_thread(u); | ||||
|         // Suspending releases the transport
 | ||||
| 
 | ||||
|         if (is_a2dp_sink) | ||||
|             pa_sink_suspend(u->sink, true, PA_SUSPEND_UNAVAILABLE); | ||||
|         else | ||||
|             pa_source_suspend(u->source, true, PA_SUSPEND_UNAVAILABLE); | ||||
| 
 | ||||
|         // TODO: Can we do this if it's synchronous?
 | ||||
|         release_codec(u); | ||||
| 
 | ||||
|         if (!pa_bluetooth_device_switch_codec(u->device, profile, capabilities_hashmap, endpoint_conf, switch_codec_cb_handler, userdata) | ||||
|                 && !u->device->codec_switching_in_progress) | ||||
|  | @ -2726,6 +2854,7 @@ static int bluez5_device_message_handler(const char *object_path, const char *me | |||
|     return -PA_ERR_NOTIMPLEMENTED; | ||||
| 
 | ||||
| profile_off: | ||||
|     stop_thread(u); | ||||
|     pa_assert_se(pa_card_set_profile(u->card, pa_hashmap_get(u->card->profiles, "off"), false) >= 0); | ||||
| 
 | ||||
|     return -PA_ERR_IO; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Marijn Suijten
						Marijn Suijten