diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ff8cc8d..941d9e662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - GATT Client: stop timer on disconnect - fixes use after free / crash - L2CAP: Use valid signaling identifier for L2CAP Connection Parameter Update Request +### Added +- A2DP Source: Support stream reconfiguration (a2dp_source_reconfigure_stream_sampling_frequency) + ## Changes August 2018 ### Added diff --git a/example/a2dp_source_demo.c b/example/a2dp_source_demo.c index 9c0c27a47..d862cea74 100644 --- a/example/a2dp_source_demo.c +++ b/example/a2dp_source_demo.c @@ -213,9 +213,9 @@ typedef struct { static const char title[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; avrcp_track_t tracks[] = { - {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, 1, "Sine", "Generated", "AVRCP Demo", "monotone", 12345}, - {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, 2, "Nao-deceased", "Decease", "AVRCP Demo", "vivid", 12345}, - {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, 3, (char *)title, "Decease", "AVRCP Demo", "vivid", 12345}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, 1, "Sine", "Generated", "A2DP Source Demo", "monotone", 12345}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, 2, "Nao-deceased", "Decease", "A2DP Source Demo", "vivid", 12345}, + {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, 3, (char *)title, "Decease", "A2DP Source Demo", "vivid", 12345}, }; int current_track_index; avrcp_play_status_info_t play_info; @@ -432,7 +432,7 @@ static void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, ui #ifndef HAVE_BTSTACK_STDIN if (hci_event_packet_get_type(packet) == BTSTACK_EVENT_STATE){ if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) return; - printf("Create AVDTP Source connection to addr %s.\n", bd_addr_to_str(device_addr)); + printf("Create A2DP Source connection to addr %s.\n", bd_addr_to_str(device_addr)); status = a2dp_source_establish_stream(device_addr, media_tracker.local_seid, &media_tracker.a2dp_cid); if (status != ERROR_CODE_SUCCESS){ printf("Could not perform command, status 0x%2x\n", status); @@ -448,7 +448,10 @@ static void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, ui } if (hci_event_packet_get_type(packet) != HCI_EVENT_A2DP_META) return; - switch (packet[2]){ + + printf("A2DP Meta %x\n", hci_event_a2dp_meta_get_subevent_code(packet)); + + switch (hci_event_a2dp_meta_get_subevent_code(packet)){ case A2DP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED: a2dp_subevent_signaling_connection_established_get_bd_addr(packet, address); cid = a2dp_subevent_signaling_connection_established_get_a2dp_cid(packet); @@ -464,7 +467,6 @@ static void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, ui break; case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION:{ - printf("A2DP Source: Received SBC codec configuration.\n"); sbc_configuration.reconfigure = a2dp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet); sbc_configuration.num_channels = a2dp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet); sbc_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet); @@ -475,17 +477,13 @@ static void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, ui sbc_configuration.min_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet); sbc_configuration.max_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet); sbc_configuration.frames_per_buffer = sbc_configuration.subbands * sbc_configuration.block_length; + printf("A2DP Source: Received SBC codec configuration, sampling frequency %u.\n", sbc_configuration.sampling_frequency); btstack_sbc_encoder_init(&sbc_encoder_state, SBC_MODE_STANDARD, sbc_configuration.block_length, sbc_configuration.subbands, sbc_configuration.allocation_method, sbc_configuration.sampling_frequency, sbc_configuration.max_bitpool_value, sbc_configuration.channel_mode); - - // status = a2dp_source_establish_stream(device_addr, media_tracker.local_seid, &media_tracker.a2dp_cid); - // if (status != ERROR_CODE_SUCCESS){ - // printf("Could not perform command, status 0x%2x\n", status); - // } break; } @@ -503,12 +501,16 @@ static void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, ui } printf("A2DP Source: Stream established, address %s, a2dp cid 0x%02x, local seid %d, remote seid %d.\n", bd_addr_to_str(address), media_tracker.a2dp_cid, media_tracker.local_seid, a2dp_subevent_stream_established_get_remote_seid(packet)); - printf("A2DP Source: Start playing mod, a2dp cid 0x%02x.\n", media_tracker.a2dp_cid); media_tracker.stream_opened = 1; data_source = STREAM_MOD; status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid); break; + case A2DP_SUBEVENT_STREAM_RECONFIGURED: + status = a2dp_subevent_stream_reconfigured_get_status(packet); + printf("A2DP Source: Reconfigured, status 0x%02x\n", status); + break; + case A2DP_SUBEVENT_STREAM_STARTED: play_info.status = AVRCP_PLAYBACK_STATUS_PLAYING; if (media_tracker.avrcp_cid){ @@ -643,8 +645,8 @@ static void show_usage(void){ bd_addr_t iut_address; gap_local_bd_addr(iut_address); printf("\n--- Bluetooth A2DP Source/AVRCP Target Demo %s ---\n", bd_addr_to_str(iut_address)); - printf("b - AVDTP Source create connection to addr %s\n", device_addr_string); - printf("B - AVDTP Source disconnect\n"); + printf("b - A2DP Source create connection to addr %s\n", device_addr_string); + printf("B - A2DP Source disconnect\n"); printf("c - AVRCP Target create connection to addr %s\n", device_addr_string); printf("C - AVRCP Target disconnect\n"); @@ -653,6 +655,8 @@ static void show_usage(void){ printf("z - start streaming '%s'\n", mod_name); } printf("p - pause streaming\n"); + printf("w - reconfigure stream for 44100 Hz\n"); + printf("e - reconfigure stream for 48000 Hz\n"); printf("\n--- Bluetooth AVRCP Target Commands %s ---\n", bd_addr_to_str(iut_address)); printf("---\n"); @@ -663,10 +667,10 @@ static void stdin_process(char cmd){ switch (cmd){ case 'b': status = a2dp_source_establish_stream(device_addr, media_tracker.local_seid, &media_tracker.a2dp_cid); - printf("%c - Create AVDTP Source connection to addr %s, cid 0x%02x.\n", cmd, bd_addr_to_str(device_addr), media_tracker.a2dp_cid); + printf("%c - Create A2DP Source connection to addr %s, cid 0x%02x.\n", cmd, bd_addr_to_str(device_addr), media_tracker.a2dp_cid); break; case 'B': - printf("%c - AVDTP Source Disconnect from cid 0x%2x\n", cmd, media_tracker.a2dp_cid); + printf("%c - A2DP Source Disconnect from cid 0x%2x\n", cmd, media_tracker.a2dp_cid); status = a2dp_source_disconnect(media_tracker.a2dp_cid); break; case 'c': @@ -707,6 +711,26 @@ static void stdin_process(char cmd){ status = a2dp_source_pause_stream(media_tracker.a2dp_cid, media_tracker.local_seid); break; + case 'w': + if (!media_tracker.stream_opened) break; + if (play_info.status == AVRCP_PLAYBACK_STATUS_PLAYING){ + printf("Stream cannot be reconfigured while playing, please pause stream first\n"); + break; + } + printf("%c - Reconfigure for 44100 Hz.\n", cmd); + status = a2dp_source_reconfigure_stream_sampling_frequency(media_tracker.a2dp_cid, 44100); + break; + + case 'e': + if (!media_tracker.stream_opened) break; + if (play_info.status == AVRCP_PLAYBACK_STATUS_PLAYING){ + printf("Stream cannot be reconfigured while playing, please pause stream first\n"); + break; + } + printf("%c - Reconfigure for 48000 Hz.\n", cmd); + status = a2dp_source_reconfigure_stream_sampling_frequency(media_tracker.a2dp_cid, 48000); + break; + default: show_usage(); return; diff --git a/src/classic/a2dp_source.c b/src/classic/a2dp_source.c index 5e2748e51..d4b213ba5 100644 --- a/src/classic/a2dp_source.c +++ b/src/classic/a2dp_source.c @@ -39,7 +39,6 @@ #define __BTSTACK_FILE__ "a2dp_source.c" #include -#include #include #include @@ -200,6 +199,20 @@ static void a2dp_signaling_emit_control_command(btstack_packet_handler_t callbac (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); } +static void a2dp_signaling_emit_reconfigured(btstack_packet_handler_t callback, uint16_t cid, uint8_t local_seid, uint8_t status){ + if (!callback) return; + uint8_t event[7]; + int pos = 0; + event[pos++] = HCI_EVENT_A2DP_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = A2DP_SUBEVENT_STREAM_RECONFIGURED; + little_endian_store_16(event, pos, cid); + pos += 2; + event[pos++] = local_seid; + event[pos++] = status; + (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(channel); UNUSED(size); @@ -365,6 +378,11 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe avdtp_source_set_configuration(cid, avdtp_stream_endpoint_seid(sc.local_stream_endpoint), sc.active_remote_sep->seid, sc.local_stream_endpoint->remote_configuration_bitmap, sc.local_stream_endpoint->remote_configuration); break; } + case A2DP_W2_RECONFIGURE_WITH_SEID: + log_info("A2DP reconfigured"); + a2dp_signaling_emit_reconfigured(a2dp_source_context.a2dp_callback, cid, avdtp_stream_endpoint_seid(sc.local_stream_endpoint), 0); + app_state = A2DP_STREAMING_OPENED; + break; case A2DP_W2_OPEN_STREAM_WITH_SEID:{ log_info("A2DP open stream "); app_state = A2DP_W4_OPEN_STREAM_WITH_SEID; @@ -474,6 +492,51 @@ uint8_t a2dp_source_disconnect(uint16_t a2dp_cid){ return avdtp_disconnect(a2dp_cid, &a2dp_source_context); } +uint8_t a2dp_source_reconfigure_stream_sampling_frequency(uint16_t a2dp_cid, uint32_t sampling_frequency){ + // UNUSED(sampling_frequency); + + log_info("a2dp_source_reconfigure_stream"); + + memcpy(sc.local_stream_endpoint->reconfigure_media_codec_sbc_info, sc.local_stream_endpoint->remote_sep.configuration.media_codec.media_codec_information, 4); + + // update sampling frequency + uint8_t config = sc.local_stream_endpoint->reconfigure_media_codec_sbc_info[0] & 0x0f; + switch (sampling_frequency){ + case 48000: + config |= (AVDTP_SBC_48000 << 4); + break; + case 44100: + config |= (AVDTP_SBC_44100 << 4); + break; + case 32000: + config |= (AVDTP_SBC_32000 << 4); + break; + case 16000: + config |= (AVDTP_SBC_16000 << 4); + break; + default: + log_error("Unsupported sampling frequency %u", sampling_frequency); + return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE; + } + sc.local_stream_endpoint->reconfigure_media_codec_sbc_info[0] = config; + + avdtp_capabilities_t new_configuration; + new_configuration.media_codec.media_type = AVDTP_AUDIO; + new_configuration.media_codec.media_codec_type = AVDTP_CODEC_SBC; + new_configuration.media_codec.media_codec_information_len = 4; + new_configuration.media_codec.media_codec_information = sc.local_stream_endpoint->reconfigure_media_codec_sbc_info; + + // sttart reconfigure + app_state = A2DP_W2_RECONFIGURE_WITH_SEID; + return avdtp_source_reconfigure( + a2dp_cid, + avdtp_stream_endpoint_seid(sc.local_stream_endpoint), + sc.active_remote_sep->seid, + 1 << AVDTP_MEDIA_CODEC, + new_configuration + ); +} + uint8_t a2dp_source_start_stream(uint16_t a2dp_cid, uint8_t local_seid){ return avdtp_start_stream(a2dp_cid, local_seid, &a2dp_source_context); } diff --git a/src/classic/a2dp_source.h b/src/classic/a2dp_source.h index 05d8882a0..616cb3d52 100644 --- a/src/classic/a2dp_source.h +++ b/src/classic/a2dp_source.h @@ -47,7 +47,7 @@ #define __A2DP_SOURCE_H #include -#include "avdtp.h" +#include "classic/avdtp.h" #if defined __cplusplus extern "C" { @@ -109,6 +109,13 @@ void a2dp_source_register_packet_handler(btstack_packet_handler_t callback); */ uint8_t a2dp_source_establish_stream(bd_addr_t remote, uint8_t local_seid, uint16_t * out_a2dp_cid); + /** + * @brief Reconfigure stream. + * @param local_seid ID assigned to a local stream endpoint + * @param sampling_frequency New sampling frequency to use. Cannot be called while stream is active + */ +uint8_t a2dp_source_reconfigure_stream_sampling_frequency(uint16_t a2dp_cid, uint32_t sampling_frequency); + /** * @brief Start stream. * @param a2dp_cid A2DP channel identifyer.