arduino-pico/libraries/BluetoothAudio/src/A2DPSink.cpp
SomebodyOdd 2348051026
A2DPSink: Remove stubs, fix volume and connect callbacks (#2757)
* A2DPSink: Remove Stream implementation and onTransmit
* A2DPSink: Fix onConnect and onVolume callbacks
* A2DPSink: Remove transmit callback field
2025-01-16 09:37:22 -08:00

822 lines
33 KiB
C++

/*
A2DP Sink (Bluetooth audio receiver)
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "A2DPSink.h"
#include <functional>
#define CCALLBACKNAME _A2DPSINKCB
#include <ctocppcallback.h>
#define PACKETHANDLERCB(class, cbFcn) \
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
#define L2CAPPACKETHANDLERCB(class, cbFcn) \
(CCALLBACKNAME<void(uint8_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), \
static_cast<void (*)(uint8_t, uint8_t *, uint16_t)>(CCALLBACKNAME<void(uint8_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
#define PCMDECODERCB(class, cbFcn) \
(CCALLBACKNAME<void(int16_t*, int, int, int, void*), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), \
static_cast<void (*)(int16_t *, int, int, int, void *)>(CCALLBACKNAME<void(int16_t *, int, int, int, void *), __COUNTER__ - 1>::callback))
// Based off of the BlueKitchen A2DP sink demo
/*
Copyright (C) 2023 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 BLUEKITCHEN
GMBH 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
*/
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "btstack.h"
#include "btstack_resample.h"
#include "btstack_ring_buffer.h"
bool A2DPSink::begin() {
if (_running || !_consumer) {
return false;
}
// init protocols
l2cap_init();
sdp_init();
#ifdef ENABLE_BLE
// Initialize LE Security Manager. Needed for cross-transport key derivation
sm_init();
#endif
// Init profiles
a2dp_sink_init();
avrcp_init();
avrcp_controller_init();
avrcp_target_init();
// Configure A2DP Sink
a2dp_sink_register_packet_handler(PACKETHANDLERCB(A2DPSink, a2dp_sink_packet_handler));
a2dp_sink_register_media_handler(L2CAPPACKETHANDLERCB(A2DPSink, handle_l2cap_media_data_packet));
a2dp_sink_stream_endpoint_t * stream_endpoint = &a2dp_sink_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));
if (!local_stream_endpoint) {
DEBUGV("A2DP Source: not enough memory to create local stream endpoint\n");
return false;
}
// - 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(PACKETHANDLERCB(A2DPSink, avrcp_packet_handler));
avrcp_controller_register_packet_handler(PACKETHANDLERCB(A2DPSink, avrcp_controller_packet_handler));
avrcp_target_register_packet_handler(PACKETHANDLERCB(A2DPSink, 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);
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;
avrcp_controller_create_sdp_record(sdp_avrcp_controller_service_buffer, sdp_create_service_record_handle(),
controller_supported_features, NULL, NULL);
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);
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);
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.
if (!_name) {
setName("PicoW A2DP 00:00:00:00:00:00");
}
gap_set_local_name(_name);
// - 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);
_hci.install();
_running = true;
_hci.begin();
return true;
}
bool A2DPSink::disconnect() {
BluetoothLock b;
if (_connected) {
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
}
if (!_running || !_connected) {
return false;
}
_connected = false;
return true;
}
void A2DPSink::clearPairing() {
BluetoothLock b;
if (_connected) {
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
}
gap_delete_all_link_keys();
}
void A2DPSink::playback_handler(int16_t * buffer, uint16_t num_audio_frames) {
// called from lower-layer but guaranteed to be on main thread
if (sbc_frame_size == 0) {
memset(buffer, 0, num_audio_frames * BYTES_PER_FRAME);
return;
}
// first fill from resampled audio
uint32_t bytes_read;
btstack_ring_buffer_read(&decoded_audio_ring_buffer, (uint8_t *) buffer, num_audio_frames * BYTES_PER_FRAME, &bytes_read);
buffer += bytes_read / NUM_CHANNELS;
num_audio_frames -= bytes_read / BYTES_PER_FRAME;
// then start decoding sbc frames using request_* globals
request_buffer = buffer;
request_frames = num_audio_frames;
while (request_frames && btstack_ring_buffer_bytes_available(&sbc_frame_ring_buffer) >= sbc_frame_size) {
// decode frame
uint8_t sbc_frame[MAX_SBC_FRAME_SIZE];
btstack_ring_buffer_read(&sbc_frame_ring_buffer, sbc_frame, sbc_frame_size, &bytes_read);
btstack_sbc_decoder_process_data(&state, 0, sbc_frame, sbc_frame_size);
}
while (request_frames) {
*(request_buffer++) = 0;
*(request_buffer++) = 0;
request_frames--;
}
}
void A2DPSink::handle_pcm_data(int16_t * data, int num_audio_frames, int num_channels, int sample_rate, void * context) {
UNUSED(sample_rate);
UNUSED(context);
UNUSED(num_channels); // must be stereo == 2
// resample into request buffer - add some additional space for resampling
uint32_t resampled_frames = btstack_resample_block(&resample_instance, data, num_audio_frames, output_buffer);
// store data in btstack_audio buffer first
int frames_to_copy = btstack_min(resampled_frames, request_frames);
memcpy(request_buffer, output_buffer, frames_to_copy * BYTES_PER_FRAME);
request_frames -= frames_to_copy;
request_buffer += frames_to_copy * NUM_CHANNELS;
// and rest in ring buffer
int frames_to_store = resampled_frames - frames_to_copy;
if (frames_to_store) {
int status = btstack_ring_buffer_write(&decoded_audio_ring_buffer, (uint8_t *)&output_buffer[frames_to_copy * NUM_CHANNELS], frames_to_store * BYTES_PER_FRAME);
if (status) {
DEBUGV("Error storing samples in PCM ring buffer!!!\n");
}
}
}
int A2DPSink::media_processing_init(BluetoothMediaCodecConfigurationSBC * configuration) {
if (media_initialized) {
return 0;
}
btstack_sbc_decoder_init(&state, mode, PCMDECODERCB(A2DPSink, handle_pcm_data), NULL);
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
_consumer->init(NUM_CHANNELS, configuration->sampling_frequency, this);
audio_stream_started = 0;
media_initialized = 1;
return 0;
}
void A2DPSink::media_processing_start(void) {
if (!media_initialized) {
return;
}
// setup audio playback
_consumer->startStream();
audio_stream_started = 1;
}
void A2DPSink::media_processing_pause(void) {
if (!media_initialized) {
return;
}
// stop audio playback
audio_stream_started = 0;
_consumer->stopStream();
// discard pending data
btstack_ring_buffer_reset(&decoded_audio_ring_buffer);
btstack_ring_buffer_reset(&sbc_frame_ring_buffer);
}
void A2DPSink::media_processing_close(void) {
if (!media_initialized) {
return;
}
media_initialized = 0;
audio_stream_started = 0;
sbc_frame_size = 0;
// stop audio playback
_consumer->close();
}
/* @section Handle Media Data Packet
@text Here the audio data, are received through the handle_l2cap_media_data_packet callback.
Currently, only the SBC media codec is supported. Hence, the media data consists of the media packet header and the SBC packet.
The SBC frame will be stored in a ring buffer for later processing (instead of decoding it to PCM right away which would require a much larger buffer).
If the audio stream wasn't started already and there are enough SBC frames in the ring buffer, start playback.
*/
void A2DPSink::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;
// 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) {
DEBUGV("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;
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);
// start stream if enough frames buffered
if (!audio_stream_started && sbc_frames_in_buffer >= OPTIMAL_FRAMES_MIN) {
media_processing_start();
}
}
int A2DPSink::read_sbc_header(uint8_t * packet, int size, int * offset, avdtp_sbc_codec_header_t * sbc_header) {
int sbc_header_len = 12; // without crc
int pos = *offset;
if (size - pos < sbc_header_len) {
DEBUGV("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++;
*offset = pos;
return 1;
}
int A2DPSink::read_media_data_header(uint8_t *packet, int size, int *offset, avdtp_media_packet_header_t *media_header) {
int media_header_len = 12; // without crc
int pos = *offset;
if (size - pos < media_header_len) {
DEBUGV("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;
return 1;
}
void A2DPSink::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_avrcp_connection_t * connection = &a2dp_sink_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) {
DEBUGV("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);
DEBUGV("AVRCP: Connected to %s, cid 0x%02x\n", bd_addr_to_str(address), connection->avrcp_cid);
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:
DEBUGV("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;
}
}
void A2DPSink::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_avrcp_connection_t * avrcp_connection = &a2dp_sink_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:
DEBUGV("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++) {
DEBUGV(" - [%s] %s\n",
(avrcp_connection->notifications_supported_by_target & (1 << event_id)) != 0 ? "X" : " ",
avrcp_notification2str((avrcp_notification_event_id_t)event_id));
}
DEBUGV("\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);
break;
case AVRCP_SUBEVENT_NOTIFICATION_STATE:
event_id = (avrcp_notification_event_id_t)avrcp_subevent_notification_state_get_event_id(packet);
DEBUGV("AVRCP Controller: %s notification registered\n", avrcp_notification2str((avrcp_notification_event_id_t)event_id));
break;
case AVRCP_SUBEVENT_NOTIFICATION_PLAYBACK_POS_CHANGED:
DEBUGV("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:
DEBUGV("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;
}
if (_playbackStatusCB) {
PlaybackStatus status;
switch (play_status) {
case AVRCP_PLAYBACK_STATUS_PLAYING:
status = PLAYING;
break;
case AVRCP_PLAYBACK_STATUS_PAUSED:
status = PAUSED;
break;
default:
status = STOPPED;
break;
}
_playbackStatusCB(_playbackStatusData, status);
}
break;
case AVRCP_SUBEVENT_NOTIFICATION_NOW_PLAYING_CONTENT_CHANGED:
DEBUGV("AVRCP Controller: Playing content changed\n");
break;
case AVRCP_SUBEVENT_NOTIFICATION_TRACK_CHANGED:
_title[0] = 0;
_artist[0] = 0;
_album[0] = 0;
_genre[0] = 0;
avrcp_controller_get_now_playing_info(avrcp_connection->avrcp_cid);
if (_trackChangedCB) {
_trackChangedCB(_trackChangedData);
}
DEBUGV("AVRCP Controller: Track changed\n");
break;
case AVRCP_SUBEVENT_NOTIFICATION_AVAILABLE_PLAYERS_CHANGED:
DEBUGV("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);
(void) shuffle_mode;
(void) repeat_mode;
DEBUGV("AVRCP Controller: %s, %s\n", avrcp_shuffle2str(shuffle_mode), avrcp_repeat2str(repeat_mode));
break;
}
case AVRCP_SUBEVENT_NOW_PLAYING_TRACK_INFO:
DEBUGV("AVRCP Controller: Track %d\n", avrcp_subevent_now_playing_track_info_get_track(packet));
break;
case AVRCP_SUBEVENT_NOW_PLAYING_TOTAL_TRACKS_INFO:
DEBUGV("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));
strncpy(_title, (char *)avrcp_subevent_value, sizeof(_title));
_title[sizeof(_title) - 1] = 0;
DEBUGV("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));
strncpy(_artist, (char *)avrcp_subevent_value, sizeof(_artist));
_artist[sizeof(_artist) - 1] = 0;
DEBUGV("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));
strncpy(_album, (char *)avrcp_subevent_value, sizeof(_album));
_album[sizeof(_album) - 1] = 0;
DEBUGV("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));
strncpy(_genre, (char *)avrcp_subevent_value, sizeof(_genre));
_genre[sizeof(_genre) - 1] = 0;
DEBUGV("AVRCP Controller: Genre %s\n", avrcp_subevent_value);
}
break;
case AVRCP_SUBEVENT_PLAY_STATUS:
DEBUGV("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:
DEBUGV("AVRCP Controller: %s complete\n", avrcp_operation2str(avrcp_subevent_operation_complete_get_operation_id(packet)));
break;
case AVRCP_SUBEVENT_OPERATION_START:
DEBUGV("AVRCP Controller: %s start\n", avrcp_operation2str(avrcp_subevent_operation_start_get_operation_id(packet)));
break;
case AVRCP_SUBEVENT_NOTIFICATION_EVENT_TRACK_REACHED_END:
DEBUGV("AVRCP Controller: Track reached end\n");
break;
case AVRCP_SUBEVENT_PLAYER_APPLICATION_VALUE_RESPONSE:
DEBUGV("AVRCP Controller: Set Player App Value %s\n", avrcp_ctype2str(avrcp_subevent_player_application_value_response_get_command_type(packet)));
break;
default:
break;
}
}
void A2DPSink::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;
(void) 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;
DEBUGV("AVRCP Target : Volume set to %d%% (%d)\n", volume_percentage, volume);
_consumer->setVolume(volume);
if (_volumeCB) {
_volumeCB(_volumeData, volume);
}
break;
case AVRCP_SUBEVENT_OPERATION:
operation_id = (avrcp_operation_id_t)avrcp_subevent_operation_get_operation_id(packet);
button_state = avrcp_subevent_operation_get_button_pressed(packet) > 0 ? "PRESS" : "RELEASE";
DEBUGV("AVRCP Target: operation %s (%s)\n", avrcp_operation2str(operation_id), button_state);
if (_avrcpCB) {
_avrcpCB(_avrcpData, operation_id, avrcp_subevent_operation_get_button_pressed(packet) > 0);
}
switch (operation_id) {
case AVRCP_OPERATION_ID_VOLUME_UP:
DEBUGV("AVRCP Target : VOLUME UP (%s)\n", button_state);
break;
case AVRCP_OPERATION_ID_VOLUME_DOWN:
DEBUGV("AVRCP Target : VOLUME DOWN (%s)\n", button_state);
break;
default:
return;
}
break;
default:
DEBUGV("AVRCP Target : Event 0x%02x is not parsed\n", packet[2]);
break;
}
}
void A2DPSink::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_a2dp_connection_t * a2dp_conn = &a2dp_sink_a2dp_connection;
switch (packet[2]) {
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CONFIGURATION:
DEBUGV("A2DP Sink : Received non SBC codec - not implemented\n");
break;
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION: {
DEBUGV("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;
}
a2dp_conn->sbc_configuration.dump();
break;
}
case A2DP_SUBEVENT_STREAM_ESTABLISHED:
status = a2dp_subevent_stream_established_get_status(packet);
if (status != ERROR_CODE_SUCCESS) {
DEBUGV("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;
DEBUGV("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);
memcpy(_sourceAddress, a2dp_conn->addr, sizeof(_sourceAddress));
_connected = true;
if (_connectCB) {
_connectCB(_connectData, true);
}
break;
case A2DP_SUBEVENT_STREAM_STARTED:
DEBUGV("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:
DEBUGV("A2DP Sink : Stream paused\n");
a2dp_conn->stream_state = STREAM_STATE_PAUSED;
media_processing_pause();
break;
case A2DP_SUBEVENT_STREAM_RELEASED:
DEBUGV("A2DP Sink : Stream released\n");
a2dp_conn->stream_state = STREAM_STATE_CLOSED;
media_processing_close();
break;
case A2DP_SUBEVENT_SIGNALING_CONNECTION_RELEASED:
DEBUGV("A2DP Sink : Signaling connection released\n");
a2dp_conn->a2dp_cid = 0;
media_processing_close();
_connected = false;
if (_connectCB) {
_connectCB(_connectData, false);
}
break;
default:
break;
}
}