/* * Copyright (C) 2016 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * 4. Any redistribution, use, or modification is done solely for * personal benefit and not for any commercial purpose or for * monetary gain. * * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Please inquire about commercial licensing options at * contact@bluekitchen-gmbh.com * */ #define BTSTACK_FILE__ "avdtp_sink_test.c" /* * avdtp_sink_test.c : Tool for testig AVDTP sink with PTS, see avdtp_sink_test.md and a2dp_sink.md for PTS tests command sequences */ #include #include #include #include #include #include "btstack.h" #include "wav_util.h" #include "btstack_resample.h" #include "btstack_ring_buffer.h" #include "wav_util.h" #ifdef HAVE_AAC_FDK #include #endif #ifdef HAVE_APTX #include #endif #define A2DP_CODEC_VENDOR_ID_APT_LTD 0x4f #define A2DP_CODEC_VENDOR_ID_QUALCOMM 0xd7 #define A2DP_APT_LTD_CODEC_APTX 0x1 #define A2DP_QUALCOMM_CODEC_APTX_HD 0x24 #ifdef HAVE_LDAC_DECODER #include #endif #define A2DP_CODEC_VENDOR_ID_SONY 0x12d #define A2DP_SONY_CODEC_LDAC 0xaa // // Configuration // #define NUM_CHANNELS 2 #define BYTES_PER_FRAME (2*NUM_CHANNELS) // Playback #define SAMPLE_RATE 48000 #define PREBUFFER_MS 100 #define AUDIO_BUFFER_SIZE (AUDIO_BUFFER_MS*SAMPLE_RATE/BYTES_PER_FRAME) #define AUDIO_BUFFER_MS 200 #define MAX_SBC_FRAME_SIZE 120 // ring buffer for SBC Frames // below 30: add samples, 30-40: fine, above 40: drop samples #define OPTIMAL_FRAMES_MIN 30 #define OPTIMAL_FRAMES_MAX 40 #define ADDITIONAL_FRAMES 20 static const char * wav_filename = "avdtp_sink_test.wav"; typedef struct { // bitmaps uint8_t sampling_frequency_bitmap; uint8_t channel_mode_bitmap; uint8_t block_length_bitmap; uint8_t subbands_bitmap; uint8_t allocation_method_bitmap; uint8_t min_bitpool_value; uint8_t max_bitpool_value; } adtvp_media_codec_information_sbc_t; typedef struct { int reconfigure; int num_channels; int sampling_frequency; int channel_mode; int block_length; int subbands; int allocation_method; int min_bitpool_value; int max_bitpool_value; } avdtp_media_codec_configuration_sbc_t; typedef struct { int reconfigure; int sampling_frequency; int aot; int bitrate; int channel_mode; int vbr; } avdtp_media_codec_configuration_aac_t; typedef struct { int reconfigure; int channel_mode; int num_channels; int sampling_frequency; } avdtp_media_codec_configuration_aptx_t; typedef struct { int reconfigure; int channel_mode; int num_channels; int num_samples; int sampling_frequency; } avdtp_media_codec_configuration_ldac_t; typedef struct { uint8_t num_channels; uint32_t sampling_frequency; } playback_configuration_t; // REMOTE Device // mac 2011: static const char * device_addr_string = "04:0C:CE:E4:85:D3"; // mac 2013: static const char * device_addr_string = "84:38:35:65:d1:15"; // phone 2013: static const char * device_addr_string = "D8:BB:2C:DF:F0:F2"; // minijambox: static const char * device_addr_string = "00:21:3C:AC:F7:38"; // head phones: static const char * device_addr_string = "00:18:09:28:50:18"; // bt dongle: static const char * device_addr_string = "00:15:83:5F:9D:46"; static const char * device_addr_string = "00:1B:DC:08:E2:5C"; static bd_addr_t device_addr; static uint8_t sdp_avdtp_sink_service_buffer[150]; // stack stuff static btstack_packet_callback_registration_t hci_event_callback_registration; static uint16_t avdtp_cid; static uint8_t local_seid; static uint8_t remote_seid; static uint8_t is_cmd_triggered_locally = 0; static uint8_t is_media_header_reported_once = 0; // playback static int playback_initialized = 0; static bool playback_active = false; static playback_configuration_t playback_configuration; // large audio buffer to store up to AUDIO_MS of audio data static uint8_t decoded_audio_storage[AUDIO_BUFFER_SIZE]; static btstack_ring_buffer_t decoded_audio_ring_buffer; // active codec static avdtp_media_codec_type_t codec_type; // SBC static btstack_sbc_decoder_state_t state; static btstack_sbc_mode_t mode = SBC_MODE_STANDARD; static adtvp_media_codec_information_sbc_t sbc_capability; static avdtp_media_codec_configuration_sbc_t sbc_configuration; static uint8_t local_seid_sbc; // AAC #ifdef HAVE_AAC_FDK // AAC handle static HANDLE_AACDECODER aac_handle; static avdtp_media_codec_configuration_aac_t aac_configuration; static uint8_t local_seid_aac; #endif // APTX #ifdef HAVE_APTX static struct aptx_context *aptx_handle; static avdtp_media_codec_configuration_aptx_t aptx_configuration; static uint8_t local_seid_aptx; static avdtp_media_codec_configuration_aptx_t aptxhd_configuration; static uint8_t local_seid_aptxhd; #endif // LDAC #ifdef HAVE_LDAC_DECODER static ldacdec_t ldac_handle; static avdtp_media_codec_configuration_ldac_t ldac_configuration; static uint8_t local_seid_ldac; #endif static uint32_t vendor_id; static uint16_t codec_id; static uint16_t remote_configuration_bitmap; static avdtp_capabilities_t remote_configuration; static bool delay_reporting_supported_on_remote = false; static uint8_t num_remote_seids; static uint8_t first_remote_seid; // prototypes static int read_media_data_header(uint8_t * packet, uint16_t size, uint16_t * offset, avdtp_media_packet_header_t * media_header); static int read_sbc_header(uint8_t * packet, uint16_t size, uint16_t * offset, avdtp_sbc_codec_header_t * sbc_header); // Playback: PCM ring buffer static void playback_handler(int16_t * buffer, uint16_t num_audio_frames){ int wav_samples = num_audio_frames * playback_configuration.num_channels; uint32_t bytes_available = btstack_ring_buffer_bytes_available(&decoded_audio_ring_buffer); uint32_t bytes_requested = wav_samples * 2; uint32_t bytes_start = playback_configuration.sampling_frequency * PREBUFFER_MS / 1000 * playback_configuration.num_channels; bool sufficient_data_now = bytes_available > bytes_requested; bool sufficient_data_start = bytes_available > bytes_start; // check for underrun if (playback_active && !sufficient_data_now){ // printf("Playback: Underrun, pause (have %u, need %u\n", bytes_available, bytes_requested); playback_active = false; } // check for start if (!playback_active && sufficient_data_start){ // printf("Playback: start - have %u, start %u\n", bytes_available, bytes_start); playback_active = true; } // play silence if (!playback_active){ playback_active = false; memset(buffer, 0, num_audio_frames * BYTES_PER_FRAME); return; } // read data from ring buffer uint32_t bytes_read; btstack_ring_buffer_read(&decoded_audio_ring_buffer, (uint8_t *) buffer, num_audio_frames * BYTES_PER_FRAME, &bytes_read); } static int playback_init(playback_configuration_t * configuration){ if (playback_initialized) return 0; playback_initialized = 1; playback_active = false; btstack_ring_buffer_init(&decoded_audio_ring_buffer, decoded_audio_storage, sizeof(decoded_audio_storage)); // wav writer printf("WAV Writer: open %s\n", wav_filename); wav_writer_open(wav_filename, configuration->num_channels, configuration->sampling_frequency); // setup audio playback const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance(); if (audio){ printf("Playback: start stream\n"); audio->init(NUM_CHANNELS, configuration->sampling_frequency, &playback_handler); audio->start_stream(); } return 0; } static void playback_start(void){ } static void playback_pause(void){ if (!playback_initialized) return; // discard audio, play silence playback_active = false; btstack_ring_buffer_init(&decoded_audio_ring_buffer, decoded_audio_storage, sizeof(decoded_audio_storage)); } static void playback_close(void){ if (!playback_initialized) return; playback_initialized = 0; playback_active = false; // close wav printf("WAV Writer: close %s\n", wav_filename); wav_writer_close(); // stop audio playback const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance(); if (audio){ printf("PlaybackL: stop stream\n"); audio->close(); } } static void playback_queue_audio(int16_t * data, int num_audio_frames, int num_channels){ if (playback_active == false) { return; } // write to wav file wav_writer_write_int16(num_audio_frames * num_channels, data); // do not write into buffer if audio is not present const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance(); if (audio == NULL){ return; } // store in audio ring buffer int status = btstack_ring_buffer_write(&decoded_audio_ring_buffer, (uint8_t *)data, num_audio_frames * num_channels * 2); if (status){ printf("Error storing samples in PCM ring buffer!!!\n"); } log_info("buffer: store %u frames, have %u bytes", num_audio_frames, btstack_ring_buffer_bytes_available(&decoded_audio_ring_buffer)); } // // SBC Codec // static void dump_sbc_capability(adtvp_media_codec_information_sbc_t capabilities){ printf(" - sampling_frequency: 0x%02x\n", capabilities.sampling_frequency_bitmap); printf(" - channel_mode: 0x%02x\n", capabilities.channel_mode_bitmap); printf(" - block_length: 0x%02x\n", capabilities.block_length_bitmap); printf(" - subbands: 0x%02x\n", capabilities.subbands_bitmap); printf(" - allocation_method: 0x%02x\n", capabilities.allocation_method_bitmap); printf(" - bitpool_value [%d, %d] \n", capabilities.min_bitpool_value, capabilities.max_bitpool_value); } static void dump_sbc_configuration(avdtp_media_codec_configuration_sbc_t configuration){ printf(" - num_channels: %d\n", configuration.num_channels); printf(" - sampling_frequency: %d\n", configuration.sampling_frequency); printf(" - channel_mode: %d\n", configuration.channel_mode); printf(" - block_length: %d\n", configuration.block_length); printf(" - subbands: %d\n", configuration.subbands); printf(" - allocation_method: %d\n", configuration.allocation_method); printf(" - bitpool_value [%d, %d] \n", configuration.min_bitpool_value, configuration.max_bitpool_value); } static int read_sbc_header(uint8_t * packet, uint16_t size, uint16_t * offset, avdtp_sbc_codec_header_t * sbc_header){ int sbc_header_len = 1; // without crc int pos = *offset; if (size - pos < sbc_header_len){ printf("Not enough data to read SBC header, expected %d, received %d\n", sbc_header_len, size-pos); return 0; } sbc_header->fragmentation = get_bit16(packet[pos], 7); sbc_header->starting_packet = get_bit16(packet[pos], 6); sbc_header->last_packet = get_bit16(packet[pos], 5); sbc_header->num_frames = packet[pos] & 0x0f; pos++; // printf("SBC HEADER: num_frames %u, fragmented %u, start %u, stop %u\n", sbc_header.num_frames, sbc_header.fragmentation, sbc_header.starting_packet, sbc_header.last_packet); *offset = pos; return 1; } static void sbc_handle_pcm_data(int16_t * data, int num_audio_frames, int num_channels, int sample_rate, void * context){ UNUSED(sample_rate); UNUSED(context); playback_queue_audio(data, num_audio_frames, num_channels); } static void handle_l2cap_media_data_packet_sbc(uint8_t *packet, uint16_t size){ uint16_t pos = 0; avdtp_media_packet_header_t media_header; if (!read_media_data_header(packet, size, &pos, &media_header)) return; avdtp_sbc_codec_header_t sbc_header; if (!read_sbc_header(packet, size, &pos, &sbc_header)) return; btstack_sbc_decoder_process_data(&state, 0, packet+pos, size-pos); } // AAC using fdk-aac #ifdef HAVE_AAC_FDK static void handle_l2cap_media_data_packet_aac(uint8_t *packet, uint16_t size){ uint16_t pos = 0; avdtp_media_packet_header_t media_header; if (!read_media_data_header(packet, size, &pos, &media_header)) return; int16_t decode_buf[2048 * 2]; // 2 == num_channel uint32_t framesize; AAC_DECODER_ERROR err; UINT uValid; UINT uSize; uSize = size - 12; // RTP header length uValid = uSize; UCHAR *ptr = (UCHAR *) (packet+pos); if ((err = aacDecoder_Fill(aac_handle, &ptr, &uSize, &uValid)) != AAC_DEC_OK) return; if ((err = aacDecoder_DecodeFrame(aac_handle, (INT_PCM *) decode_buf, sizeof(decode_buf) / sizeof(INT_PCM), 0)) != AAC_DEC_OK) { printf("decode error %d\n", err); return; } CStreamInfo *aac_info = aacDecoder_GetStreamInfo(aac_handle); framesize = aac_info->frameSize * 2; // 2 channels playback_queue_audio(decode_buf, aac_info->frameSize, 2); } #endif static void handle_l2cap_media_data_packet_aptx(uint8_t *packet, uint16_t size) { #ifdef HAVE_APTX size_t written; int16_t decode_buf16[2048 * 2]; // 2 == num_channel unsigned char decode_buf8[2048 * 2 * 2]; aptx_decode(aptx_handle, packet, size, decode_buf8, sizeof(decode_buf8), &written); // convert to 16-bit for (int i = 0; i < written/3; i++) { decode_buf16[i] = (decode_buf8[i * 3 + 2] << 8) | decode_buf8[i * 3 + 1]; } playback_queue_audio(decode_buf16, written/(3 * aptx_configuration.num_channels), aptx_configuration.num_channels); #endif } static void handle_l2cap_media_data_packet_aptxhd(uint8_t *packet, uint16_t size) { #ifdef HAVE_APTX uint16_t pos = 0; size_t written; int16_t decode_buf16[2048 * 2]; // 2 == num_channel unsigned char decode_buf8[2048 * 2 * 2]; avdtp_media_packet_header_t media_header; if (!read_media_data_header(packet, size, &pos, &media_header)) return; aptx_decode(aptx_handle, packet+pos, size-12, decode_buf8, sizeof(decode_buf8), &written); // convert to 16-bit for (int i = 0; i < written/3; i++) { decode_buf16[i] = (decode_buf8[i * 3 + 2] << 8) | decode_buf8[i * 3 + 1]; } playback_queue_audio(decode_buf16, written/(3 * aptxhd_configuration.num_channels), aptxhd_configuration.num_channels); #endif } static void handle_l2cap_media_data_packet_ldac(uint8_t *packet, uint16_t size) { #ifdef HAVE_LDAC_DECODER uint16_t pos = 0; int used; int16_t decode_buf16[2048 * 2] = {0}; // 2 == num_channel avdtp_media_packet_header_t media_header; if (!read_media_data_header(packet, size, &pos, &media_header)) return; pos++; // first byte is header while (pos < size) { ldacDecode(&ldac_handle, packet+pos, decode_buf16, &used); playback_queue_audio(decode_buf16, ldac_handle.frame.frameSamples, ldac_configuration.num_channels); pos += used; } #endif } // Codec XXX // // // A2DP Media Payload Processing static int read_media_data_header(uint8_t *packet, u_int16_t size, uint16_t *offset, avdtp_media_packet_header_t *media_header){ int media_header_len = 12; // without crc int pos = *offset; if (size - pos < media_header_len){ printf("Not enough data to read media packet header, expected %d, received %d\n", media_header_len, size-pos); return 0; } media_header->version = packet[pos] & 0x03; media_header->padding = get_bit16(packet[pos],2); media_header->extension = get_bit16(packet[pos],3); media_header->csrc_count = (packet[pos] >> 4) & 0x0F; pos++; media_header->marker = get_bit16(packet[pos],0); media_header->payload_type = (packet[pos] >> 1) & 0x7F; pos++; media_header->sequence_number = big_endian_read_16(packet, pos); pos+=2; media_header->timestamp = big_endian_read_32(packet, pos); pos+=4; media_header->synchronization_source = big_endian_read_32(packet, pos); pos+=4; *offset = pos; // TODO: read csrc list // printf_hexdump( packet, pos ); if (!is_media_header_reported_once){ is_media_header_reported_once = 1; printf("MEDIA HEADER: %u timestamp, version %u, padding %u, extension %u, csrc_count %u\n", media_header->timestamp, media_header->version, media_header->padding, media_header->extension, media_header->csrc_count); printf("MEDIA HEADER: marker %02x, payload_type %02x, sequence_number %u, synchronization_source %u\n", media_header->marker, media_header->payload_type, media_header->sequence_number, media_header->synchronization_source); } return 1; } static void handle_l2cap_media_data_packet(uint8_t seid, uint8_t *packet, uint16_t size){ UNUSED(seid); switch (codec_type){ case AVDTP_CODEC_SBC: handle_l2cap_media_data_packet_sbc(packet, size); break; case AVDTP_CODEC_MPEG_2_4_AAC: handle_l2cap_media_data_packet_aac(packet, size); break; case AVDTP_CODEC_NON_A2DP: if (vendor_id == A2DP_CODEC_VENDOR_ID_APT_LTD && codec_id == A2DP_APT_LTD_CODEC_APTX) { handle_l2cap_media_data_packet_aptx(packet, size); } else if (vendor_id == A2DP_CODEC_VENDOR_ID_QUALCOMM && codec_id == A2DP_QUALCOMM_CODEC_APTX_HD) { handle_l2cap_media_data_packet_aptxhd(packet, size); } else if (vendor_id == A2DP_CODEC_VENDOR_ID_SONY && codec_id == A2DP_SONY_CODEC_LDAC) { handle_l2cap_media_data_packet_ldac(packet, size); } break; default: printf("Media Codec %u not supported yet\n", codec_type); return; } } static uint32_t get_vendor_id(const uint8_t *codec_info) { uint32_t vendor_id = 0; vendor_id |= codec_info[0]; vendor_id |= codec_info[1] << 8; vendor_id |= codec_info[2] << 16; vendor_id |= codec_info[3] << 24; return vendor_id; } static uint16_t get_codec_id(const uint8_t *codec_info) { uint16_t codec_id = 0; codec_id |= codec_info[4]; codec_id |= codec_info[5] << 8; return codec_id; } static int convert_aptx_sampling_frequency(uint8_t frequency_bitmap) { switch (frequency_bitmap) { case 1 << 4: return 48000; case 1 << 5: return 44100; case 1 << 6: return 32000; case 1 <<7: return 16000; default: printf("invalid aptx sampling frequency %d\n", frequency_bitmap); return 0; } } static int convert_aptx_num_channels(uint8_t channel_mode) { switch (channel_mode) { case 1 << 0: case 1 << 1: case 1 << 2: return 2; case 1 << 3: return 1; default: printf("invalid aptx channel mode %d\n", channel_mode); return 0; } } static int convert_ldac_sampling_frequency(uint8_t frequency_bitmap) { switch (frequency_bitmap) { case 1 << 0: return 192000; case 1 << 1: return 176400; case 1 << 2: return 96000; case 1 << 3: return 88200; case 1 << 4: return 48000; case 1 << 5: return 44100; default: printf("invalid ldac sampling frequency %d\n", frequency_bitmap); return 0; } } static int convert_ldac_num_channels(uint8_t channel_mode) { switch (channel_mode) { case 1 << 0: // stereo case 1 << 1: // dual channel return 2; case 1 << 2: return 1; default: printf("error ldac channel mode\n"); return 0; } } static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(channel); UNUSED(size); uint8_t status; if (packet_type != HCI_EVENT_PACKET) return; if (hci_event_packet_get_type(packet) != HCI_EVENT_AVDTP_META) return; switch (packet[2]){ case AVDTP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED: avdtp_cid = avdtp_subevent_signaling_connection_established_get_avdtp_cid(packet); status = avdtp_subevent_signaling_connection_established_get_status(packet); if (status != ERROR_CODE_SUCCESS){ printf("AVDTP connection failed with status 0x%02x.\n", status); break; } printf("AVDTP Sink connected: avdtp_cid 0x%02x.\n", avdtp_cid); break; case AVDTP_SUBEVENT_SIGNALING_CONNECTION_RELEASED: avdtp_cid = avdtp_subevent_signaling_connection_released_get_avdtp_cid(packet); printf("AVDTP connection released: avdtp_cid 0x%02x.\n", avdtp_cid); break; case AVDTP_SUBEVENT_SIGNALING_SEP_FOUND: remote_seid = avdtp_subevent_signaling_sep_found_get_remote_seid(packet); printf("Found sep: seid %u, in_use %d, media type %d, sep type %d (1-SNK)\n", remote_seid, avdtp_subevent_signaling_sep_found_get_in_use(packet), avdtp_subevent_signaling_sep_found_get_media_type(packet), avdtp_subevent_signaling_sep_found_get_sep_type(packet)); if (num_remote_seids == 0){ first_remote_seid = remote_seid; } num_remote_seids++; break; case AVDTP_SUBEVENT_SIGNALING_SEP_DICOVERY_DONE: // select remote if there's only a single remote if (num_remote_seids == 1){ printf("Only one remote Stream Endpoint with SEID %u, select it for initiator commands\n", first_remote_seid); remote_seid = first_remote_seid; } break; case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CAPABILITY: printf("CAPABILITY - MEDIA_CODEC_SBC: \n"); sbc_capability.sampling_frequency_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_sampling_frequency_bitmap(packet); sbc_capability.channel_mode_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_channel_mode_bitmap(packet); sbc_capability.block_length_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_block_length_bitmap(packet); sbc_capability.subbands_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_subbands_bitmap(packet); sbc_capability.allocation_method_bitmap = avdtp_subevent_signaling_media_codec_sbc_capability_get_allocation_method_bitmap(packet); sbc_capability.min_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_capability_get_min_bitpool_value(packet); sbc_capability.max_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_capability_get_max_bitpool_value(packet); dump_sbc_capability(sbc_capability); local_seid = local_seid_sbc; printf("Selecting local SBC endpoint with SEID %u\n", local_seid); break; case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_MPEG_AAC_CAPABILITY: local_seid = local_seid_aac; printf("Selecting local MPEG AAC endpoint with SEID %u\n", local_seid); break; case AVDTP_SUBEVENT_SIGNALING_MEDIA_TRANSPORT_CAPABILITY: printf("CAPABILITY - MEDIA_TRANSPORT supported on remote.\n"); break; case AVDTP_SUBEVENT_SIGNALING_REPORTING_CAPABILITY: printf("CAPABILITY - REPORTING supported on remote.\n"); break; case AVDTP_SUBEVENT_SIGNALING_RECOVERY_CAPABILITY: printf("CAPABILITY - RECOVERY supported on remote: \n"); printf(" - recovery_type %d\n", avdtp_subevent_signaling_recovery_capability_get_recovery_type(packet)); printf(" - maximum_recovery_window_size %d\n", avdtp_subevent_signaling_recovery_capability_get_maximum_recovery_window_size(packet)); printf(" - maximum_number_media_packets %d\n", avdtp_subevent_signaling_recovery_capability_get_maximum_number_media_packets(packet)); break; case AVDTP_SUBEVENT_SIGNALING_CONTENT_PROTECTION_CAPABILITY: printf("CAPABILITY - CONTENT_PROTECTION supported on remote: \n"); printf(" - cp_type %d\n", avdtp_subevent_signaling_content_protection_capability_get_cp_type(packet)); printf(" - cp_type_value_len %d\n", avdtp_subevent_signaling_content_protection_capability_get_cp_type_value_len(packet)); printf(" - cp_type_value \'%s\'\n", avdtp_subevent_signaling_content_protection_capability_get_cp_type_value(packet)); break; case AVDTP_SUBEVENT_SIGNALING_MULTIPLEXING_CAPABILITY: printf("CAPABILITY - MULTIPLEXING supported on remote: \n"); printf(" - fragmentation %d\n", avdtp_subevent_signaling_multiplexing_capability_get_fragmentation(packet)); printf(" - transport_identifiers_num %d\n", avdtp_subevent_signaling_multiplexing_capability_get_transport_identifiers_num(packet)); printf(" - transport_session_identifier_1 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_transport_session_identifier_1(packet)); printf(" - transport_session_identifier_2 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_transport_session_identifier_2(packet)); printf(" - transport_session_identifier_3 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_transport_session_identifier_3(packet)); printf(" - tcid_1 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_tcid_1(packet)); printf(" - tcid_2 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_tcid_2(packet)); printf(" - tcid_3 %d\n", avdtp_subevent_signaling_multiplexing_capability_get_tcid_3(packet)); break; case AVDTP_SUBEVENT_SIGNALING_DELAY_REPORTING_CAPABILITY: printf("CAPABILITY - DELAY_REPORTING supported on remote.\n"); delay_reporting_supported_on_remote = true; break; case AVDTP_SUBEVENT_SIGNALING_HEADER_COMPRESSION_CAPABILITY: printf("CAPABILITY - HEADER_COMPRESSION supported on remote: \n"); printf(" - back_ch %d\n", avdtp_subevent_signaling_header_compression_capability_get_back_ch(packet)); printf(" - media %d\n", avdtp_subevent_signaling_header_compression_capability_get_media(packet)); printf(" - recovery %d\n", avdtp_subevent_signaling_header_compression_capability_get_recovery(packet)); break; case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CAPABILITY:{ printf("OTHER CAPABILITY\n"); const uint8_t *media_info = avdtp_subevent_signaling_media_codec_other_capability_get_media_codec_information(packet); vendor_id = get_vendor_id(media_info); codec_id = get_codec_id(media_info); // APTX if (vendor_id == A2DP_CODEC_VENDOR_ID_APT_LTD && codec_id == A2DP_APT_LTD_CODEC_APTX) { #ifdef HAVE_APTX local_seid = local_seid_aptx; printf("Selecting local APTX endpoint with SEID %u\n", local_seid); #endif } else if (vendor_id == A2DP_CODEC_VENDOR_ID_QUALCOMM && codec_id == A2DP_QUALCOMM_CODEC_APTX_HD) { #ifdef HAVE_APTX local_seid = local_seid_aptxhd; printf("Selecting local APTX HD endpoint with SEID %u\n", local_seid); #endif } else if (vendor_id == A2DP_CODEC_VENDOR_ID_SONY && codec_id == A2DP_SONY_CODEC_LDAC) { #ifdef HAVE_LDAC_DECODER local_seid = local_seid_ldac; printf("Selecting local LDAC endpoint with SEID %u\n", local_seid); #endif } break;} case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION: codec_type = AVDTP_CODEC_SBC; local_seid = avdtp_subevent_signaling_media_codec_sbc_configuration_get_local_seid(packet); playback_configuration.num_channels = avdtp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet); playback_configuration.sampling_frequency = avdtp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet);; btstack_sbc_decoder_init(&state, mode, sbc_handle_pcm_data, NULL); printf("Received SBC codec configuration: avdtp_cid 0x%02x.\n", avdtp_cid); sbc_configuration.reconfigure = avdtp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet); sbc_configuration.num_channels = avdtp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet); sbc_configuration.sampling_frequency = avdtp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet); sbc_configuration.channel_mode = avdtp_subevent_signaling_media_codec_sbc_configuration_get_channel_mode(packet); sbc_configuration.block_length = avdtp_subevent_signaling_media_codec_sbc_configuration_get_block_length(packet); sbc_configuration.subbands = avdtp_subevent_signaling_media_codec_sbc_configuration_get_subbands(packet); sbc_configuration.allocation_method = avdtp_subevent_signaling_media_codec_sbc_configuration_get_allocation_method(packet); sbc_configuration.min_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet); sbc_configuration.max_bitpool_value = avdtp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet); dump_sbc_configuration(sbc_configuration); avdtp_sink_delay_report(avdtp_cid, local_seid, 100); break; #ifdef HAVE_AAC_FDK case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_MPEG_AAC_CONFIGURATION:{ codec_type = AVDTP_CODEC_MPEG_2_4_AAC; local_seid = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_local_seid(packet); playback_configuration.num_channels = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_num_channels(packet); playback_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_sampling_frequency(packet);; aac_configuration.reconfigure = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_reconfigure(packet); aac_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_sampling_frequency(packet); aac_configuration.aot = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_object_type(packet); aac_configuration.channel_mode = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_num_channels(packet); aac_configuration.bitrate = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_bit_rate(packet); aac_configuration.vbr = a2dp_subevent_signaling_media_codec_mpeg_aac_configuration_get_vbr(packet); if (aac_configuration.reconfigure){ printf("A2DP Sink: Remote requested reconfiguration, ignoring it.\n"); } printf("A2DP Sink: Received AAC configuration! Sampling frequency: %u, object type %u, channel mode %u, bitrate %u, vbr: %u\n", aac_configuration.sampling_frequency, aac_configuration.aot, aac_configuration.channel_mode, aac_configuration.bitrate, aac_configuration.vbr); // initialize decoder AAC_DECODER_ERROR err; aac_handle = aacDecoder_Open(TT_MP4_LATM_MCP1, 1); if ((err = aacDecoder_SetParam(aac_handle, AAC_PCM_MAX_OUTPUT_CHANNELS, aac_configuration.channel_mode)) != AAC_DEC_OK) { printf("Couldn't set output channels: %d", err); break; } if ((err = aacDecoder_SetParam(aac_handle, AAC_PCM_MIN_OUTPUT_CHANNELS, aac_configuration.channel_mode)) != AAC_DEC_OK) { printf("Couldn't set output channels: %d", err); break; } break; } #endif case AVDTP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CONFIGURATION:{ codec_type = AVDTP_CODEC_NON_A2DP; local_seid = a2dp_subevent_signaling_media_codec_other_configuration_get_local_seid(packet); const uint8_t *media_info = avdtp_subevent_signaling_media_codec_other_configuration_get_media_codec_information(packet); vendor_id = get_vendor_id(media_info); codec_id = get_codec_id(media_info); // APTX if (vendor_id == A2DP_CODEC_VENDOR_ID_APT_LTD && codec_id == A2DP_APT_LTD_CODEC_APTX) { #if HAVE_APTX aptx_configuration.reconfigure = a2dp_subevent_signaling_media_codec_other_configuration_get_reconfigure(packet); aptx_configuration.sampling_frequency = media_info[6] & 0xF0; aptx_configuration.channel_mode = media_info[6] & 0x0F; aptx_configuration.sampling_frequency = convert_aptx_sampling_frequency(aptx_configuration.sampling_frequency); aptx_configuration.num_channels = convert_aptx_num_channels(aptx_configuration.channel_mode); printf("A2DP Source: Received APTX configuration! Sampling frequency: %d, channel mode: %d channels: %d\n", aptx_configuration.sampling_frequency, aptx_configuration.channel_mode, aptx_configuration.num_channels); aptx_handle = aptx_init(0); playback_configuration.num_channels = aptx_configuration.num_channels; playback_configuration.sampling_frequency = aptx_configuration.sampling_frequency; #endif } else if (vendor_id == A2DP_CODEC_VENDOR_ID_QUALCOMM && codec_id == A2DP_QUALCOMM_CODEC_APTX_HD) { #if HAVE_APTX aptxhd_configuration.reconfigure = a2dp_subevent_signaling_media_codec_other_configuration_get_reconfigure(packet); aptxhd_configuration.sampling_frequency = media_info[6] & 0xF0; aptxhd_configuration.channel_mode = media_info[6] & 0x0F; aptxhd_configuration.sampling_frequency = convert_aptx_sampling_frequency(aptxhd_configuration.sampling_frequency); aptxhd_configuration.num_channels = convert_aptx_num_channels(aptxhd_configuration.channel_mode); printf("A2DP Source: Received APTX HD configuration! Sampling frequency: %d, channel mode: %d channels: %d\n", aptxhd_configuration.sampling_frequency, aptxhd_configuration.channel_mode, aptxhd_configuration.num_channels); aptx_handle = aptx_init(1); playback_configuration.num_channels = aptxhd_configuration.num_channels; playback_configuration.sampling_frequency = aptxhd_configuration.sampling_frequency; #endif } else if (vendor_id == A2DP_CODEC_VENDOR_ID_SONY && codec_id == A2DP_SONY_CODEC_LDAC) { #ifdef HAVE_LDAC_DECODER ldac_configuration.reconfigure = a2dp_subevent_signaling_media_codec_other_configuration_get_reconfigure(packet); ldac_configuration.sampling_frequency = media_info[6]; ldac_configuration.channel_mode = media_info[7]; ldac_configuration.sampling_frequency = convert_ldac_sampling_frequency(ldac_configuration.sampling_frequency); ldac_configuration.num_channels = convert_ldac_num_channels(ldac_configuration.channel_mode); printf("A2DP Source: Received LDAC configuration! Sampling frequency: %d, channel mode: %d channels: %d\n", ldac_configuration.sampling_frequency, ldac_configuration.channel_mode, ldac_configuration.num_channels); ldacdecInit(&ldac_handle); playback_configuration.num_channels = ldac_configuration.num_channels; playback_configuration.sampling_frequency = ldac_configuration.sampling_frequency; #endif } break;} case AVDTP_SUBEVENT_STREAMING_CONNECTION_ESTABLISHED: avdtp_cid = avdtp_subevent_streaming_connection_established_get_avdtp_cid(packet); printf("Streaming connection opened: avdtp_cid 0x%02x.\n", avdtp_cid); break; case AVDTP_SUBEVENT_STREAMING_CONNECTION_RELEASED: printf("Streaming connection released: avdtp_cid 0x%02x.\n", avdtp_cid); is_cmd_triggered_locally = 0; is_media_header_reported_once = 0; playback_close(); break; case AVDTP_SUBEVENT_SIGNALING_ACCEPT:{ switch (avdtp_subevent_signaling_accept_get_signal_identifier(packet)){ case AVDTP_SI_START: printf("Stream started\n"); playback_close(); playback_init(&playback_configuration); playback_start(); break; case AVDTP_SI_SUSPEND: printf("Stream paused\n"); playback_pause(); break; case AVDTP_SI_ABORT: case AVDTP_SI_CLOSE: printf("Stream stoped\n"); playback_close(); break; default: break; } if (is_cmd_triggered_locally){ is_cmd_triggered_locally = 0; printf("AVDTP Sink command accepted\n"); } break; } case AVDTP_SUBEVENT_SIGNALING_REJECT: case AVDTP_SUBEVENT_SIGNALING_GENERAL_REJECT: if (is_cmd_triggered_locally){ is_cmd_triggered_locally = 0; printf("AVDTP Sink command rejected\n"); } break; default: if (is_cmd_triggered_locally){ is_cmd_triggered_locally = 0; } break; } } static uint8_t media_sbc_codec_capabilities[] = { 0xFF,//(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, 0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, 2, 53 }; static uint8_t media_sbc_codec_configuration[] = { (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, 2, 53 }; static uint8_t media_sbc_codec_reconfiguration[] = { // (AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO, // (AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_SNR, // 32, 32 (AVDTP_SBC_44100 << 4) | AVDTP_SBC_MONO, (AVDTP_SBC_BLOCK_LENGTH_8 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS, 32, 32 }; static uint8_t media_aac_codec_capabilities[] = { 0xF0, 0xFF, 0xFC, 0x80, 0, 0 }; static uint8_t media_aac_codec_configuration[6]; static uint8_t media_aptx_codec_capabilities[] = { 0x4F, 0x0, 0x0, 0x0, 0x1, 0, 0xFF, }; static uint8_t media_aptx_codec_configuration[7]; static uint8_t media_aptxhd_codec_capabilities[] = { 0xD7, 0x0, 0x0, 0x0, 0x24, 0, 0xFF, 0, 0, 0, 0 }; static uint8_t media_aptxhd_codec_configuration[11]; static uint8_t media_ldac_codec_capabilities[] = { 0x2D, 0x1, 0x0, 0x0, 0xAA, 0, 0x3C, 0x7 }; static uint8_t media_ldac_codec_configuration[8]; static void show_usage(void){ bd_addr_t iut_address; gap_local_bd_addr(iut_address); printf("\n--- Bluetooth AVDTP SINK Test Console %s ---\n", bd_addr_to_str(iut_address)); printf("Supported Codecs: SBC"); #ifdef HAVE_AAC_FDK printf(", MPEG AAC"); #endif #ifdef HAVE_APTX printf(", aptX, aptX HD"); #endif #ifdef HAVE_LDAC_DECODER printf(", LDAC"); #endif printf("\n"); printf("c - create connection to addr %s\n", device_addr_string); printf("d - discover stream endpoints\n"); printf("g - get capabilities\n"); printf("a - get all capabilities\n"); printf("s - set configuration\n"); printf("f - get configuration\n"); printf("R - reconfigure stream with local seid %d\n", local_seid); printf("o - establish stream with local seid %d\n", local_seid); printf("m - start stream with local %d\n", local_seid); printf("A - abort stream with local %d\n", local_seid); printf("P - suspend (pause) stream with local %d\n", local_seid); printf("S - stop (release) stream with local %d\n", local_seid); printf("D - send delay report\n"); printf("C - disconnect\n"); printf("Ctrl-c - exit\n"); printf("---\n"); } static void stdin_process(char cmd){ uint8_t status = ERROR_CODE_SUCCESS; remote_seid = 1; is_cmd_triggered_locally = 1; switch (cmd){ case 'c': printf("Establish AVDTP Sink connection to %s\n", device_addr_string); status = avdtp_sink_connect(device_addr, &avdtp_cid); break; case 'C': printf("Disconnect AVDTP Sink\n"); status = avdtp_sink_disconnect(avdtp_cid); break; case 'd': printf("Discover stream endpoints of %s\n", device_addr_string); first_remote_seid = 0; num_remote_seids = 0; status = avdtp_sink_discover_stream_endpoints(avdtp_cid); break; case 'g': printf("Get capabilities of stream endpoint with seid %d\n", remote_seid); status = avdtp_sink_get_capabilities(avdtp_cid, remote_seid); break; case 'a': printf("Get all capabilities of stream endpoint with seid %d\n", remote_seid); status = avdtp_sink_get_all_capabilities(avdtp_cid, remote_seid); break; case 'f': printf("Get configuration of stream endpoint with seid %d\n", remote_seid); status = avdtp_sink_get_configuration(avdtp_cid, remote_seid); break; case 's': printf("Set configuration of stream endpoint with local %u and remote seid %d\n", local_seid, remote_seid); remote_configuration_bitmap = store_bit16(remote_configuration_bitmap, AVDTP_MEDIA_CODEC, 1); remote_configuration.media_codec.media_type = AVDTP_AUDIO; remote_configuration.media_codec.media_codec_type = AVDTP_CODEC_SBC; remote_configuration.media_codec.media_codec_information_len = sizeof(media_sbc_codec_configuration); remote_configuration.media_codec.media_codec_information = media_sbc_codec_configuration; if (delay_reporting_supported_on_remote){ remote_configuration_bitmap = store_bit16(remote_configuration_bitmap, AVDTP_DELAY_REPORTING, 1); } status = avdtp_sink_set_configuration(avdtp_cid, local_seid, remote_seid, remote_configuration_bitmap, remote_configuration); break; case 'R': printf("Reconfigure stream endpoint with seid %d\n", remote_seid); remote_configuration_bitmap = 0; remote_configuration_bitmap = store_bit16(remote_configuration_bitmap, AVDTP_MEDIA_CODEC, 1); remote_configuration.media_codec.media_type = AVDTP_AUDIO; remote_configuration.media_codec.media_codec_type = AVDTP_CODEC_SBC; remote_configuration.media_codec.media_codec_information_len = sizeof(media_sbc_codec_reconfiguration); remote_configuration.media_codec.media_codec_information = media_sbc_codec_reconfiguration; status = avdtp_sink_reconfigure(avdtp_cid, local_seid, remote_seid, remote_configuration_bitmap, remote_configuration); break; case 'o': printf("Establish stream between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_open_stream(avdtp_cid, local_seid, remote_seid); break; case 'm': printf("Start stream between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_start_stream(avdtp_cid, local_seid); break; case 'A': printf("Abort stream between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_abort_stream(avdtp_cid, local_seid); break; case 'S': printf("Release stream between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_stop_stream(avdtp_cid, local_seid); break; case 'P': printf("Suspend stream between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_suspend(avdtp_cid, local_seid); break; case 'D': printf("Send delay report between local %d and remote %d seid\n", local_seid, remote_seid); status = avdtp_sink_delay_report(avdtp_cid, local_seid, 100); break; case '\n': case '\r': is_cmd_triggered_locally = 0; break; default: is_cmd_triggered_locally = 0; show_usage(); break; } if (status != ERROR_CODE_SUCCESS){ printf("AVDTP Sink cmd \'%c\' failed, status 0x%02x\n", cmd, status); } } int btstack_main(int argc, const char * argv[]); int btstack_main(int argc, const char * argv[]){ UNUSED(argc); (void)argv; /* Register for HCI events */ hci_event_callback_registration.callback = &packet_handler; hci_add_event_handler(&hci_event_callback_registration); l2cap_init(); // Initialize AVDTP Sink avdtp_sink_init(); avdtp_sink_register_packet_handler(&packet_handler); avdtp_stream_endpoint_t * local_stream_endpoint; // in PTS 7.6.1 Build 4, test AVDTP/SNK/ACP/SIG/SMG/BI-08-C fails if AAC Endpoint is defined last #ifdef HAVE_AAC_FDK // Setup AAC Endpoint local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); btstack_assert(local_stream_endpoint != NULL); local_stream_endpoint->media_codec_configuration_info = media_aac_codec_configuration; local_stream_endpoint->media_codec_configuration_len = sizeof(media_aac_codec_configuration); local_seid_aac = avdtp_local_seid(local_stream_endpoint); avdtp_sink_register_media_transport_category(local_seid_aac); avdtp_sink_register_media_codec_category(local_seid_aac, AVDTP_AUDIO, AVDTP_CODEC_MPEG_2_4_AAC, media_aac_codec_capabilities, sizeof(media_aac_codec_capabilities)); #endif #ifdef HAVE_LDAC_DECODER // Setup LDAC Endpoint local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); btstack_assert(local_stream_endpoint != NULL); local_stream_endpoint->media_codec_configuration_info = media_ldac_codec_configuration; local_stream_endpoint->media_codec_configuration_len = sizeof(media_ldac_codec_configuration); local_seid_ldac = avdtp_local_seid(local_stream_endpoint); avdtp_sink_register_media_transport_category(local_seid_ldac); avdtp_sink_register_media_codec_category(local_seid_ldac, AVDTP_AUDIO, AVDTP_CODEC_NON_A2DP, media_ldac_codec_capabilities, sizeof(media_ldac_codec_capabilities)); #endif #ifdef HAVE_APTX // Setup APTX Endpoint local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); btstack_assert(local_stream_endpoint != NULL); local_stream_endpoint->media_codec_configuration_info = media_aptx_codec_configuration; local_stream_endpoint->media_codec_configuration_len = sizeof(media_aptx_codec_configuration); local_seid_aptx = avdtp_local_seid(local_stream_endpoint); avdtp_sink_register_media_transport_category(local_seid_aptx); avdtp_sink_register_media_codec_category(local_seid_aptx, AVDTP_AUDIO, AVDTP_CODEC_NON_A2DP, media_aptx_codec_capabilities, sizeof(media_aptx_codec_capabilities)); // Setup APTX HD Endpoint local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); btstack_assert(local_stream_endpoint != NULL); local_stream_endpoint->media_codec_configuration_info = media_aptxhd_codec_configuration; local_stream_endpoint->media_codec_configuration_len = sizeof(media_aptxhd_codec_configuration); local_seid_aptxhd = avdtp_local_seid(local_stream_endpoint); avdtp_sink_register_media_transport_category(local_seid_aptxhd); avdtp_sink_register_media_codec_category(local_seid_aptxhd, AVDTP_AUDIO, AVDTP_CODEC_NON_A2DP, media_aptxhd_codec_capabilities, sizeof(media_aptxhd_codec_capabilities)); #endif // Setup SBC Endpoint local_stream_endpoint = avdtp_sink_create_stream_endpoint(AVDTP_SINK, AVDTP_AUDIO); btstack_assert(local_stream_endpoint != NULL); local_stream_endpoint->media_codec_configuration_info = media_sbc_codec_configuration; local_stream_endpoint->media_codec_configuration_len = sizeof(media_sbc_codec_configuration); local_seid_sbc = avdtp_local_seid(local_stream_endpoint); avdtp_sink_register_media_transport_category(local_seid_sbc); avdtp_sink_register_media_codec_category(local_seid_sbc, AVDTP_AUDIO, AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities)); avdtp_sink_register_delay_reporting_category(avdtp_stream_endpoint_seid(local_stream_endpoint)); avdtp_sink_register_media_handler(&handle_l2cap_media_data_packet); // Initialize SDP sdp_init(); memset(sdp_avdtp_sink_service_buffer, 0, sizeof(sdp_avdtp_sink_service_buffer)); a2dp_sink_create_sdp_record(sdp_avdtp_sink_service_buffer, 0x10001, AVDTP_SINK_FEATURE_MASK_HEADPHONE, NULL, NULL); sdp_register_service(sdp_avdtp_sink_service_buffer); // printf("BTstack AVDTP Sink, supported features 0x%04x\n", ); gap_set_local_name("AVDTP Sink 00:00:00:00:00:00"); gap_discoverable_control(1); gap_set_class_of_device(0x200408); // parse human readable Bluetooth address sscanf_bd_addr(device_addr_string, device_addr); btstack_stdin_setup(stdin_process); // turn on! hci_power_control(HCI_POWER_ON); return 0; }