And bt_A2DPSink.c code:
#include "bt_A2DPSink.h"
static void a2dp_sink_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void handle_l2cap_media_data_packet(uint8_t seid, uint8_t *packet, uint16_t size);
static void avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void dump_sbc_configuration(media_codec_configuration_sbc_t * configuration);
static void media_processing_close(void);
static int media_processing_init(media_codec_configuration_sbc_t * configuration);
static a2dp_sink_demo_stream_endpoint_t a2dp_sink_demo_stream_endpoint;
static btstack_packet_callback_registration_t hci_event_callback_registration;
static a2dp_sink_demo_a2dp_connection_t a2dp_sink_demo_a2dp_connection;
static uint8_t sdp_avdtp_sink_service_buffer[150];
static uint8_t sdp_avrcp_target_service_buffer[150];
static uint8_t sdp_avrcp_controller_service_buffer[200];
static uint8_t device_id_sdp_service_buffer[100];
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 int volume_percentage = 0;
int setup_demo(void) {
// init protocols
l2cap_init();
sdp_init();
#ifdef ENABLE_BLE
// Initialize LE Security Manager. Needed for cross-transport key derivation
sm_init();
#endif
#ifdef ENABLE_AVRCP_COVER_ART
goep_client_init();
avrcp_cover_art_client_init();
#endif
// Init profiles
a2dp_sink_init();
avrcp_init();
avrcp_controller_init();
avrcp_target_init();
// Configure A2DP Sink
a2dp_sink_register_packet_handler(&a2dp_sink_packet_handler);
a2dp_sink_register_media_handler(&handle_l2cap_media_data_packet);
a2dp_sink_demo_stream_endpoint_t * stream_endpoint = &a2dp_sink_demo_stream_endpoint;
avdtp_stream_endpoint_t * local_stream_endpoint = a2dp_sink_create_stream_endpoint(AVDTP_AUDIO,
AVDTP_CODEC_SBC,
media_sbc_codec_capabilities,
sizeof(media_sbc_codec_capabilities),
stream_endpoint->media_sbc_codec_configuration,
sizeof(stream_endpoint->media_sbc_codec_configuration));
btstack_assert(local_stream_endpoint != NULL);
// - Store stream enpoint's SEP ID, as it is used by A2DP API to identify the stream endpoint
stream_endpoint->a2dp_local_seid = avdtp_local_seid(local_stream_endpoint);
// Configure AVRCP Controller + Target
avrcp_register_packet_handler(&avrcp_packet_handler);
avrcp_controller_register_packet_handler(&avrcp_controller_packet_handler);
avrcp_target_register_packet_handler(&avrcp_target_packet_handler);
// Configure SDP
// - Create and register A2DP Sink service record
memset(sdp_avdtp_sink_service_buffer, 0, sizeof(sdp_avdtp_sink_service_buffer));
a2dp_sink_create_sdp_record(sdp_avdtp_sink_service_buffer,
sdp_create_service_record_handle(),
AVDTP_SINK_FEATURE_MASK_HEADPHONE,
NULL,
NULL);
btstack_assert(de_get_len(sdp_avdtp_sink_service_buffer) <= sizeof(sdp_avdtp_sink_service_buffer));
sdp_register_service(sdp_avdtp_sink_service_buffer);
// - Create AVRCP Controller service record and register it with SDP. We send Category 1 commands to the media player, e.g. play/pause
memset(sdp_avrcp_controller_service_buffer, 0, sizeof(sdp_avrcp_controller_service_buffer));
uint16_t controller_supported_features = 1 << AVRCP_CONTROLLER_SUPPORTED_FEATURE_CATEGORY_PLAYER_OR_RECORDER;
#ifdef AVRCP_BROWSING_ENABLED
controller_supported_features |= 1 << AVRCP_CONTROLLER_SUPPORTED_FEATURE_BROWSING;
#endif
#ifdef ENABLE_AVRCP_COVER_ART
controller_supported_features |= 1 << AVRCP_CONTROLLER_SUPPORTED_FEATURE_COVER_ART_GET_LINKED_THUMBNAIL;
#endif
avrcp_controller_create_sdp_record(sdp_avrcp_controller_service_buffer,
sdp_create_service_record_handle(),
controller_supported_features,
NULL,
NULL);
btstack_assert(de_get_len(sdp_avrcp_controller_service_buffer) <= sizeof(sdp_avrcp_controller_service_buffer));
sdp_register_service(sdp_avrcp_controller_service_buffer);
// - Create and register A2DP Sink service record
// - We receive Category 2 commands from the media player, e.g. volume up/down
memset(sdp_avrcp_target_service_buffer, 0, sizeof(sdp_avrcp_target_service_buffer));
uint16_t target_supported_features = 1 << AVRCP_TARGET_SUPPORTED_FEATURE_CATEGORY_MONITOR_OR_AMPLIFIER;
avrcp_target_create_sdp_record(sdp_avrcp_target_service_buffer,
sdp_create_service_record_handle(),
target_supported_features,
NULL,
NULL);
btstack_assert(de_get_len(sdp_avrcp_target_service_buffer) <= sizeof(sdp_avrcp_target_service_buffer));
sdp_register_service(sdp_avrcp_target_service_buffer);
// - Create and register Device ID (PnP) service record
memset(device_id_sdp_service_buffer, 0, sizeof(device_id_sdp_service_buffer));
device_id_create_sdp_record(device_id_sdp_service_buffer,
sdp_create_service_record_handle(),
DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH,
BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH,
1,
1);
btstack_assert(de_get_len(device_id_sdp_service_buffer) <= sizeof(device_id_sdp_service_buffer));
sdp_register_service(device_id_sdp_service_buffer);
// Configure GAP - discovery / connection
// - Set local name with a template Bluetooth address, that will be automatically
// replaced with an actual address once it is available, i.e. when BTstack boots
// up and starts talking to a Bluetooth module.
gap_set_local_name("A2DP Sink Demo 00:00:00:00:00:00");
// - Allow to show up in Bluetooth inquiry
gap_discoverable_control(1);
// - Set Class of Device - Service Class: Audio, Major Device Class: Audio, Minor: Loudspeaker
gap_set_class_of_device(0x200414);
// - Allow for role switch in general and sniff mode
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_ROLE_SWITCH | LM_LINK_POLICY_ENABLE_SNIFF_MODE);
// - Allow for role switch on outgoing connections
// - This allows A2DP Source, e.g. smartphone, to become master when we re-connect to it.
gap_set_allow_role_switch(true);
// Register for HCI events
hci_event_callback_registration.callback = &hci_packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
// Inform about audio playback / test options
#ifdef HAVE_POSIX_FILE_IO
if (!btstack_audio_sink_get_instance()) {
printf("No audio playback.\n");
}
else {
printf("Audio playback supported.\n");
}
#ifdef STORE_TO_WAV_FILE
printf("Audio will be stored to \'%s\' file.\n", wav_filename);
#endif
#endif
return 0;
}
/* LISTING_END */
static void a2dp_sink_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
uint8_t status;
uint8_t allocation_method;
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_A2DP_META) return;
a2dp_sink_demo_a2dp_connection_t * a2dp_conn = &a2dp_sink_demo_a2dp_connection;
switch (packet[2]) {
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CONFIGURATION:
printf("A2DP Sink : Received non SBC codec - not implemented\n");
break;
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION: {
printf("A2DP Sink : Received SBC codec configuration\n");
a2dp_conn->sbc_configuration.reconfigure = a2dp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet);
a2dp_conn->sbc_configuration.num_channels = a2dp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet);
a2dp_conn->sbc_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet);
a2dp_conn->sbc_configuration.block_length = a2dp_subevent_signaling_media_codec_sbc_configuration_get_block_length(packet);
a2dp_conn->sbc_configuration.subbands = a2dp_subevent_signaling_media_codec_sbc_configuration_get_subbands(packet);
a2dp_conn->sbc_configuration.min_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet);
a2dp_conn->sbc_configuration.max_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet);
allocation_method = a2dp_subevent_signaling_media_codec_sbc_configuration_get_allocation_method(packet);
// Adapt Bluetooth spec definition to SBC Encoder expected input
a2dp_conn->sbc_configuration.allocation_method = (btstack_sbc_allocation_method_t)(allocation_method - 1);
switch (a2dp_subevent_signaling_media_codec_sbc_configuration_get_channel_mode(packet)) {
case AVDTP_CHANNEL_MODE_JOINT_STEREO:
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
break;
case AVDTP_CHANNEL_MODE_STEREO:
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_STEREO;
break;
case AVDTP_CHANNEL_MODE_DUAL_CHANNEL:
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
break;
case AVDTP_CHANNEL_MODE_MONO:
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_MONO;
break;
default:
btstack_assert(false);
break;
}
dump_sbc_configuration(&a2dp_conn->sbc_configuration);
break;
}
case A2DP_SUBEVENT_STREAM_ESTABLISHED:
status = a2dp_subevent_stream_established_get_status(packet);
if (status != ERROR_CODE_SUCCESS) {
printf("A2DP Sink : Streaming connection failed, status 0x%02x\n", status);
break;
}
a2dp_subevent_stream_established_get_bd_addr(packet, a2dp_conn->addr);
a2dp_conn->a2dp_cid = a2dp_subevent_stream_established_get_a2dp_cid(packet);
a2dp_conn->a2dp_local_seid = a2dp_subevent_stream_established_get_local_seid(packet);
a2dp_conn->stream_state = STREAM_STATE_OPEN;
printf("A2DP Sink : Streaming connection is established, address %s, cid 0x%02x, local seid %d\n",
bd_addr_to_str(a2dp_conn->addr),
a2dp_conn->a2dp_cid,
a2dp_conn->a2dp_local_seid);
#ifdef HAVE_BTSTACK_STDIN
// use address for outgoing connections
memcpy(device_addr, a2dp_conn->addr, 6);
#endif
break;
#ifdef ENABLE_AVDTP_ACCEPTOR_EXPLICIT_START_STREAM_CONFIRMATION
case A2DP_SUBEVENT_START_STREAM_REQUESTED:
printf("A2DP Sink : Explicit Accept to start stream, local_seid %d\n", a2dp_subevent_start_stream_requested_get_local_seid(packet));
a2dp_sink_start_stream_accept(a2dp_cid, a2dp_local_seid);
break;
#endif
case A2DP_SUBEVENT_STREAM_STARTED:
printf("A2DP Sink : Stream started\n");
a2dp_conn->stream_state = STREAM_STATE_PLAYING;
if (a2dp_conn->sbc_configuration.reconfigure) {
media_processing_close();
}
// prepare media processing
media_processing_init(&a2dp_conn->sbc_configuration);
// audio stream is started when buffer reaches minimal level
break;
case A2DP_SUBEVENT_STREAM_SUSPENDED:
printf("A2DP Sink : Stream paused\n");
a2dp_conn->stream_state = STREAM_STATE_PAUSED;
media_processing_pause();
break;
case A2DP_SUBEVENT_STREAM_RELEASED:
printf("A2DP Sink : Stream released\n");
a2dp_conn->stream_state = STREAM_STATE_CLOSED;
media_processing_close();
break;
case A2DP_SUBEVENT_SIGNALING_CONNECTION_RELEASED:
printf("A2DP Sink : Signaling connection released\n");
a2dp_conn->a2dp_cid = 0;
media_processing_close();
break;
default:
break;
}
}
static void handle_l2cap_media_data_packet(uint8_t seid, uint8_t *packet, uint16_t size) {
UNUSED(seid);
int 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;
int packet_length = size - pos;
uint8_t *packet_begin = packet + pos;
const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance();
// process data right away if there's no audio implementation active, e.g. on posix systems to store as .wav
if (!audio) {
sbc_decoder_instance->decode_signed_16(&sbc_decoder_context, 0, packet_begin, packet_length);
return;
}
// store sbc frame size for buffer management
sbc_frame_size = packet_length / sbc_header.num_frames;
int status = btstack_ring_buffer_write(&sbc_frame_ring_buffer, packet_begin, packet_length);
if (status != ERROR_CODE_SUCCESS) {
printf("Error storing samples in SBC ring buffer!!!\n");
}
// decide on audio sync drift based on number of sbc frames in queue
int sbc_frames_in_buffer = btstack_ring_buffer_bytes_available(&sbc_frame_ring_buffer) / sbc_frame_size;
#ifdef HAVE_BTSTACK_AUDIO_EFFECTIVE_SAMPLERATE
if (!l2cap_stream_started && audio_stream_started) {
l2cap_stream_started = 1;
btstack_sample_rate_compensation_init(&sample_rate_compensation, btstack_run_loop_get_time_ms(), a2dp_sink_demo_a2dp_connection.sbc_configuration.sampling_frequency, FLOAT_TO_Q15(1.f));
}
// update sample rate compensation
if (audio_stream_started && (audio != NULL)) {
uint32_t resampling_factor = btstack_sample_rate_compensation_update(&sample_rate_compensation, btstack_run_loop_get_time_ms(), sbc_header.num_frames * 128, audio->get_samplerate());
btstack_resample_set_factor(&resample_instance, resampling_factor);
// printf("sbc buffer level : %"PRIu32"\n", btstack_ring_buffer_bytes_available(&sbc_frame_ring_buffer));
}
#else
uint32_t resampling_factor;
// nominal factor (fixed-point 2^16) and compensation offset
uint32_t nominal_factor = 0x10000;
uint32_t compensation = 0x00100;
if (sbc_frames_in_buffer < OPTIMAL_FRAMES_MIN) {
resampling_factor = nominal_factor - compensation; // stretch samples
}
else if (sbc_frames_in_buffer <= OPTIMAL_FRAMES_MAX) {
resampling_factor = nominal_factor; // nothing to do
}
else {
resampling_factor = nominal_factor + compensation; // compress samples
}
btstack_resample_set_factor(&resample_instance, resampling_factor);
#endif
// start stream if enough frames buffered
if (!audio_stream_started && sbc_frames_in_buffer >= OPTIMAL_FRAMES_MIN) {
media_processing_start();
}
}
static void avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
uint16_t local_cid;
uint8_t status;
bd_addr_t address;
a2dp_sink_demo_avrcp_connection_t * connection = &a2dp_sink_demo_avrcp_connection;
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) return;
switch (packet[2]) {
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED: {
local_cid = avrcp_subevent_connection_established_get_avrcp_cid(packet);
status = avrcp_subevent_connection_established_get_status(packet);
if (status != ERROR_CODE_SUCCESS) {
printf("AVRCP: Connection failed, status 0x%02x\n", status);
connection->avrcp_cid = 0;
return;
}
connection->avrcp_cid = local_cid;
avrcp_subevent_connection_established_get_bd_addr(packet, address);
printf("AVRCP: Connected to %s, cid 0x%02x\n", bd_addr_to_str(address), connection->avrcp_cid);
#ifdef HAVE_BTSTACK_STDIN
// use address for outgoing connections
avrcp_subevent_connection_established_get_bd_addr(packet, device_addr);
#endif
avrcp_target_support_event(connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED);
avrcp_target_support_event(connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_BATT_STATUS_CHANGED);
avrcp_target_battery_status_changed(connection->avrcp_cid, battery_status);
// query supported events:
avrcp_controller_get_supported_events(connection->avrcp_cid);
return;
}
case AVRCP_SUBEVENT_CONNECTION_RELEASED:
printf("AVRCP: Channel released: cid 0x%02x\n", avrcp_subevent_connection_released_get_avrcp_cid(packet));
connection->avrcp_cid = 0;
connection->notifications_supported_by_target = 0;
return;
default:
break;
}
}
static void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
// helper to print c strings
uint8_t avrcp_subevent_value[256];
uint8_t play_status;
uint8_t event_id;
a2dp_sink_demo_avrcp_connection_t * avrcp_connection = &a2dp_sink_demo_avrcp_connection;
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) return;
if (avrcp_connection->avrcp_cid == 0) return;
memset(avrcp_subevent_value, 0, sizeof(avrcp_subevent_value));
switch (packet[2]) {
case AVRCP_SUBEVENT_GET_CAPABILITY_EVENT_ID:
avrcp_connection->notifications_supported_by_target |= (1 << avrcp_subevent_get_capability_event_id_get_event_id(packet));
break;
case AVRCP_SUBEVENT_GET_CAPABILITY_EVENT_ID_DONE:
printf("AVRCP Controller: supported notifications by target:\n");
for (event_id = (uint8_t) AVRCP_NOTIFICATION_EVENT_FIRST_INDEX; event_id < (uint8_t) AVRCP_NOTIFICATION_EVENT_LAST_INDEX; event_id++) {
printf(" - [%s] %s\n",
(avrcp_connection->notifications_supported_by_target & (1 << event_id)) != 0 ? "X" : " ",
avrcp_notification2str((avrcp_notification_event_id_t)event_id));
}
printf("\n\n");
// automatically enable notifications
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_PLAYBACK_STATUS_CHANGED);
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED);
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED);
#ifdef ENABLE_AVRCP_COVER_ART
// image handles become invalid on player change, registe for notifications
avrcp_controller_enable_notification(a2dp_sink_demo_avrcp_connection.avrcp_cid, AVRCP_NOTIFICATION_EVENT_UIDS_CHANGED);
// trigger cover art client connection
a2dp_sink_demo_cover_art_connect();
#endif
break;
case AVRCP_SUBEVENT_NOTIFICATION_STATE:
event_id = (avrcp_notification_event_id_t)avrcp_subevent_notification_state_get_event_id(packet);
printf("AVRCP Controller: %s notification registered\n", avrcp_notification2str(event_id));
break;
case AVRCP_SUBEVENT_NOTIFICATION_PLAYBACK_POS_CHANGED:
printf("AVRCP Controller: Playback position changed, position %d ms\n", (unsigned int) avrcp_subevent_notification_playback_pos_changed_get_playback_position_ms(packet));
break;
case AVRCP_SUBEVENT_NOTIFICATION_PLAYBACK_STATUS_CHANGED:
printf("AVRCP Controller: Playback status changed %s\n", avrcp_play_status2str(avrcp_subevent_notification_playback_status_changed_get_play_status(packet)));
play_status = avrcp_subevent_notification_playback_status_changed_get_play_status(packet);
switch (play_status) {
case AVRCP_PLAYBACK_STATUS_PLAYING:
avrcp_connection->playing = true;
break;
default:
avrcp_connection->playing = false;
break;
}
break;
case AVRCP_SUBEVENT_NOTIFICATION_NOW_PLAYING_CONTENT_CHANGED:
printf("AVRCP Controller: Playing content changed\n");
break;
case AVRCP_SUBEVENT_NOTIFICATION_TRACK_CHANGED:
printf("AVRCP Controller: Track changed\n");
break;
case AVRCP_SUBEVENT_NOTIFICATION_AVAILABLE_PLAYERS_CHANGED:
printf("AVRCP Controller: Available Players Changed\n");
break;
case AVRCP_SUBEVENT_SHUFFLE_AND_REPEAT_MODE: {
uint8_t shuffle_mode = avrcp_subevent_shuffle_and_repeat_mode_get_shuffle_mode(packet);
uint8_t repeat_mode = avrcp_subevent_shuffle_and_repeat_mode_get_repeat_mode(packet);
printf("AVRCP Controller: %s, %s\n", avrcp_shuffle2str(shuffle_mode), avrcp_repeat2str(repeat_mode));
break;
}
case AVRCP_SUBEVENT_NOW_PLAYING_TRACK_INFO:
printf("AVRCP Controller: Track %d\n", avrcp_subevent_now_playing_track_info_get_track(packet));
break;
case AVRCP_SUBEVENT_NOW_PLAYING_TOTAL_TRACKS_INFO:
printf("AVRCP Controller: Total Tracks %d\n", avrcp_subevent_now_playing_total_tracks_info_get_total_tracks(packet));
break;
case AVRCP_SUBEVENT_NOW_PLAYING_TITLE_INFO:
if (avrcp_subevent_now_playing_title_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_title_info_get_value(packet), avrcp_subevent_now_playing_title_info_get_value_len(packet));
printf("AVRCP Controller: Title %s\n", avrcp_subevent_value);
}
break;
case AVRCP_SUBEVENT_NOW_PLAYING_ARTIST_INFO:
if (avrcp_subevent_now_playing_artist_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_artist_info_get_value(packet), avrcp_subevent_now_playing_artist_info_get_value_len(packet));
printf("AVRCP Controller: Artist %s\n", avrcp_subevent_value);
}
break;
case AVRCP_SUBEVENT_NOW_PLAYING_ALBUM_INFO:
if (avrcp_subevent_now_playing_album_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_album_info_get_value(packet), avrcp_subevent_now_playing_album_info_get_value_len(packet));
printf("AVRCP Controller: Album %s\n", avrcp_subevent_value);
}
break;
case AVRCP_SUBEVENT_NOW_PLAYING_GENRE_INFO:
if (avrcp_subevent_now_playing_genre_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_genre_info_get_value(packet), avrcp_subevent_now_playing_genre_info_get_value_len(packet));
printf("AVRCP Controller: Genre %s\n", avrcp_subevent_value);
}
break;
case AVRCP_SUBEVENT_PLAY_STATUS:
printf("AVRCP Controller: Song length %"PRIu32" ms, Song position %"PRIu32" ms, Play status %s\n",
avrcp_subevent_play_status_get_song_length(packet),
avrcp_subevent_play_status_get_song_position(packet),
avrcp_play_status2str(avrcp_subevent_play_status_get_play_status(packet)));
break;
case AVRCP_SUBEVENT_OPERATION_COMPLETE:
printf("AVRCP Controller: %s complete\n", avrcp_operation2str(avrcp_subevent_operation_complete_get_operation_id(packet)));
break;
case AVRCP_SUBEVENT_OPERATION_START:
printf("AVRCP Controller: %s start\n", avrcp_operation2str(avrcp_subevent_operation_start_get_operation_id(packet)));
break;
case AVRCP_SUBEVENT_NOTIFICATION_EVENT_TRACK_REACHED_END:
printf("AVRCP Controller: Track reached end\n");
break;
case AVRCP_SUBEVENT_PLAYER_APPLICATION_VALUE_RESPONSE:
printf("AVRCP Controller: Set Player App Value %s\n", avrcp_ctype2str(avrcp_subevent_player_application_value_response_get_command_type(packet)));
break;
#ifdef ENABLE_AVRCP_COVER_ART
case AVRCP_SUBEVENT_NOTIFICATION_EVENT_UIDS_CHANGED:
if (a2dp_sink_demo_cover_art_client_connected) {
printf("AVRCP Controller: UIDs changed -> disconnect cover art client\n");
avrcp_cover_art_client_disconnect(a2dp_sink_demo_cover_art_cid);
}
break;
case AVRCP_SUBEVENT_NOW_PLAYING_COVER_ART_INFO:
if (avrcp_subevent_now_playing_cover_art_info_get_value_len(packet) == 7) {
memcpy(a2dp_sink_demo_image_handle, avrcp_subevent_now_playing_cover_art_info_get_value(packet), 7);
printf("AVRCP Controller: Cover Art %s\n", a2dp_sink_demo_image_handle);
}
break;
#endif
default:
break;
}
}
static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) return;
uint8_t volume;
char const * button_state;
avrcp_operation_id_t operation_id;
switch (packet[2]) {
case AVRCP_SUBEVENT_NOTIFICATION_VOLUME_CHANGED:
volume = avrcp_subevent_notification_volume_changed_get_absolute_volume(packet);
volume_percentage = volume * 100 / 127;
printf("AVRCP Target : Volume set to %d%% (%d)\n", volume_percentage, volume);
avrcp_volume_changed(volume);
break;
case AVRCP_SUBEVENT_OPERATION:
operation_id = avrcp_subevent_operation_get_operation_id(packet);
button_state = avrcp_subevent_operation_get_button_pressed(packet) > 0 ? "PRESS" : "RELEASE";
switch (operation_id) {
case AVRCP_OPERATION_ID_VOLUME_UP:
printf("AVRCP Target : VOLUME UP (%s)\n", button_state);
break;
case AVRCP_OPERATION_ID_VOLUME_DOWN:
printf("AVRCP Target : VOLUME DOWN (%s)\n", button_state);
break;
default:
return;
}
break;
default:
printf("AVRCP Target : Event 0x%02x is not parsed\n", packet[2]);
break;
}
}
static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) == HCI_EVENT_PIN_CODE_REQUEST) {
bd_addr_t address;
printf("Pin code request - using '0000'\n");
hci_event_pin_code_request_get_bd_addr(packet, address);
gap_pin_code_response(address, "0000");
}
}
static void dump_sbc_configuration(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);
printf("\n");
}
static void media_processing_close(void) {
if (!media_initialized) return;
media_initialized = 0;
audio_stream_started = 0;
sbc_frame_size = 0;
#ifdef HAVE_BTSTACK_AUDIO_EFFECTIVE_SAMPLERATE
l2cap_stream_started = 0;
#endif
#ifdef STORE_TO_WAV_FILE
wav_writer_close();
uint32_t total_frames_nr = sbc_decoder_context.good_frames_nr + sbc_decoder_context.bad_frames_nr + sbc_decoder_context.zero_frames_nr;
printf("WAV Writer: Decoding done. Processed %u SBC frames:\n - %d good\n - %d bad\n", total_frames_nr, sbc_decoder_context.good_frames_nr, total_frames_nr - sbc_decoder_context.good_frames_nr);
printf("WAV Writer: Wrote %u audio frames to wav file: %s\n", audio_frame_count, wav_filename);
#endif
// stop audio playback
const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance();
if (audio) {
printf("close stream\n");
audio->close();
}
}
static int media_processing_init(media_codec_configuration_sbc_t * configuration) {
if (media_initialized) return 0;
sbc_decoder_instance = btstack_sbc_decoder_bluedroid_init_instance(&sbc_decoder_context);
sbc_decoder_instance->configure(&sbc_decoder_context, SBC_MODE_STANDARD, handle_pcm_data, NULL);
#ifdef STORE_TO_WAV_FILE
wav_writer_open(wav_filename, configuration->num_channels, configuration->sampling_frequency);
#endif
btstack_ring_buffer_init(&sbc_frame_ring_buffer, sbc_frame_storage, sizeof(sbc_frame_storage));
btstack_ring_buffer_init(&decoded_audio_ring_buffer, decoded_audio_storage, sizeof(decoded_audio_storage));
btstack_resample_init(&resample_instance, configuration->num_channels);
// setup audio playback
const btstack_audio_sink_t * audio = btstack_audio_sink_get_instance();
if (audio) {
audio->init(NUM_CHANNELS, configuration->sampling_frequency, &playback_handler);
}
audio_stream_started = 0;
media_initialized = 1;
return 0;
}
By as I sad it is compileing but have pairing issues. Your help will be appreciate, :-)