mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-01-31 18:33:00 +00:00
1473 lines
61 KiB
C
1473 lines
61 KiB
C
/*
|
|
* 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 <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#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 <fdk-aac/aacdecoder_lib.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_APTX
|
|
#include <openaptx.h>
|
|
#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 <ldacdec/ldacdec.h>
|
|
#endif
|
|
#define A2DP_CODEC_VENDOR_ID_SONY 0x12d
|
|
#define A2DP_SONY_CODEC_LDAC 0xaa
|
|
|
|
#ifdef HAVE_LC3PLUS
|
|
#include <LC3plus/lc3.h>
|
|
#endif
|
|
#define A2DP_CODEC_VENDOR_ID_FRAUNHOFER 0x08A9
|
|
#define A2DP_FRAUNHOFER_CODEC_LC3PLUS 0x0001
|
|
|
|
//
|
|
// 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 {
|
|
int reconfigure;
|
|
int frame_duration;
|
|
int num_channels;
|
|
int sampling_frequency;
|
|
int bitrate;
|
|
int hrmode;
|
|
} avdtp_media_codec_configuration_lc3plus_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 = "DC:A6:32:62:1C:DD";
|
|
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
|
|
|
|
// LC3PLUS
|
|
#ifdef HAVE_LC3PLUS
|
|
LC3PLUS_Dec * lc3plus_handle = NULL;
|
|
static uint8_t * lc3plus_scratch = NULL;
|
|
static avdtp_media_codec_configuration_lc3plus_t lc3plus_configuration;
|
|
static uint8_t local_seid_lc3plus;
|
|
#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);
|
|
#else
|
|
UNUSED(packet);
|
|
UNUSED(size);
|
|
#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);
|
|
#else
|
|
UNUSED(packet);
|
|
UNUSED(size);
|
|
#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;
|
|
}
|
|
#else
|
|
UNUSED(packet);
|
|
UNUSED(size);
|
|
#endif
|
|
}
|
|
|
|
static void interleave_int24_to_int16(int32_t** in, int16_t* out, int32_t n, int32_t channels)
|
|
{
|
|
int32_t ch, i;
|
|
for (ch = 0; ch < channels; ch++) {
|
|
for (i = 0; i < n; i++) {
|
|
out[i * channels + ch] = (in[ch][i] >> 8) & 0xFFFF;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void handle_l2cap_media_data_packet_lc3plus(uint8_t *packet, uint16_t size) {
|
|
#ifdef HAVE_LC3PLUS
|
|
uint16_t pos = 0;
|
|
static uint8_t lc3_bytes[LC3PLUS_MAX_BYTES];
|
|
static int32_t lc3_out_ch1[LC3PLUS_MAX_SAMPLES];
|
|
static int32_t lc3_out_ch2[LC3PLUS_MAX_SAMPLES];
|
|
static int16_t out_samples[LC3PLUS_MAX_SAMPLES * 2];
|
|
static uint16_t byte_ptr = 0;
|
|
|
|
// read media header
|
|
avdtp_media_packet_header_t media_header;
|
|
if (!read_media_data_header(packet, size, &pos, &media_header)) return;
|
|
size -= pos;
|
|
|
|
// read payload header
|
|
uint8_t payload_header = packet[pos];
|
|
size--;
|
|
pos++;
|
|
bool is_fragmented = payload_header & (1 << 7);
|
|
bool is_last_fragment = payload_header & (1 << 5);
|
|
uint8_t number_of_frames = payload_header & 0x0F;
|
|
|
|
// check if frame is not complete yet
|
|
if (is_fragmented) {
|
|
memcpy(&lc3_bytes[byte_ptr], &packet[pos], size);
|
|
byte_ptr += size;
|
|
if (!is_last_fragment) {
|
|
return;
|
|
}
|
|
} else {
|
|
byte_ptr = size;
|
|
}
|
|
|
|
// get frame len and number of samples
|
|
uint16_t frame_len = byte_ptr / number_of_frames;
|
|
uint16_t num_samples = lc3plus_dec_get_output_samples(lc3plus_handle);
|
|
|
|
// prepare output
|
|
int32_t *out_buf[2] = { lc3_out_ch1, lc3_out_ch2 };
|
|
|
|
// decode complete fragmented frame
|
|
if (is_fragmented) {
|
|
int err = lc3plus_dec24(lc3plus_handle, lc3_bytes, frame_len, out_buf, lc3plus_scratch, 0);
|
|
if (err == LC3PLUS_DECODE_ERROR)
|
|
printf("using plc\n");
|
|
else if (err != LC3PLUS_OK) {
|
|
printf("LC3plus decoding error: %d\n", err);
|
|
return;
|
|
}
|
|
interleave_int24_to_int16(out_buf, out_samples, num_samples, lc3plus_configuration.num_channels);
|
|
playback_queue_audio(out_samples, num_samples, lc3plus_configuration.num_channels);
|
|
byte_ptr = 0;
|
|
return;
|
|
}
|
|
|
|
// decode all non fragmented frames
|
|
int err;
|
|
for (uint16_t i = 0; i < number_of_frames; i++) {
|
|
err = lc3plus_dec24(lc3plus_handle, &packet[pos], frame_len, out_buf, lc3plus_scratch, 0);
|
|
if (err == LC3PLUS_DECODE_ERROR)
|
|
printf("using plc\n");
|
|
else if (err != LC3PLUS_OK) {
|
|
printf("LC3plus decoding error: %d\n", err);
|
|
return;
|
|
}
|
|
interleave_int24_to_int16(out_buf, out_samples, num_samples, lc3plus_configuration.num_channels);
|
|
playback_queue_audio(out_samples, num_samples, lc3plus_configuration.num_channels);
|
|
pos += frame_len;
|
|
}
|
|
byte_ptr = 0;
|
|
#else
|
|
UNUSED(packet);
|
|
UNUSED(size);
|
|
#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);
|
|
} else if (vendor_id == A2DP_CODEC_VENDOR_ID_FRAUNHOFER && codec_id == A2DP_FRAUNHOFER_CODEC_LC3PLUS) {
|
|
handle_l2cap_media_data_packet_lc3plus(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 vid = 0;
|
|
vid |= codec_info[0];
|
|
vid |= codec_info[1] << 8;
|
|
vid |= codec_info[2] << 16;
|
|
vid |= codec_info[3] << 24;
|
|
return vid;
|
|
}
|
|
|
|
static uint16_t get_codec_id(const uint8_t *codec_info) {
|
|
uint16_t cid = 0;
|
|
cid |= codec_info[4];
|
|
cid |= codec_info[5] << 8;
|
|
return cid;
|
|
}
|
|
|
|
#if HAVE_APTX
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LDAC_DECODER
|
|
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;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LC3PLUS
|
|
typedef enum {
|
|
A2DP_LC3PLUS_FRAME_DURATION_2_5 = 1 << 4,
|
|
A2DP_LC3PLUS_FRAME_DURATION_5 = 1 << 5,
|
|
A2DP_LC3PLUS_FRAME_DURATION_10 = 1 << 6,
|
|
} a2dp_shifted_lc3plus_frame_duration_t;
|
|
|
|
typedef enum {
|
|
A2DP_LC3PLUS_CHANNEL_COUNT_2 = 1 << 6,
|
|
A2DP_LC3PLUS_CHANNEL_COUNT_1 = 1 << 7,
|
|
} a2dp_shifted_lc3plus_channel_count_t;
|
|
|
|
typedef enum {
|
|
A2DP_LC3PLUS_48000_HR = 1 << 0,
|
|
A2DP_LC3PLUS_96000_HR = 1 << 15,
|
|
} a2dp_shifted_lc3plus_samplerate_t;
|
|
static uint8_t lc3plus_get_frame_durations(uint8_t *codec_info) { return codec_info[6] & 0xF0; }
|
|
|
|
static uint8_t lc3plus_get_channel_count(uint8_t *codec_info) { return codec_info[7]; }
|
|
|
|
uint16_t lc3plus_get_samplerate(uint8_t *codec_info) {
|
|
return (codec_info[9] << 8) | codec_info[8];
|
|
}
|
|
|
|
int convert_lc3plus_frame_duration(int duration) {
|
|
switch (duration) {
|
|
case A2DP_LC3PLUS_FRAME_DURATION_2_5:
|
|
return 25;
|
|
case A2DP_LC3PLUS_FRAME_DURATION_5:
|
|
return 50;
|
|
case A2DP_LC3PLUS_FRAME_DURATION_10:
|
|
return 100;
|
|
default:
|
|
printf("invalid lc3plus frame duration %d\n", duration);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int convert_lc3plus_samplerate(int rate) {
|
|
switch (rate) {
|
|
case A2DP_LC3PLUS_96000_HR:
|
|
return 96000;
|
|
case A2DP_LC3PLUS_48000_HR:
|
|
return 48000;
|
|
default:
|
|
printf("invalid lc3plus samplerate %d\n", rate);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int convert_lc3plus_channel_count(int channels) {
|
|
switch (channels) {
|
|
case A2DP_LC3PLUS_CHANNEL_COUNT_2:
|
|
return 2;
|
|
case A2DP_LC3PLUS_CHANNEL_COUNT_1:
|
|
return 1;
|
|
default:
|
|
printf("invalid lc3plus channel count %d\n", channels);
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
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);
|
|
#ifdef HAVE_LC3PLUS
|
|
if (lc3plus_handle) {
|
|
free(lc3plus_handle);
|
|
lc3plus_handle = NULL;
|
|
}
|
|
if (lc3plus_scratch) {
|
|
free(lc3plus_scratch);
|
|
lc3plus_scratch = NULL;
|
|
}
|
|
#endif
|
|
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
|
|
} else if (vendor_id == A2DP_CODEC_VENDOR_ID_FRAUNHOFER && codec_id == A2DP_FRAUNHOFER_CODEC_LC3PLUS) {
|
|
#ifdef HAVE_LC3PLUS
|
|
local_seid = local_seid_lc3plus;
|
|
printf("Selecting local LC3plus 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
|
|
} else if (vendor_id == A2DP_CODEC_VENDOR_ID_FRAUNHOFER && codec_id == A2DP_FRAUNHOFER_CODEC_LC3PLUS) {
|
|
#ifdef HAVE_LC3PLUS
|
|
lc3plus_configuration.reconfigure = a2dp_subevent_signaling_media_codec_other_configuration_get_reconfigure(packet);
|
|
lc3plus_configuration.sampling_frequency = lc3plus_get_samplerate((uint8_t *) media_info);
|
|
lc3plus_configuration.frame_duration = lc3plus_get_frame_durations((uint8_t *) media_info);
|
|
lc3plus_configuration.num_channels = lc3plus_get_channel_count((uint8_t *) media_info);
|
|
|
|
lc3plus_configuration.frame_duration = convert_lc3plus_frame_duration(lc3plus_configuration.frame_duration);
|
|
lc3plus_configuration.sampling_frequency = convert_lc3plus_samplerate(lc3plus_configuration.sampling_frequency);
|
|
lc3plus_configuration.num_channels = convert_lc3plus_channel_count(lc3plus_configuration.num_channels);
|
|
playback_configuration.num_channels = lc3plus_configuration.num_channels;
|
|
playback_configuration.sampling_frequency = lc3plus_configuration.sampling_frequency;
|
|
printf("A2DP Sink: Received LC3Plus configuration! Sampling frequency: %d, channels: %d, frame duration %0.1f\n",
|
|
lc3plus_configuration.sampling_frequency,
|
|
lc3plus_configuration.num_channels,
|
|
lc3plus_configuration.frame_duration * 0.1);
|
|
|
|
lc3plus_handle = malloc(lc3plus_dec_get_size(lc3plus_configuration.sampling_frequency, lc3plus_configuration.num_channels, LC3PLUS_PLC_ADVANCED));
|
|
if (lc3plus_handle == NULL) {
|
|
printf("Failed to allocate lc3plus decoder memory\n");
|
|
break;
|
|
}
|
|
if (lc3plus_dec_init(lc3plus_handle, lc3plus_configuration.sampling_frequency,
|
|
lc3plus_configuration.num_channels, LC3PLUS_PLC_ADVANCED, 1) != LC3PLUS_OK) {
|
|
free(lc3plus_handle);
|
|
lc3plus_handle = NULL;
|
|
printf("Failed to initialize lc3plus decoder\n");
|
|
break;
|
|
}
|
|
if (lc3plus_dec_set_frame_dms(
|
|
lc3plus_handle, lc3plus_configuration.frame_duration) != LC3PLUS_OK) {
|
|
free(lc3plus_handle);
|
|
lc3plus_handle = NULL;
|
|
printf("Failed to set lc3plus frame duration\n");
|
|
break;
|
|
}
|
|
lc3plus_scratch = malloc(lc3plus_dec_get_scratch_size(lc3plus_handle));
|
|
if (lc3plus_scratch == NULL) {
|
|
printf("Failed to allocate lc3plus scratch memory\n");
|
|
free(lc3plus_handle);
|
|
lc3plus_handle = NULL;
|
|
break;
|
|
}
|
|
#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];
|
|
|
|
#ifdef HAVE_APTX
|
|
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];
|
|
#endif
|
|
|
|
#ifdef HAVE_LDAC_DECODER
|
|
static uint8_t media_ldac_codec_capabilities[] = {
|
|
0x2D, 0x1, 0x0, 0x0,
|
|
0xAA, 0,
|
|
0x3C,
|
|
0x7
|
|
};
|
|
static uint8_t media_ldac_codec_configuration[8];
|
|
#endif
|
|
|
|
#ifdef HAVE_LC3PLUS
|
|
static uint8_t media_lc3plus_codec_capabilities[] = {
|
|
0xA9, 0x08, 0x0, 0x0,
|
|
0x01, 0x0,
|
|
0x70,
|
|
0xc0,
|
|
0x01, 0x80
|
|
};
|
|
static uint8_t media_lc3plus_codec_configuration[] = {
|
|
0xA9, 0x08, 0x0, 0x0,
|
|
0x01, 0x0,
|
|
0x40,
|
|
0x40,
|
|
0x00, 0x80
|
|
};
|
|
#endif
|
|
|
|
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
|
|
#ifdef HAVE_LC3PLUS
|
|
printf(", LC3plus");
|
|
#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;
|
|
if (local_seid == local_seid_sbc) {
|
|
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;
|
|
#ifdef HAVE_LC3PLUS
|
|
} else if (local_seid == local_seid_lc3plus) {
|
|
remote_configuration.media_codec.media_codec_type = AVDTP_CODEC_NON_A2DP;
|
|
remote_configuration.media_codec.media_codec_information_len = sizeof(media_lc3plus_codec_configuration);
|
|
remote_configuration.media_codec.media_codec_information = media_lc3plus_codec_configuration;
|
|
#endif
|
|
} else {
|
|
printf("Set configuration for local seid %d not implemented\n", local_seid);
|
|
break;
|
|
}
|
|
|
|
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
|
|
|
|
#ifdef HAVE_LC3PLUS
|
|
// Setup lc3plus 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_lc3plus_codec_configuration;
|
|
local_stream_endpoint->media_codec_configuration_len = sizeof(media_lc3plus_codec_configuration);
|
|
local_seid_lc3plus = avdtp_local_seid(local_stream_endpoint);
|
|
avdtp_sink_register_media_transport_category(local_seid_lc3plus);
|
|
avdtp_sink_register_media_codec_category(local_seid_lc3plus, AVDTP_AUDIO, AVDTP_CODEC_NON_A2DP, media_lc3plus_codec_capabilities, sizeof(media_lc3plus_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;
|
|
}
|