mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-03-12 01:14:17 +00:00
avrcp target: notifications and fragmentation
This commit is contained in:
parent
63dd1c760e
commit
d1207cd896
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user