avrcp target: notifications and fragmentation

This commit is contained in:
Milanka Ringwald 2017-09-15 16:36:30 +02:00
parent 63dd1c760e
commit d1207cd896
12 changed files with 699 additions and 342 deletions

View File

@ -53,14 +53,16 @@
#define TABLE_SIZE_441HZ 100
typedef enum {
STREAM_SINE,
STREAM_MOD
STREAM_SINE = 0,
STREAM_MOD,
STREAM_PTS_TEST
} stream_data_source_t;
typedef struct {
uint16_t a2dp_cid;
uint8_t local_seid;
uint8_t connected;
uint32_t time_audio_data_sent; // ms
uint32_t acc_num_missed_samples;
uint32_t samples_ready;
@ -111,6 +113,7 @@ static uint8_t media_sbc_codec_configuration[4];
static a2dp_media_sending_context_t media_tracker;
static uint16_t avrcp_cid;
static uint8_t avrcp_connected;
static stream_data_source_t data_source;
@ -157,22 +160,22 @@ static uint8_t events[] = {
};
typedef struct {
char title[40];
char artist[40];
char album[40];
char genre[40];
uint8_t track_id[8];
uint32_t song_length_ms;
int total_tracks;
int track_nr;
} avrcp_now_playing_info_t;
typedef struct {
avrcp_play_status_t status;
uint32_t song_length_ms; // 0xFFFFFFFF if not supported
avrcp_playback_status_t status;
uint32_t song_position_ms; // 0xFFFFFFFF if not supported
} avrcp_play_status_info_t;
avrcp_now_playing_info_t now_playing_info;
// python -c "print('a'*512)"
static const char title[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
avrcp_track_t tracks[] = {
{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, 1, "Sine", "Generated", "AVRCP Demo", "monotone", 12345},
{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}, 2, "Nao-deceased", "Decease", "AVRCP Demo", "vivid", 12345},
{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}, 3, (char *)title, "Decease", "AVRCP Demo", "vivid", 12345},
};
int current_track_index;
avrcp_play_status_info_t play_info;
/* AVRCP Target context END */
@ -210,6 +213,8 @@ static void produce_audio(int16_t * pcm_buffer, int num_samples){
case STREAM_MOD:
produce_mod_audio(pcm_buffer, num_samples);
break;
default:
break;
}
}
@ -301,7 +306,8 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
uint8_t status;
uint8_t local_seid;
bd_addr_t address;
uint16_t cid;
if (packet_type != HCI_EVENT_PACKET) return;
#ifndef HAVE_BTSTACK_STDIN
@ -319,8 +325,12 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
if (hci_event_packet_get_type(packet) != HCI_EVENT_A2DP_META) return;
switch (packet[2]){
case A2DP_SUBEVENT_INCOMING_CONNECTION_ESTABLISHED:
// TODO: check incoming cid
a2dp_subevent_incoming_connection_established_get_bd_addr(packet, address);
media_tracker.a2dp_cid = a2dp_subevent_incoming_connection_established_get_a2dp_cid(packet);
cid = a2dp_subevent_incoming_connection_established_get_a2dp_cid(packet);
if (cid != media_tracker.a2dp_cid) break;
media_tracker.connected = 1;
printf("A2DP: Incoming connection established: address %s, a2dp cid 0x%02x. Create stream on local seid %d.\n",
bd_addr_to_str(address), media_tracker.a2dp_cid, media_tracker.local_seid);
status = a2dp_source_establish_stream(device_addr, media_tracker.local_seid, &media_tracker.a2dp_cid);
@ -349,7 +359,9 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
break;
case A2DP_SUBEVENT_STREAM_STARTED:
play_info.status = AVRCP_PLAY_STATUS_PLAYING;
play_info.status = AVRCP_PLAYBACK_STATUS_PLAYING;
avrcp_target_set_now_playing_info(avrcp_cid, &tracks[data_source], sizeof(tracks)/sizeof(avrcp_track_t));
avrcp_target_set_playback_status(avrcp_cid, AVRCP_PLAYBACK_STATUS_PLAYING);
a2dp_demo_timer_start(&media_tracker);
printf("A2DP: Stream started.\n");
break;
@ -359,18 +371,26 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
break;
case A2DP_SUBEVENT_STREAM_SUSPENDED:
play_info.status = AVRCP_PLAY_STATUS_PAUSED;
play_info.status = AVRCP_PLAYBACK_STATUS_PAUSED;
avrcp_target_set_playback_status(avrcp_cid, AVRCP_PLAYBACK_STATUS_PAUSED);
printf("A2DP: Stream paused.\n");
a2dp_demo_timer_pause(&media_tracker);
break;
case A2DP_SUBEVENT_STREAM_RELEASED:
play_info.status = AVRCP_PLAY_STATUS_STOPPED;
avrcp_target_set_now_playing_info(avrcp_cid, NULL, sizeof(tracks)/sizeof(avrcp_track_t));
play_info.status = AVRCP_PLAYBACK_STATUS_STOPPED;
avrcp_target_set_playback_status(avrcp_cid, AVRCP_PLAYBACK_STATUS_STOPPED);
printf("A2DP: Stream released.\n");
a2dp_demo_timer_stop(&media_tracker);
break;
case A2DP_SUBEVENT_SIGNALING_CONNECTION_RELEASED:
printf("A2DP: Signaling released.\n");
cid = a2dp_subevent_signaling_connection_released_get_a2dp_cid(packet);
if (cid == media_tracker.a2dp_cid) {
media_tracker.connected = 0;
media_tracker.a2dp_cid = 0;
}
break;
default:
printf("A2DP: event 0x%02x is not parsed\n", packet[2]);
@ -383,8 +403,7 @@ static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, u
UNUSED(size);
bd_addr_t event_addr;
uint16_t local_cid;
uint8_t a2dp_cmd_status;
uint8_t avrcp_cmd_status;
uint8_t status = ERROR_CODE_SUCCESS;
if (packet_type != HCI_EVENT_PACKET) return;
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) return;
@ -392,84 +411,56 @@ static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, u
switch (packet[2]){
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED: {
local_cid = avrcp_subevent_connection_established_get_avrcp_cid(packet);
if (avrcp_cid != 0 && avrcp_cid != local_cid) {
printf("AVRCP: Connection failed, expected 0x%02X l2cap cid, received 0x%02X\n", avrcp_cid, local_cid);
return;
}
avrcp_cmd_status = avrcp_subevent_connection_established_get_status(packet);
if (avrcp_cmd_status != ERROR_CODE_SUCCESS){
printf("AVRCP: Connection failed: status 0x%02x\n", avrcp_cmd_status);
avrcp_cid = 0;
// if (avrcp_cid != 0 && avrcp_cid != local_cid) {
// printf("AVRCP: Connection failed, expected 0x%02X l2cap cid, received 0x%02X\n", avrcp_cid, local_cid);
// return;
// }
// if (avrcp_cid != local_cid) break;
status = avrcp_subevent_connection_established_get_status(packet);
if (status != ERROR_CODE_SUCCESS){
printf("AVRCP: Connection failed: status 0x%02x\n", status);
return;
}
avrcp_connected = 1;
avrcp_cid = local_cid;
play_info.song_length_ms = 0xFFFFFFFF;
play_info.song_position_ms = 0xFFFFFFFF;
play_info.status = AVRCP_PLAY_STATUS_ERROR;
avrcp_subevent_connection_established_get_bd_addr(packet, event_addr);
printf("AVRCP: connected to %s, avrcp_cid 0x%02x\n", bd_addr_to_str(event_addr), local_cid);
avrcp_target_set_now_playing_info(avrcp_cid, NULL, sizeof(tracks)/sizeof(avrcp_track_t));
avrcp_target_set_unit_info(avrcp_cid, AVRCP_SUBUNIT_TYPE_AUDIO, company_id);
avrcp_target_set_subunit_info(avrcp_cid, AVRCP_SUBUNIT_TYPE_AUDIO, (uint8_t *)subunit_info, sizeof(subunit_info));
return;
}
case AVRCP_SUBEVENT_UNIT_INFO_QUERY:
avrcp_target_unit_info(avrcp_cid, AVRCP_SUBUNIT_TYPE_AUDIO, company_id);
break;
case AVRCP_SUBEVENT_SUBUNIT_INFO_QUERY:
avrcp_target_subunit_info(avrcp_cid, AVRCP_SUBUNIT_TYPE_UNIT, avrcp_subevent_subunit_info_query_get_offset(packet), (uint8_t *)subunit_info);
break;
case AVRCP_SUBEVENT_EVENT_IDS_QUERY:
avrcp_target_supported_events(avrcp_cid, events_num, events, sizeof(events));
status = avrcp_target_supported_events(avrcp_cid, events_num, events, sizeof(events));
break;
case AVRCP_SUBEVENT_COMPANY_IDS_QUERY:
avrcp_target_supported_companies(avrcp_cid, companies_num, companies, sizeof(companies));
status = avrcp_target_supported_companies(avrcp_cid, companies_num, companies, sizeof(companies));
break;
case AVRCP_SUBEVENT_PLAY_STATUS_QUERY:
avrcp_target_play_status(avrcp_cid, play_info.song_length_ms, play_info.song_position_ms, play_info.status);
break;
case AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY:
avrcp_target_set_now_playing_title(avrcp_cid, now_playing_info.title);
avrcp_target_set_now_playing_artist(avrcp_cid, now_playing_info.artist);
avrcp_target_set_now_playing_album(avrcp_cid, now_playing_info.album);
avrcp_target_set_now_playing_genre(avrcp_cid, now_playing_info.genre);
avrcp_target_set_now_playing_track_nr(avrcp_cid, now_playing_info.track_nr);
avrcp_target_set_now_playing_total_tracks(avrcp_cid, now_playing_info.total_tracks);
avrcp_target_set_now_playing_song_length_ms(avrcp_cid, now_playing_info.song_length_ms);
avrcp_target_now_playing_info(avrcp_cid);
status = avrcp_target_play_status(avrcp_cid, play_info.song_length_ms, play_info.song_position_ms, play_info.status);
break;
// case AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY:
// status = avrcp_target_now_playing_info(avrcp_cid);
// break;
case AVRCP_SUBEVENT_OPERATION:{
avrcp_operation_id_t operation_id = avrcp_subevent_operation_get_operation_id(packet);
uint8_t operands_length = avrcp_subevent_operation_get_operands_length(packet);
uint8_t operand = avrcp_subevent_operation_get_operand(packet);
printf("AVRCP: operation 0x%02x, operands length %d\n", operation_id, operands_length);
if (!media_tracker.connected) break;
switch (operation_id){
case AVRCP_OPERATION_ID_PLAY:
a2dp_cmd_status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case AVRCP_OPERATION_ID_PAUSE:
a2dp_cmd_status = a2dp_source_pause_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
status = a2dp_source_pause_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case AVRCP_OPERATION_ID_STOP:
case AVRCP_OPERATION_ID_REWIND:
case AVRCP_OPERATION_ID_FAST_FORWARD:
case AVRCP_OPERATION_ID_FORWARD:
case AVRCP_OPERATION_ID_BACKWARD:
case AVRCP_OPERATION_ID_SKIP:
case AVRCP_OPERATION_ID_VOLUME_UP:
case AVRCP_OPERATION_ID_VOLUME_DOWN:
case AVRCP_OPERATION_ID_MUTE:
case AVRCP_OPERATION_ID_UNDEFINED:
avrcp_cmd_status = avrcp_target_operation_not_implemented(avrcp_cid, operation_id, operands_length, operand);
status = a2dp_source_disconnect(media_tracker.a2dp_cid);
break;
default:
return;
}
// printf("a2dp_cmd_status 0x%02x \n", a2dp_cmd_status);
(void) a2dp_cmd_status;
avrcp_cmd_status = avrcp_target_operation_accepted(avrcp_cid, operation_id, operands_length, operand);
(void) avrcp_cmd_status;
break;
}
case AVRCP_SUBEVENT_CONNECTION_RELEASED:
@ -480,6 +471,10 @@ static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, u
printf("AVRCP: event not parsed %02x\n", packet[2]);
break;
}
if (status != ERROR_CODE_SUCCESS){
printf("Responding to event 0x%02x failed with status 0x%02x\n", packet[2], status);
}
}
#ifdef HAVE_BTSTACK_STDIN
@ -491,7 +486,8 @@ static void show_usage(void){
printf("B - AVDTP Source disconnect\n");
printf("c - AVRCP Target create connection to addr %s\n", device_addr_string);
printf("C - AVRCP Target disconnect\n");
printf("0 - AVRCP reset now playing info\n");
printf("x - start streaming sine\n");
if (hxcmod_initialized){
printf("z - start streaming '%s'\n", mod_name);
@ -510,7 +506,7 @@ static void stdin_process(char cmd){
status = a2dp_source_establish_stream(device_addr, media_tracker.local_seid, &media_tracker.a2dp_cid);
break;
case 'B':
printf(" - AVDTP Source Disconnect\n");
printf(" - AVDTP Source Disconnect from cid 0x%2x\n", media_tracker.a2dp_cid);
status = a2dp_source_disconnect(media_tracker.a2dp_cid);
break;
case 'c':
@ -526,29 +522,45 @@ static void stdin_process(char cmd){
case '\r':
break;
case 't':
printf("STREAM_PTS_TEST.\n");
data_source = STREAM_PTS_TEST;
avrcp_target_set_now_playing_info(avrcp_cid, &tracks[data_source], sizeof(tracks)/sizeof(avrcp_track_t));
if (!media_tracker.connected) break;
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case 'x':
avrcp_target_set_now_playing_info(avrcp_cid, &tracks[data_source], sizeof(tracks)/sizeof(avrcp_track_t));
if (data_source == STREAM_SINE) {
printf("Already playing sine.\n");
return;
}
printf("Playing sine.\n");
data_source = STREAM_SINE;
if (!media_tracker.connected) break;
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case 'z':
avrcp_target_set_now_playing_info(avrcp_cid, &tracks[data_source], sizeof(tracks)/sizeof(avrcp_track_t));
if (data_source == STREAM_MOD) {
printf("Already playing mode.\n");
return;
}
printf("Playing mod.\n");
data_source = STREAM_MOD;
if (!media_tracker.connected) break;
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case 'p':
printf("Pause stream.\n");
if (!media_tracker.connected) break;
status = a2dp_source_pause_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
break;
case '0':
avrcp_target_set_now_playing_info(avrcp_cid, NULL, sizeof(tracks)/sizeof(avrcp_track_t));
printf("Reset now playing info\n");
break;
default:
show_usage();
return;
@ -584,7 +596,6 @@ int btstack_main(int argc, const char * argv[]){
// Initialize AVRCP Target
avrcp_target_init();
avrcp_target_register_packet_handler(&avrcp_target_packet_handler);
// Initialize SDP
sdp_init();
memset(sdp_a2dp_source_service_buffer, 0, sizeof(sdp_a2dp_source_service_buffer));
@ -601,16 +612,6 @@ int btstack_main(int argc, const char * argv[]){
hxcmod_load(&mod_context, (void *) &mod_data, mod_len);
printf("loaded mod '%s', size %u\n", mod_name, mod_len);
}
// For PTS test
memcpy(now_playing_info.title, "Title 1", 8);
memcpy(now_playing_info.artist, "Artist 1", 8);
memcpy(now_playing_info.album, "Album 1", 8);
memcpy(now_playing_info.genre, "Genre 1", 8);
now_playing_info.track_nr = 1;
now_playing_info.total_tracks = 10;
now_playing_info.song_length_ms = 3655;
// parse human readable Bluetooth address
sscanf_bd_addr(device_addr_string, device_addr);

View File

@ -1697,21 +1697,6 @@ typedef uint8_t sm_key_t[16];
*/
#define AVRCP_SUBEVENT_PLAYER_APPLICATION_VALUE_RESPONSE 0x0F
/**
* @format 12
* @param subevent_code
* @param avrcp_cid
*/
#define AVRCP_SUBEVENT_UNIT_INFO_QUERY 0x10
/**
* @format 121
* @param subevent_code
* @param avrcp_cid
* @param offset page*4
*/
#define AVRCP_SUBEVENT_SUBUNIT_INFO_QUERY 0x11
/**
* @format 12
* @param subevent_code
@ -1733,13 +1718,6 @@ typedef uint8_t sm_key_t[16];
*/
#define AVRCP_SUBEVENT_PLAY_STATUS_QUERY 0x14
/**
* @format 12
* @param subevent_code
* @param avrcp_cid
*/
#define AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY 0x15
/**
* @format 12111
* @param subevent_code
@ -1748,7 +1726,7 @@ typedef uint8_t sm_key_t[16];
* @param operands_length
* @param operand
*/
#define AVRCP_SUBEVENT_OPERATION 0x16
#define AVRCP_SUBEVENT_OPERATION 0x15
/**
* @format 121BH1

View File

@ -5494,35 +5494,6 @@ static inline uint8_t avrcp_subevent_player_application_value_response_get_comma
return event[5];
}
/**
* @brief Get field avrcp_cid from event AVRCP_SUBEVENT_UNIT_INFO_QUERY
* @param event packet
* @return avrcp_cid
* @note: btstack_type 2
*/
static inline uint16_t avrcp_subevent_unit_info_query_get_avrcp_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field avrcp_cid from event AVRCP_SUBEVENT_SUBUNIT_INFO_QUERY
* @param event packet
* @return avrcp_cid
* @note: btstack_type 2
*/
static inline uint16_t avrcp_subevent_subunit_info_query_get_avrcp_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field offset from event AVRCP_SUBEVENT_SUBUNIT_INFO_QUERY
* @param event packet
* @return offset
* @note: btstack_type 1
*/
static inline uint8_t avrcp_subevent_subunit_info_query_get_offset(const uint8_t * event){
return event[5];
}
/**
* @brief Get field avrcp_cid from event AVRCP_SUBEVENT_COMPANY_IDS_QUERY
* @param event packet
@ -5553,16 +5524,6 @@ static inline uint16_t avrcp_subevent_play_status_query_get_avrcp_cid(const uint
return little_endian_read_16(event, 3);
}
/**
* @brief Get field avrcp_cid from event AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY
* @param event packet
* @return avrcp_cid
* @note: btstack_type 2
*/
static inline uint16_t avrcp_subevent_now_playing_info_query_get_avrcp_cid(const uint8_t * event){
return little_endian_read_16(event, 3);
}
/**
* @brief Get field avrcp_cid from event AVRCP_SUBEVENT_OPERATION
* @param event packet

View File

@ -355,6 +355,27 @@ uint32_t btstack_atoi(const char *str){
}
}
int string_len_for_uint32(uint32_t i){
if (i < 10) return 1;
if (i < 100) return 2;
if (i < 1000) return 3;
if (i < 10000) return 4;
if (i < 100000) return 5;
if (i < 1000000) return 6;
if (i < 10000000) return 7;
if (i < 100000000) return 8;
if (i < 1000000000) return 9;
return 10;
}
int count_set_bits_uint32(uint32_t x){
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);
return x;
}
/*
* CRC (reversed crc) lookup table as calculated by the table generator in ETSI TS 101 369 V6.3.0.

View File

@ -253,6 +253,20 @@ int uuid_has_bluetooth_prefix(const uint8_t * uuid128);
*/
uint32_t btstack_atoi(const char *str);
/**
* @brief Return number of digits of a uint32 number
* @param uint32_number
* @return num_digits
*/
int string_len_for_uint32(uint32_t i);
/**
* @brief Return number of set bits in a uint32 number
* @param uint32_number
* @return num_set_bits
*/
int count_set_bits_uint32(uint32_t x);
/**
* CRC8 functions using ETSI TS 101 369 V6.3.0.
* Only used by RFCOMM

View File

@ -591,6 +591,7 @@ void avdtp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet
stream_endpoint->connection = connection;
stream_endpoint->l2cap_media_cid = l2cap_event_channel_opened_get_local_cid(packet);
stream_endpoint->media_con_handle = l2cap_event_channel_opened_get_handle(packet);
log_info(" -> AVDTP_STREAM_ENDPOINT_OPENED, avdtp cid 0x%02x, l2cap_media_cid 0x%02x, local seid %d, remote seid %d", context->avdtp_cid, stream_endpoint->l2cap_media_cid, avdtp_local_seid(stream_endpoint), avdtp_remote_seid(stream_endpoint));
avdtp_streaming_emit_connection_established(context->avdtp_callback, context->avdtp_cid, event_addr, avdtp_local_seid(stream_endpoint), avdtp_remote_seid(stream_endpoint), 0);
return;

View File

@ -616,6 +616,10 @@ void avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet
break;
}
connection->l2cap_signaling_cid = local_cid;
connection->song_length_ms = 0xFFFFFFFF;
connection->song_position_ms = 0xFFFFFFFF;
connection->playback_status = AVRCP_PLAYBACK_STATUS_ERROR;
log_info("L2CAP_EVENT_CHANNEL_OPENED avrcp_cid 0x%02x, l2cap_signaling_cid 0x%02x", connection->avrcp_cid, connection->l2cap_signaling_cid);
connection->state = AVCTP_CONNECTION_OPENED;
avrcp_emit_connection_established(context->avrcp_callback, connection->avrcp_cid, event_addr, ERROR_CODE_SUCCESS);

View File

@ -102,7 +102,8 @@ typedef enum {
} avrcp_capability_id_t;
typedef enum {
AVRCP_MEDIA_ATTR_TITLE = 1,
AVRCP_MEDIA_ATTR_NONE = 0,
AVRCP_MEDIA_ATTR_TITLE,
AVRCP_MEDIA_ATTR_ARTIST,
AVRCP_MEDIA_ATTR_ALBUM,
AVRCP_MEDIA_ATTR_TRACK,
@ -118,6 +119,7 @@ typedef enum {
AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES = 0x20,
AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30,
AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31,
AVRCP_PDU_ID_REQUEST_CONTINUING_RESPONSE = 0x40,
AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME = 0x50,
AVRCP_PDU_ID_UNDEFINED = 0xFF
} avrcp_pdu_id_t;
@ -138,6 +140,10 @@ typedef enum {
AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED = 0x0d // The volume has been changed locally on the TG, see 6.13.3.
} avrcp_notification_event_id_t;
// control command response: accepted, rejected, interim
// status command response: not implemented, rejected, in transition, stable
// notify command response: not implemented, rejected, changed
typedef enum {
AVRCP_CTYPE_CONTROL = 0,
AVRCP_CTYPE_STATUS,
@ -157,9 +163,6 @@ typedef enum {
AVRCP_CTYPE_RESPONSE_INTERIM // target is unable to respond with either ACCEPTED or REJECTED within 100 millisecond
} avrcp_command_type_t;
// control command response: accepted, rejected, interim
// status command response: not implemented, rejected, in transiiton, stable
// notify command response: not implemented, rejected, changed
typedef enum {
AVRCP_SUBUNIT_TYPE_MONITOR = 0,
AVRCP_SUBUNIT_TYPE_AUDIO = 1,
@ -213,13 +216,22 @@ typedef enum {
} avrcp_operation_id_t;
typedef enum{
AVRCP_PLAY_STATUS_STOPPED = 0x00,
AVRCP_PLAY_STATUS_PLAYING,
AVRCP_PLAY_STATUS_PAUSED,
AVRCP_PLAY_STATUS_FWD_SEEK,
AVRCP_PLAY_STATUS_REV_SEEK,
AVRCP_PLAY_STATUS_ERROR = 0xFF
} avrcp_play_status_t;
AVRCP_PLAYBACK_STATUS_STOPPED = 0x00,
AVRCP_PLAYBACK_STATUS_PLAYING,
AVRCP_PLAYBACK_STATUS_PAUSED,
AVRCP_PLAYBACK_STATUS_FWD_SEEK,
AVRCP_PLAYBACK_STATUS_REV_SEEK,
AVRCP_PLAYBACK_STATUS_ERROR = 0xFF
} avrcp_playback_status_t;
typedef enum{
AVRCP_BATTERY_STATUS_NORMAL = 0x00, // Battery operation is in normal state
AVRCP_BATTERY_STATUS_WARNING, // unable to operate soon. Is provided when the battery level is going down.
AVRCP_BATTERY_STATUS_CRITICAL, // can not operate any more. Is provided when the battery level is going down.
AVRCP_BATTERY_STATUS_EXTERNAL, // Plugged to external power supply
AVRCP_BATTERY_STATUS_FULL_CHARGE // when the device is completely charged from the external power supply
} avrcp_battery_status_t;
typedef enum {
AVCTP_CONNECTION_IDLE,
@ -240,6 +252,17 @@ typedef struct {
uint8_t * value;
} avrcp_now_playing_info_item_t;
typedef struct {
uint8_t track_id[8];
uint16_t track_nr;
char * title;
char * artist;
char * album;
char * genre;
uint32_t song_length_ms;
uint32_t song_position_ms;
} avrcp_track_t;
typedef struct {
btstack_linked_item_t item;
bd_addr_t remote_addr;
@ -255,6 +278,8 @@ typedef struct {
avrcp_command_type_t command_type;
avrcp_subunit_type_t subunit_type;
avrcp_subunit_id_t subunit_id;
avrcp_packet_type_t packet_type;
uint8_t cmd_operands[20];
uint8_t cmd_operands_length;
btstack_timer_source_t press_and_hold_cmd_timer;
@ -263,14 +288,36 @@ typedef struct {
uint16_t notifications_to_register;
uint16_t notifications_to_deregister;
avrcp_subunit_type_t unit_type;
uint32_t company_id;
avrcp_subunit_type_t subunit_info_type;
const uint8_t * subunit_info_data;
uint16_t subunit_info_data_size;
avrcp_now_playing_info_item_t now_playing_info[AVRCP_MEDIA_ATTR_COUNT];
uint8_t track_id[8];
uint32_t song_length_ms;
uint32_t song_position_ms;
int total_tracks;
int track_nr;
// used for fragmentation
int offset;
int total_num_bytes;
uint8_t track_selected;
uint8_t track_changed;
avrcp_playback_status_t playback_status;
uint8_t playback_status_changed;
uint8_t playing_content_changed;
avrcp_battery_status_t battery_status;
uint8_t battery_status_changed;
uint8_t volume_percentage;
uint8_t volume_percentage_changed;
uint8_t now_playing_info_response;
uint8_t now_playing_info_attr_bitmap;
// used for fragmentation
avrcp_media_attribute_id_t next_attr_id;
int fragmented_value_offset;
} avrcp_connection_t;
typedef enum {

View File

@ -601,6 +601,8 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
event[pos++] = 0;
}
break;
default:
break;
}
}
event[1] = pos - 2;

View File

@ -46,6 +46,11 @@
#include "btstack.h"
#include "classic/avrcp.h"
#define AVRCP_ATTR_HEADER_LEN 8
static const uint8_t AVRCP_NOTIFICATION_TRACK_SELECTED[] = {0,0,0,0,0,0,0,0};
static const uint8_t AVRCP_NOTIFICATION_TRACK_NOT_SELECTED[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
static avrcp_context_t avrcp_target_context;
void avrcp_target_create_sdp_record(uint8_t * service, uint32_t service_record_handle, uint8_t browsing, uint16_t supported_features, const char * service_name, const char * service_provider_name){
@ -67,31 +72,6 @@ static void avrcp_target_emit_operation(btstack_packet_handler_t callback, uint1
(*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static void avrcp_target_emit_respond_query(btstack_packet_handler_t callback, uint16_t avrcp_cid, uint8_t subeventID){
if (!callback) return;
uint8_t event[5];
int pos = 0;
event[pos++] = HCI_EVENT_AVRCP_META;
event[pos++] = sizeof(event) - 2;
event[pos++] = subeventID;
little_endian_store_16(event, pos, avrcp_cid);
pos += 2;
(*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static void avrcp_target_emit_respond_subunit_info_query(btstack_packet_handler_t callback, uint16_t avrcp_cid, uint8_t offset){
if (!callback) return;
uint8_t event[6];
int pos = 0;
event[pos++] = HCI_EVENT_AVRCP_META;
event[pos++] = sizeof(event) - 2;
event[pos++] = AVRCP_SUBEVENT_SUBUNIT_INFO_QUERY;
little_endian_store_16(event, pos, avrcp_cid);
pos += 2;
event[pos++] = offset;
(*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static void avrcp_target_emit_respond_vendor_dependent_query(btstack_packet_handler_t callback, uint16_t avrcp_cid, uint8_t subevent_id){
if (!callback) return;
uint8_t event[5];
@ -103,8 +83,7 @@ static void avrcp_target_emit_respond_vendor_dependent_query(btstack_packet_hand
(*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event));
}
static uint16_t avrcp_target_pack_single_element_attribute_number(uint8_t * packet, uint16_t size, uint16_t pos, avrcp_media_attribute_id_t attr_id, uint32_t value){
UNUSED(size);
static uint16_t avrcp_target_pack_single_element_attribute_number(uint8_t * packet, uint16_t pos, avrcp_media_attribute_id_t attr_id, uint32_t value){
if ((attr_id < 1) || (attr_id > AVRCP_MEDIA_ATTR_COUNT)) return 0;
uint16_t attr_value_length = sprintf((char *)(packet+pos+8), "%0" PRIu32, value);
big_endian_store_32(packet, pos, attr_id);
@ -116,27 +95,29 @@ static uint16_t avrcp_target_pack_single_element_attribute_number(uint8_t * pack
return 8 + attr_value_length;
}
static uint16_t avrcp_target_pack_single_element_attribute_string(uint8_t * packet, uint16_t size, uint16_t pos, rfc2978_charset_mib_enumid_t mib_enumid, avrcp_media_attribute_id_t attr_id, uint8_t * attr_value, uint16_t attr_value_length){
UNUSED(size);
if (attr_value_length == 0) return 0;
static uint16_t avrcp_target_pack_single_element_attribute_string(uint8_t * packet, uint16_t pos, rfc2978_charset_mib_enumid_t mib_enumid, avrcp_media_attribute_id_t attr_id, uint8_t * attr_value, uint16_t attr_value_to_copy, uint16_t attr_value_size, uint8_t header){
if (attr_value_size == 0) return 0;
if ((attr_id < 1) || (attr_id > AVRCP_MEDIA_ATTR_COUNT)) return 0;
big_endian_store_32(packet, pos, attr_id);
pos += 4;
big_endian_store_16(packet, pos, mib_enumid);
pos += 2;
big_endian_store_16(packet, pos, attr_value_length);
pos += 2;
memcpy(packet+pos, attr_value, attr_value_length);
pos += attr_value_length;
return 8 + attr_value_length;
if (header){
big_endian_store_32(packet, pos, attr_id);
pos += 4;
big_endian_store_16(packet, pos, mib_enumid);
pos += 2;
big_endian_store_16(packet, pos, attr_value_size);
pos += 2;
}
memcpy(packet+pos, attr_value, attr_value_to_copy);
pos += attr_value_size;
return header * 8 + attr_value_size;
}
static int avrcp_target_send_now_playing_info(uint16_t cid, avrcp_connection_t * connection){
uint16_t pos = 0;
l2cap_reserve_packet_buffer();
uint8_t * packet = l2cap_get_outgoing_buffer();
uint16_t size = l2cap_get_remote_mtu_for_local_cid(connection->l2cap_signaling_cid);
packet[pos++] = (connection->transaction_label << 4) | (AVRCP_SINGLE_PACKET << 2) | (AVRCP_RESPONSE_FRAME << 1) | 0;
// Profile IDentifier (PID)
packet[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL >> 8;
@ -154,53 +135,114 @@ static int avrcp_target_send_now_playing_info(uint16_t cid, avrcp_connection_t *
pos += 3;
packet[pos++] = AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES;
packet[pos++] = 0;
// num_attrs
int i;
uint16_t playing_info_buffer_len = 1;
// reserve byte for packet type
uint8_t pos_packet_type = pos;
pos++;
uint16_t playing_info_buffer_len_position = pos;
uint8_t media_attr_count = 0;
uint16_t media_attr_count_position = pos + 2;
pos += 3;
pos += 2;
for (i = 0; i < AVRCP_MEDIA_ATTR_COUNT; i++){
int attr_id = i+1;
int attr_len;
switch (attr_id){
case AVRCP_MEDIA_ATTR_TRACK:
attr_len = avrcp_target_pack_single_element_attribute_number(packet, size, pos, attr_id, connection->track_nr);
break;
case AVRCP_MEDIA_ATTR_TOTAL_TRACKS:
attr_len = avrcp_target_pack_single_element_attribute_number(packet, size, pos, attr_id, connection->total_tracks);
break;
case AVRCP_MEDIA_ATTR_SONG_LENGTH:
attr_len = avrcp_target_pack_single_element_attribute_number(packet, size, pos, attr_id, connection->song_length_ms);
break;
default:
attr_len = avrcp_target_pack_single_element_attribute_string(packet, size, pos, UTF8, attr_id, connection->now_playing_info[i].value, connection->now_playing_info[i].len);
break;
}
if (attr_len > 0) {
pos += attr_len;
playing_info_buffer_len += attr_len;
media_attr_count++;
if (connection->next_attr_id == AVRCP_MEDIA_ATTR_NONE){
packet[pos_packet_type] = AVRCP_SINGLE_PACKET;
connection->packet_type = AVRCP_SINGLE_PACKET;
packet[pos++] = count_set_bits_uint32(connection->now_playing_info_attr_bitmap);
connection->next_attr_id++;
}
uint8_t fragmented = 0;
int num_free_bytes = size - pos - 2;
uint8_t MAX_NUMBER_ATTR_LEN = 10;
while (!fragmented && num_free_bytes > 0 && connection->next_attr_id <= AVRCP_MEDIA_ATTR_SONG_LENGTH){
avrcp_media_attribute_id_t attr_id = connection->next_attr_id;
int attr_index = attr_id - 1;
if (connection->now_playing_info_attr_bitmap & (1 << attr_id)){
int num_written_bytes = 0;
int num_bytes_to_write = 0;
switch (attr_id){
case AVRCP_MEDIA_ATTR_NONE:
break;
case AVRCP_MEDIA_ATTR_TRACK:
num_bytes_to_write = AVRCP_ATTR_HEADER_LEN + MAX_NUMBER_ATTR_LEN;
if (num_free_bytes >= num_bytes_to_write){
num_written_bytes = avrcp_target_pack_single_element_attribute_number(packet, pos, attr_id, connection->track_nr);
break;
}
fragmented = 1;
connection->fragmented_value_offset = 0;
break;
case AVRCP_MEDIA_ATTR_TOTAL_TRACKS:
num_bytes_to_write = AVRCP_ATTR_HEADER_LEN + MAX_NUMBER_ATTR_LEN;
if (num_free_bytes >= num_bytes_to_write){
num_written_bytes = avrcp_target_pack_single_element_attribute_number(packet, pos, attr_id, connection->total_tracks);
break;
}
fragmented = 1;
connection->fragmented_value_offset = 0;
break;
case AVRCP_MEDIA_ATTR_SONG_LENGTH:
num_bytes_to_write = AVRCP_ATTR_HEADER_LEN + MAX_NUMBER_ATTR_LEN;
if (num_free_bytes >= num_bytes_to_write){
num_written_bytes = avrcp_target_pack_single_element_attribute_number(packet, pos, attr_id, connection->song_length_ms);
break;
}
fragmented = 1;
connection->fragmented_value_offset = 0;
break;
default:{
uint8_t header = (connection->fragmented_value_offset == 0);
uint8_t * attr_value = (uint8_t *) (connection->now_playing_info[attr_index].value + connection->fragmented_value_offset);
uint16_t attr_value_len = connection->now_playing_info[attr_index].len - connection->fragmented_value_offset;
num_bytes_to_write = attr_value_len + header * AVRCP_ATTR_HEADER_LEN;
if (num_bytes_to_write <= num_free_bytes){
connection->fragmented_value_offset = 0;
num_written_bytes = num_bytes_to_write;
avrcp_target_pack_single_element_attribute_string(packet, pos, UTF8, attr_id, attr_value, attr_value_len, connection->now_playing_info[attr_index].len, header);
break;
}
fragmented = 1;
num_written_bytes = num_free_bytes;
attr_value_len = num_free_bytes - header * AVRCP_ATTR_HEADER_LEN;
avrcp_target_pack_single_element_attribute_string(packet, pos, UTF8, attr_id, attr_value, attr_value_len, connection->now_playing_info[attr_index].len, header);
connection->fragmented_value_offset += attr_value_len;
break;
}
}
pos += num_written_bytes;
num_free_bytes -= num_written_bytes;
}
if (!fragmented){
connection->next_attr_id++;
}
}
big_endian_store_16(packet, playing_info_buffer_len_position, playing_info_buffer_len);
packet[media_attr_count_position] = media_attr_count;
// TODO fragmentation
if (playing_info_buffer_len + pos > l2cap_get_remote_mtu_for_local_cid(connection->l2cap_signaling_cid)) {
return ERROR_CODE_MEMORY_CAPACITY_EXCEEDED;
}
connection->wait_to_send = 0;
return l2cap_send_prepared(cid, pos);
if (fragmented){
switch (connection->packet_type){
case AVRCP_SINGLE_PACKET:
connection->packet_type = AVRCP_START_PACKET;
break;
default:
connection->packet_type = AVRCP_CONTINUE_PACKET;
break;
}
} else {
if (connection->next_attr_id >= AVRCP_MEDIA_ATTR_SONG_LENGTH){ // DONE
if (connection->packet_type != AVRCP_SINGLE_PACKET){
connection->packet_type = AVRCP_END_PACKET;
}
}
}
packet[pos_packet_type] = connection->packet_type;
// store attr value length
big_endian_store_16(packet, playing_info_buffer_len_position, pos - playing_info_buffer_len_position - 2);
return l2cap_send_prepared(cid, size);
}
static int avrcp_target_send_response(uint16_t cid, avrcp_connection_t * connection){
int pos = 0;
l2cap_reserve_packet_buffer();
@ -208,11 +250,14 @@ static int avrcp_target_send_response(uint16_t cid, avrcp_connection_t * connect
// transport header
// Transaction label | Packet_type | C/R | IPID (1 == invalid profile identifier)
packet[pos++] = (connection->transaction_label << 4) | (AVRCP_SINGLE_PACKET << 2) | (AVRCP_RESPONSE_FRAME << 1) | 0;
// TODO: check for fragmentation
connection->packet_type = AVRCP_SINGLE_PACKET;
packet[pos++] = (connection->transaction_label << 4) | (connection->packet_type << 2) | (AVRCP_RESPONSE_FRAME << 1) | 0;
// Profile IDentifier (PID)
packet[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL >> 8;
packet[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL & 0x00FF;
// command_type
packet[pos++] = connection->command_type;
// subunit_type | subunit ID
@ -234,7 +279,6 @@ static uint8_t avrcp_target_response_reject(avrcp_connection_t * connection, avr
connection->subunit_type = subunit_type;
connection->subunit_id = subunit_id;
connection->command_opcode = opcode;
// company id is 3 bytes long
int pos = connection->cmd_operands_length;
connection->cmd_operands[pos++] = pdu_id;
@ -244,12 +288,78 @@ static uint8_t avrcp_target_response_reject(avrcp_connection_t * connection, avr
pos += 2;
connection->cmd_operands[pos++] = status;
connection->cmd_operands_length = pos;
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
static uint8_t avrcp_target_response_not_implemented(avrcp_connection_t * connection, avrcp_subunit_type_t subunit_type, avrcp_subunit_id_t subunit_id, avrcp_command_opcode_t opcode, avrcp_pdu_id_t pdu_id, uint8_t event_id){
connection->command_type = AVRCP_CTYPE_RESPONSE_NOT_IMPLEMENTED;
connection->subunit_type = subunit_type;
connection->subunit_id = subunit_id;
connection->command_opcode = opcode;
// company id is 3 bytes long
int pos = connection->cmd_operands_length;
connection->cmd_operands[pos++] = pdu_id;
connection->cmd_operands[pos++] = 0;
// param length
big_endian_store_16(connection->cmd_operands, pos, 1);
pos += 2;
connection->cmd_operands[pos++] = event_id;
connection->cmd_operands_length = pos;
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
static uint8_t avrcp_target_response_vendor_dependent_interim(avrcp_connection_t * connection, avrcp_subunit_type_t subunit_type, avrcp_subunit_id_t subunit_id, avrcp_command_opcode_t opcode, avrcp_pdu_id_t pdu_id, uint8_t event_id, const uint8_t * value, uint16_t value_len){
connection->command_type = AVRCP_CTYPE_RESPONSE_INTERIM;
connection->subunit_type = subunit_type;
connection->subunit_id = subunit_id;
connection->command_opcode = opcode;
// company id is 3 bytes long
int pos = connection->cmd_operands_length;
connection->cmd_operands[pos++] = pdu_id;
connection->cmd_operands[pos++] = 0;
// param length
big_endian_store_16(connection->cmd_operands, pos, 1 + value_len);
pos += 2;
connection->cmd_operands[pos++] = event_id;
if (value && value_len > 0){
memcpy(connection->cmd_operands + pos, value, value_len);
pos += value_len;
}
connection->cmd_operands_length = pos;
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
// static uint8_t avrcp_target_response_vendor_dependent_changed(avrcp_connection_t * connection, avrcp_pdu_id_t pdu_id, uint8_t event_id){
// connection->command_opcode = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT;
// connection->command_type = AVRCP_CTYPE_RESPONSE_CHANGED_STABLE;
// connection->subunit_type = AVRCP_SUBUNIT_TYPE_PANEL;
// connection->subunit_id = AVRCP_SUBUNIT_ID;
// // company id is 3 bytes long
// int pos = connection->cmd_operands_length;
// connection->cmd_operands[pos++] = pdu_id;
// connection->cmd_operands[pos++] = 0;
// // param length
// big_endian_store_16(connection->cmd_operands, pos, 1);
// pos += 2;
// connection->cmd_operands[pos++] = event_id;
// connection->cmd_operands_length = pos;
// connection->state = AVCTP_W2_SEND_RESPONSE;
// avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
// return ERROR_CODE_SUCCESS;
// }
static uint8_t avrcp_target_pass_through_response(uint16_t avrcp_cid, avrcp_command_type_t cmd_type, avrcp_operation_id_t opid, uint8_t operands_length, uint8_t operand){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
@ -289,12 +399,28 @@ uint8_t avrcp_target_operation_not_implemented(uint16_t avrcp_cid, avrcp_operati
return avrcp_target_pass_through_response(avrcp_cid, AVRCP_CTYPE_RESPONSE_ACCEPTED, opid, operands_length, operand);
}
uint8_t avrcp_target_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_type, uint32_t company_id){
void avrcp_target_set_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_type, uint32_t company_id){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
log_error("avrcp_target_operation_reject: could not find a connection.");
return;
}
connection->unit_type = unit_type;
connection->company_id = company_id;
}
void avrcp_target_set_subunit_info(uint16_t avrcp_cid, avrcp_subunit_type_t subunit_type, const uint8_t * subunit_info_data, uint16_t subunit_info_data_size){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_target_operation_reject: could not find a connection.");
return;
}
connection->subunit_info_type = subunit_type;
connection->subunit_info_data = subunit_info_data;
connection->subunit_info_data_size = subunit_info_data_size;
}
static uint8_t avrcp_target_unit_info(avrcp_connection_t * connection){
if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED;
uint8_t unit = 0;
@ -305,33 +431,31 @@ uint8_t avrcp_target_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_typ
connection->cmd_operands_length = 5;
connection->cmd_operands[0] = 0x07;
connection->cmd_operands[1] = (unit_type << 4) | unit;
connection->cmd_operands[1] = (connection->unit_type << 4) | unit;
// company id is 3 bytes long
big_endian_store_32(connection->cmd_operands, 2, company_id);
big_endian_store_32(connection->cmd_operands, 2, connection->company_id);
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_subunit_info(uint16_t avrcp_cid, avrcp_subunit_type_t subunit_type, uint8_t offset, uint8_t * subunit_info_data){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
static uint8_t avrcp_target_subunit_info(avrcp_connection_t * connection, uint8_t offset){
if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED;
if (offset - 4 > connection->subunit_info_data_size) return AVRCP_STATUS_INVALID_PARAMETER;
connection->command_opcode = AVRCP_CMD_OPCODE_SUBUNIT_INFO;
connection->command_type = AVRCP_CTYPE_RESPONSE_IMPLEMENTED_STABLE;
connection->subunit_type = subunit_type; //vendor unique
connection->subunit_type = AVRCP_SUBUNIT_TYPE_UNIT; //vendor unique
connection->subunit_id = AVRCP_SUBUNIT_ID_IGNORE;
printf("avrcp_target_subunit_info subunit_type %d\n", connection->subunit_type);
uint8_t page = offset / 4;
uint8_t extension_code = 7;
connection->cmd_operands_length = 5;
connection->cmd_operands[0] = (page << 4) | extension_code;
memcpy(connection->cmd_operands+1, subunit_info_data + offset, 4);
memcpy(connection->cmd_operands+1, connection->subunit_info_data + offset, 4);
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
@ -383,7 +507,7 @@ uint8_t avrcp_target_supported_companies(uint16_t avrcp_cid, uint8_t capabilitie
return avrcp_target_capability(avrcp_cid, AVRCP_CAPABILITY_ID_COMPANY, capabilities_num, capabilities, size);
}
uint8_t avrcp_target_play_status(uint16_t avrcp_cid, uint32_t song_length_ms, uint32_t song_position_ms, avrcp_play_status_t play_status){
uint8_t avrcp_target_play_status(uint16_t avrcp_cid, uint32_t song_length_ms, uint32_t song_position_ms, avrcp_playback_status_t play_status){
avrcp_connection_t * connection = NULL;
uint8_t status = avrcp_prepare_vendor_dependent_response(avrcp_cid, &connection, AVRCP_PDU_ID_GET_PLAY_STATUS, 11);
if (status != ERROR_CODE_SUCCESS) return status;
@ -399,84 +523,114 @@ uint8_t avrcp_target_play_status(uint16_t avrcp_cid, uint32_t song_length_ms, ui
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_now_playing_info(uint16_t avrcp_cid){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp tartget: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
static uint8_t avrcp_target_now_playing_info(avrcp_connection_t * connection){
connection->now_playing_info_response = 1;
if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED;
connection->command_opcode = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT;
connection->command_type = AVRCP_CTYPE_RESPONSE_IMPLEMENTED_STABLE;
connection->subunit_type = AVRCP_SUBUNIT_TYPE_PANEL;
connection->subunit_id = AVRCP_SUBUNIT_ID;
connection->now_playing_info_response = 1;
connection->state = AVCTP_W2_SEND_RESPONSE;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
static uint8_t avrcp_target_store_media_attr(uint16_t avrcp_cid, avrcp_media_attribute_id_t attr_id, const char * value){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
static uint8_t avrcp_target_store_media_attr(avrcp_connection_t * connection, avrcp_media_attribute_id_t attr_id, const char * value){
int index = attr_id - 1;
if (!value) return AVRCP_STATUS_INVALID_PARAMETER;
connection->now_playing_info[index].value = (uint8_t*)value;
connection->now_playing_info[index].len = strlen(value);
printf("store %lu bytes, %s\n", strlen(value), value);
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_set_now_playing_title(uint16_t avrcp_cid, const char * title){
return avrcp_target_store_media_attr(avrcp_cid, AVRCP_MEDIA_ATTR_TITLE, title);
}
uint8_t avrcp_target_set_now_playing_artist(uint16_t avrcp_cid, const char * artist){
return avrcp_target_store_media_attr(avrcp_cid, AVRCP_MEDIA_ATTR_ARTIST, artist);
}
uint8_t avrcp_target_set_now_playing_album(uint16_t avrcp_cid, const char * album){
return avrcp_target_store_media_attr(avrcp_cid, AVRCP_MEDIA_ATTR_ALBUM, album);
}
uint8_t avrcp_target_set_now_playing_genre(uint16_t avrcp_cid, const char * genre){
return avrcp_target_store_media_attr(avrcp_cid, AVRCP_MEDIA_ATTR_GENRE, genre);
}
uint8_t avrcp_target_set_now_playing_song_length_ms(uint16_t avrcp_cid, const uint32_t song_length_ms){
uint8_t avrcp_target_set_playback_status(uint16_t avrcp_cid, avrcp_playback_status_t playback_status){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
connection->song_length_ms = song_length_ms;
if (connection->playback_status == playback_status) return ERROR_CODE_SUCCESS;
connection->playback_status = playback_status;
connection->playback_status_changed = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_set_now_playing_total_tracks(uint16_t avrcp_cid, const int total_tracks){
void avrcp_target_set_now_playing_info(uint16_t avrcp_cid, const avrcp_track_t * current_track, uint16_t total_tracks){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
printf("avrcp_target_now_playing_info 0\n");
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
printf("avrcp_unit_info: could not find a connection. cid 0x%02x\n", avrcp_cid);
return;
}
printf("avrcp_target_set_now_playing_info 1\n");
if (!current_track){
connection->track_selected = 0;
connection->playback_status = AVRCP_PLAYBACK_STATUS_ERROR;
return;
}
printf("avrcp_target_set_now_playing_info 2\n");
printf("store track_id %s\n", current_track->track_id );
memcpy(connection->track_id, current_track->track_id, 8);
connection->song_length_ms = current_track->song_length_ms;
connection->track_nr = current_track->track_nr;
connection->total_tracks = total_tracks;
return ERROR_CODE_SUCCESS;
avrcp_target_store_media_attr(connection, AVRCP_MEDIA_ATTR_TITLE, current_track->title);
avrcp_target_store_media_attr(connection, AVRCP_MEDIA_ATTR_ARTIST, current_track->artist);
avrcp_target_store_media_attr(connection, AVRCP_MEDIA_ATTR_ALBUM, current_track->album);
avrcp_target_store_media_attr(connection, AVRCP_MEDIA_ATTR_GENRE, current_track->genre);
connection->track_selected = 1;
if (connection->notifications_enabled & (1 << AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED)) {
connection->track_changed = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
}
return;
}
uint8_t avrcp_target_set_now_playing_track_nr(uint16_t avrcp_cid, const int track_nr){
uint8_t avrcp_target_playing_content_changed(uint16_t avrcp_cid){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
connection->track_nr = track_nr;
connection->playing_content_changed = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_battery_status_changed(uint16_t avrcp_cid, avrcp_battery_status_t battery_status){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
if (connection->battery_status == battery_status) return ERROR_CODE_SUCCESS;
connection->battery_status = battery_status;
connection->battery_status_changed = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_target_volume_changed(uint16_t avrcp_cid, uint8_t volume_percentage){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_target_context);
if (!connection){
log_error("avrcp_unit_info: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
if (connection->volume_percentage == volume_percentage) return ERROR_CODE_SUCCESS;
connection->volume_percentage = volume_percentage;
connection->volume_percentage_changed = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}
static uint8_t * avrcp_get_company_id(uint8_t *packet, uint16_t size){
UNUSED(size);
return packet + 6;
@ -487,6 +641,10 @@ static uint8_t * avrcp_get_pdu(uint8_t *packet, uint16_t size){
return packet + 9;
}
static uint8_t avrcp_is_receive_pass_through_cmd(uint8_t operation_id){
return operation_id & 0x80;
}
static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connection_t * connection, uint8_t *packet, uint16_t size){
UNUSED(connection);
UNUSED(packet);
@ -523,17 +681,46 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
switch (opcode){
case AVRCP_CMD_OPCODE_UNIT_INFO:
avrcp_target_emit_respond_query(avrcp_target_context.avrcp_callback, connection->avrcp_cid, AVRCP_SUBEVENT_UNIT_INFO_QUERY);
avrcp_target_unit_info(connection);
break;
case AVRCP_CMD_OPCODE_SUBUNIT_INFO:{
uint8_t offset = 4 * (packet[pos+2]>>4);
avrcp_target_emit_respond_subunit_info_query(avrcp_target_context.avrcp_callback, connection->avrcp_cid, offset);
avrcp_target_subunit_info(connection, offset);
break;
}
case AVRCP_CMD_OPCODE_PASS_THROUGH:
case AVRCP_CMD_OPCODE_PASS_THROUGH:{
log_info("AVRCP_OPERATION_ID 0x%02x, operands length %d, operand %d", packet[6], packet[7], packet[8]);
avrcp_target_emit_operation(avrcp_target_context.avrcp_callback, connection->avrcp_cid, packet[6], packet[7], packet[8]);
avrcp_operation_id_t operation_id = packet[6];
if (avrcp_is_receive_pass_through_cmd(operation_id)){
operation_id = packet[6] & 0x7F;
}
switch (operation_id){
case AVRCP_OPERATION_ID_PLAY:
case AVRCP_OPERATION_ID_PAUSE:
case AVRCP_OPERATION_ID_STOP:
avrcp_target_operation_accepted(connection->avrcp_cid, packet[6], packet[7], packet[8]);
avrcp_target_emit_operation(avrcp_target_context.avrcp_callback, connection->avrcp_cid, packet[6], packet[7], packet[8]);
break;
case AVRCP_OPERATION_ID_REWIND:
case AVRCP_OPERATION_ID_FAST_FORWARD:
case AVRCP_OPERATION_ID_FORWARD:
case AVRCP_OPERATION_ID_BACKWARD:
case AVRCP_OPERATION_ID_SKIP:
case AVRCP_OPERATION_ID_VOLUME_UP:
case AVRCP_OPERATION_ID_VOLUME_DOWN:
case AVRCP_OPERATION_ID_MUTE:
case AVRCP_OPERATION_ID_UNDEFINED:
avrcp_target_operation_not_implemented(connection->avrcp_cid, packet[6], packet[7], packet[8]);
return;
default:
avrcp_target_operation_not_implemented(connection->avrcp_cid, packet[6], packet[7], packet[8]);
break;
}
break;
}
case AVRCP_CMD_OPCODE_VENDOR_DEPENDENT:
pdu_id = pdu[0];
// 1 - reserved
@ -559,6 +746,14 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
case AVRCP_PDU_ID_GET_PLAY_STATUS:
avrcp_target_emit_respond_vendor_dependent_query(avrcp_target_context.avrcp_callback, connection->avrcp_cid, AVRCP_SUBEVENT_PLAY_STATUS_QUERY);
break;
case AVRCP_PDU_ID_REQUEST_CONTINUING_RESPONSE:
if (pdu[4] != AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES){
avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_COMMAND);
return;
}
connection->now_playing_info_response = 1;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
case AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES:{
uint8_t play_identifier[8];
memset(play_identifier, 0, 8);
@ -566,11 +761,70 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_PARAMETER);
return;
}
avrcp_target_emit_respond_vendor_dependent_query(avrcp_target_context.avrcp_callback, connection->avrcp_cid, AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY);
pos += 8;
uint8_t attribute_count = pdu[pos++];
connection->next_attr_id = AVRCP_MEDIA_ATTR_NONE;
if (!attribute_count){
connection->now_playing_info_attr_bitmap = 0xFE;
} else {
int i;
connection->now_playing_info_attr_bitmap = 0;
for (i=0; i < attribute_count; i++){
uint16_t attr_id = big_endian_read_16(pdu, pos);
pos += 2;
connection->now_playing_info_attr_bitmap |= (1 << attr_id);
}
}
log_info("now_playing_info_attr_bitmap 0x%02x", connection->now_playing_info_attr_bitmap);
avrcp_target_now_playing_info(connection);
break;
}
case AVRCP_PDU_ID_REGISTER_NOTIFICATION:{
// 0 - pdu id
// 1 - reserved
// 2-3 param length
avrcp_notification_event_id_t event_id = (avrcp_notification_event_id_t) pdu[4];
uint16_t event_mask = (1 << event_id);
switch (event_id){
case AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED:
connection->notifications_enabled |= event_mask;
if (connection->track_selected){
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, AVRCP_NOTIFICATION_TRACK_SELECTED, 8);
} else {
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, AVRCP_NOTIFICATION_TRACK_NOT_SELECTED, 8);
}
break;
case AVRCP_NOTIFICATION_EVENT_PLAYBACK_STATUS_CHANGED:
connection->notifications_enabled |= event_mask;
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, (const uint8_t *)&connection->playback_status, 1);
break;
case AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED:
connection->notifications_enabled |= event_mask;
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, NULL, 0);
break;
case AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED:
connection->notifications_enabled |= event_mask;
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, (const uint8_t *)&connection->volume_percentage, 1);
break;
case AVRCP_NOTIFICATION_EVENT_BATT_STATUS_CHANGED:
connection->notifications_enabled |= event_mask;
avrcp_target_response_vendor_dependent_interim(connection, subunit_type, subunit_id, opcode, pdu_id, event_id, (const uint8_t *)&connection->battery_status, 1);
break;
case AVRCP_NOTIFICATION_EVENT_AVAILABLE_PLAYERS_CHANGED:
case AVRCP_NOTIFICATION_EVENT_PLAYER_APPLICATION_SETTING_CHANGED:
case AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED:
case AVRCP_NOTIFICATION_EVENT_UIDS_CHANGED:
avrcp_target_response_not_implemented(connection, subunit_type, subunit_id, opcode, pdu_id, event_id);
return;
default:
avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_PARAMETER);
return;
}
break;
}
default:
printf("unhandled pdu id 0x%02x\n", pdu_id);
printf("AVRCP target: unhandled pdu id 0x%02x\n", pdu_id);
avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_COMMAND);
break;
}
@ -581,6 +835,53 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
}
}
static int avrcp_target_send_notification(uint16_t cid, avrcp_connection_t * connection, uint8_t notification_id, uint8_t * value, uint16_t value_len){
if (!connection){
log_error("avrcp tartget: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
connection->command_opcode = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT;
connection->command_type = AVRCP_CTYPE_RESPONSE_CHANGED_STABLE;
connection->subunit_type = AVRCP_SUBUNIT_TYPE_PANEL;
connection->subunit_id = AVRCP_SUBUNIT_ID;
uint16_t pos = 0;
l2cap_reserve_packet_buffer();
uint8_t * packet = l2cap_get_outgoing_buffer();
uint16_t size = l2cap_get_remote_mtu_for_local_cid(connection->l2cap_signaling_cid);
connection->packet_type = AVRCP_SINGLE_PACKET;
packet[pos++] = (connection->transaction_label << 4) | (connection->packet_type << 2) | (AVRCP_RESPONSE_FRAME << 1) | 0;
// Profile IDentifier (PID)
packet[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL >> 8;
packet[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL & 0x00FF;
// command_type
packet[pos++] = connection->command_type;
// subunit_type | subunit ID
packet[pos++] = (connection->subunit_type << 3) | connection->subunit_id;
// opcode
packet[pos++] = (uint8_t)connection->command_opcode;
// company id is 3 bytes long
big_endian_store_24(packet, pos, BT_SIG_COMPANY_ID);
pos += 3;
packet[pos++] = AVRCP_PDU_ID_REGISTER_NOTIFICATION;
packet[pos++] = 0;
uint16_t remainig_outgoing_buffer_size = size > (value_len + 2 + 1) ? size - (value_len + 2 + 1): 0;
uint16_t caped_value_len = value_len > remainig_outgoing_buffer_size ? remainig_outgoing_buffer_size : value_len;
big_endian_store_16(packet, pos, caped_value_len);
pos += 2;
packet[pos++] = notification_id;
memcpy(packet+pos, value, caped_value_len);
pos += caped_value_len;
connection->wait_to_send = 0;
return l2cap_send_prepared(cid, pos);
}
static void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
avrcp_connection_t * connection;
switch (packet_type) {
@ -591,24 +892,67 @@ static void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channe
break;
case HCI_EVENT_PACKET:
switch (hci_event_packet_get_type(packet)){
case L2CAP_EVENT_CAN_SEND_NOW:
case L2CAP_EVENT_CAN_SEND_NOW:{
connection = get_avrcp_connection_for_l2cap_signaling_cid(channel, &avrcp_target_context);
if (!connection) break;
if (connection->now_playing_info_response){
connection->now_playing_info_response = 0;
avrcp_target_send_now_playing_info(connection->l2cap_signaling_cid, connection);
break;
}
break;
if (connection->track_changed){
connection->track_changed = 0;
avrcp_target_send_notification(connection->l2cap_signaling_cid, connection, AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED, connection->track_id, 8);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
}
if (connection->playback_status_changed){
connection->playback_status_changed = 0;
avrcp_target_send_notification(connection->l2cap_signaling_cid, connection, AVRCP_NOTIFICATION_EVENT_PLAYBACK_STATUS_CHANGED, &connection->playback_status_changed, 1);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
}
if (connection->playing_content_changed){
connection->playing_content_changed = 0;
avrcp_target_send_notification(connection->l2cap_signaling_cid, connection, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED, NULL, 0);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
}
if (connection->battery_status_changed){
connection->battery_status_changed = 0;
avrcp_target_send_notification(connection->l2cap_signaling_cid, connection, AVRCP_NOTIFICATION_EVENT_BATT_STATUS_CHANGED, (uint8_t *)&connection->battery_status, 1);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
}
if (connection->volume_percentage_changed){
connection->volume_percentage_changed = 0;
avrcp_target_send_notification(connection->l2cap_signaling_cid, connection, AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED, &connection->volume_percentage, 1);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
break;
}
switch (connection->state){
case AVCTP_W2_SEND_RESPONSE:
connection->state = AVCTP_CONNECTION_OPENED;
if (connection->now_playing_info_response){
printf("now_playing_info_response \n");
connection->now_playing_info_response = 0;
avrcp_target_send_now_playing_info(connection->l2cap_signaling_cid, connection);
break;
}
// if (connection->now_playing_info_response){
// connection->now_playing_info_response = 0;
// avrcp_target_send_now_playing_info(connection->l2cap_signaling_cid, connection);
// break;
// }
avrcp_target_send_response(connection->l2cap_signaling_cid, connection);
break;
default:
return;
}
break;
}
default:
avrcp_packet_handler(packet_type, channel, packet, size, &avrcp_target_context);
break;

View File

@ -74,22 +74,20 @@ uint8_t avrcp_target_connect(bd_addr_t bd_addr, uint16_t * avrcp_cid);
uint8_t avrcp_target_disconnect(uint16_t avrcp_cid);
uint8_t avrcp_target_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_type, uint32_t company_id);
uint8_t avrcp_target_subunit_info(uint16_t avrcp_cid, avrcp_subunit_type_t subunit_type, uint8_t offset, uint8_t * subunit_info_data);
uint8_t avrcp_target_supported_companies(uint16_t avrcp_cid, uint8_t capabilities_length, uint8_t * capabilities, uint8_t size);
uint8_t avrcp_target_supported_events(uint16_t avrcp_cid, uint8_t capabilities_length, uint8_t * capabilities, uint8_t size);
uint8_t avrcp_target_play_status(uint16_t avrcp_cid, uint32_t song_length_ms, uint32_t song_position_ms, avrcp_play_status_t status);
uint8_t avrcp_target_play_status(uint16_t avrcp_cid, uint32_t song_length_ms, uint32_t song_position_ms, avrcp_playback_status_t status);
void avrcp_target_set_now_playing_info(uint16_t avrcp_cid, const avrcp_track_t * current_track, uint16_t total_tracks);
uint8_t avrcp_target_set_playback_status(uint16_t avrcp_cid, avrcp_playback_status_t playback_status);
void avrcp_target_set_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_type, uint32_t company_id);
void avrcp_target_set_subunit_info(uint16_t avrcp_cid, avrcp_subunit_type_t subunit_type, const uint8_t * subunit_info_data, uint16_t subunit_info_data_size);
uint8_t avrcp_target_playing_content_changed(uint16_t avrcp_cid);
uint8_t avrcp_target_battery_status_changed(uint16_t avrcp_cid, avrcp_battery_status_t battery_status);
uint8_t avrcp_target_volume_changed(uint16_t avrcp_cid, uint8_t volume_percentage);
uint8_t avrcp_target_set_now_playing_title(uint16_t avrcp_cid, const char * title);
uint8_t avrcp_target_set_now_playing_artist(uint16_t avrcp_cid, const char * artist);
uint8_t avrcp_target_set_now_playing_album(uint16_t avrcp_cid, const char * album);
uint8_t avrcp_target_set_now_playing_genre(uint16_t avrcp_cid, const char * genre);
uint8_t avrcp_target_set_now_playing_song_length_ms(uint16_t avrcp_cid, const uint32_t song_length_ms);
uint8_t avrcp_target_set_now_playing_total_tracks(uint16_t avrcp_cid, const int total_tracks);
uint8_t avrcp_target_set_now_playing_track_nr(uint16_t avrcp_cid, const int track_nr);
uint8_t avrcp_target_now_playing_info(uint16_t avrcp_cid);
uint8_t avrcp_target_operation_rejected(uint16_t avrcp_cid, avrcp_operation_id_t opid, uint8_t operands_length, uint8_t operand);
uint8_t avrcp_target_operation_accepted(uint16_t avrcp_cid, avrcp_operation_id_t opid, uint8_t operands_length, uint8_t operand);

View File

@ -264,20 +264,6 @@ static int hfp_ag_send_report_extended_audio_gateway_error(uint16_t cid, uint8_t
return send_str_over_rfcomm(cid, buffer);
}
// fast & small implementation for fixed int size
static int string_len_for_uint32(uint32_t i){
if (i < 10) return 1;
if (i < 100) return 2;
if (i < 1000) return 3;
if (i < 10000) return 4;
if (i < 100000) return 5;
if (i < 1000000) return 6;
if (i < 10000000) return 7;
if (i < 100000000) return 8;
if (i < 1000000000) return 9;
return 10;
}
// get size for indicator string
static int hfp_ag_indicators_string_size(hfp_connection_t * hfp_connection, int i){
// template: ("$NAME",($MIN,$MAX))