avrcp_controller: add avrcp_controller_send_custom_command that fragments outgoing commands

This commit is contained in:
Matthias Ringwald 2018-04-26 18:25:06 +02:00
parent 86c0eebd9a
commit c4b5c3d5ad
4 changed files with 114 additions and 19 deletions

View File

@ -272,7 +272,8 @@ typedef enum {
AVCTP_W2_SEND_COMMAND,
AVCTP_W2_SEND_RESPONSE,
AVCTP_W2_RECEIVE_PRESS_RESPONSE,
AVCTP_W2_RECEIVE_RESPONSE
AVCTP_W2_RECEIVE_RESPONSE,
AVCTP_W2_SEND_FRAGMENTED_COMMAND,
} avctp_connection_state_t;
typedef enum {
@ -407,6 +408,11 @@ typedef struct {
uint8_t cmd_operands[20];
uint8_t cmd_operands_length;
// long/fragmented commands
const uint8_t * cmd_operands_fragmented_buffer;
uint16_t cmd_operands_fragmented_pos;
uint16_t cmd_operands_fragmented_len;
btstack_timer_source_t press_and_hold_cmd_timer;
uint8_t continuous_fast_forward_cmd;
uint16_t notifications_enabled;

View File

@ -156,26 +156,57 @@ static uint8_t request_continuous_pass_through_press_control_cmd(uint16_t avrcp_
return request_pass_through_press_control_cmd(avrcp_cid, opid, playback_speed, 1, &avrcp_controller_context);
}
static int avrcp_send_cmd(avrcp_connection_t * connection){
uint8_t command[30];
#define AVRCP_CMD_BUFFER_SIZE 30
static uint16_t avrcp_get_max_payload_size_for_packet_type(avrcp_packet_type_t packet_type){
switch (packet_type){
case AVRCP_SINGLE_PACKET:
return AVRCP_CMD_BUFFER_SIZE - 3;
case AVRCP_START_PACKET:
return AVRCP_CMD_BUFFER_SIZE - 4;
case AVRCP_CONTINUE_PACKET:
case AVRCP_END_PACKET:
return AVRCP_CMD_BUFFER_SIZE - 1;
}
}
static int avrcp_send_cmd(avrcp_connection_t * connection, avrcp_packet_type_t packet_type){
uint8_t command[AVRCP_CMD_BUFFER_SIZE];
int pos = 0;
uint16_t max_bytes = sizeof(command) - 1;
// transport header
// Transaction label | Packet_type | C/R | IPID (1 == invalid profile identifier)
command[pos++] = (connection->transaction_label << 4) | (AVRCP_SINGLE_PACKET << 2) | (AVRCP_COMMAND_FRAME << 1) | 0;
// Profile IDentifier (PID)
command[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL >> 8;
command[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL & 0x00FF;
command[pos++] = (connection->transaction_label << 4) | (packet_type << 2) | (AVRCP_COMMAND_FRAME << 1) | 0;
// command_type
command[pos++] = connection->command_type;
// subunit_type | subunit ID
command[pos++] = (connection->subunit_type << 3) | connection->subunit_id;
// opcode
command[pos++] = (uint8_t)connection->command_opcode;
// operands
memcpy(command+pos, connection->cmd_operands, connection->cmd_operands_length);
pos += connection->cmd_operands_length;
if (packet_type == AVRCP_START_PACKET){
// num packets: (3 bytes overhead (PID, num packets) + command) / (MTU - transport header)
command[pos++] = ((connection->cmd_operands_fragmented_len + 3 - 1) / (AVRCP_CMD_BUFFER_SIZE - 1)) + 1;
max_bytes -= 3;
}
if (packet_type == AVRCP_SINGLE_PACKET || packet_type == AVRCP_START_PACKET){
// Profile IDentifier (PID)
command[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL >> 8;
command[pos++] = BLUETOOTH_SERVICE_CLASS_AV_REMOTE_CONTROL & 0x00FF;
// command_type
command[pos++] = connection->command_type;
// subunit_type | subunit ID
command[pos++] = (connection->subunit_type << 3) | connection->subunit_id;
// opcode
command[pos++] = (uint8_t)connection->command_opcode;
}
if (packet_type == AVRCP_SINGLE_PACKET){
// operands
memcpy(command+pos, connection->cmd_operands, connection->cmd_operands_length);
pos += connection->cmd_operands_length;
} else {
uint16_t bytes_to_copy = btstack_min(connection->cmd_operands_fragmented_len-connection->cmd_operands_fragmented_pos, max_bytes);
memcpy(command+pos, &connection->cmd_operands_fragmented_buffer[connection->cmd_operands_fragmented_pos], bytes_to_copy);
pos += bytes_to_copy;
connection->cmd_operands_fragmented_pos += bytes_to_copy;
}
return l2cap_send(connection->l2cap_signaling_cid, command, pos);
}
@ -805,12 +836,12 @@ static void avrcp_controller_handle_can_send_now(avrcp_connection_t * connection
switch (connection->state){
case AVCTP_W2_SEND_PRESS_COMMAND:
connection->state = AVCTP_W2_RECEIVE_PRESS_RESPONSE;
avrcp_send_cmd(connection);
avrcp_send_cmd(connection, AVRCP_SINGLE_PACKET);
break;
case AVCTP_W2_SEND_COMMAND:
case AVCTP_W2_SEND_RELEASE_COMMAND:
connection->state = AVCTP_W2_RECEIVE_RESPONSE;
avrcp_send_cmd(connection);
avrcp_send_cmd(connection, AVRCP_SINGLE_PACKET);
break;
case AVCTP_CONNECTION_OPENED:
if (connection->notifications_to_register != 0){
@ -819,12 +850,25 @@ static void avrcp_controller_handle_can_send_now(avrcp_connection_t * connection
connection->notifications_to_register &= ~ (1 << i);
avrcp_prepare_notification(connection, (avrcp_notification_event_id_t) i);
connection->state = AVCTP_W2_RECEIVE_RESPONSE;
avrcp_send_cmd(connection);
avrcp_send_cmd(connection, AVRCP_SINGLE_PACKET);
return;
}
}
}
return;
case AVCTP_W2_SEND_FRAGMENTED_COMMAND:
if (connection->cmd_operands_fragmented_pos == 0){
avrcp_send_cmd(connection, AVRCP_START_PACKET);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
} else {
if (connection->cmd_operands_fragmented_len - connection->cmd_operands_fragmented_pos > avrcp_get_max_payload_size_for_packet_type(AVRCP_CONTINUE_PACKET)){
avrcp_send_cmd(connection, AVRCP_CONTINUE_PACKET);
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
} else {
connection->state = AVCTP_W2_RECEIVE_RESPONSE;
avrcp_send_cmd(connection, AVRCP_END_PACKET);
}
}
default:
return;
}
@ -1335,3 +1379,26 @@ uint8_t avrcp_controller_set_max_num_fragments(uint16_t avrcp_cid, uint8_t max_n
connection->max_num_fragments = max_num_fragments;
return ERROR_CODE_SUCCESS;
}
uint8_t avrcp_controller_send_custom_command(uint16_t avrcp_cid, avrcp_command_type_t command_type, avrcp_subunit_type_t subunit_type, avrcp_subunit_id_t subunit_id, avrcp_command_opcode_t command_opcode, const uint8_t * command_buffer, uint16_t command_len){
avrcp_connection_t * connection = get_avrcp_connection_for_avrcp_cid(avrcp_cid, &avrcp_controller_context);
if (!connection){
log_error("avrcp_controller_play_item: could not find a connection.");
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
}
if (connection->state != AVCTP_CONNECTION_OPENED) return ERROR_CODE_COMMAND_DISALLOWED;
connection->state = AVCTP_W2_SEND_FRAGMENTED_COMMAND;
connection->transaction_label++;
connection->command_opcode = command_opcode;
connection->command_type = command_type;
connection->subunit_type = subunit_type;
connection->subunit_id = subunit_id;
connection->cmd_operands_fragmented_buffer = command_buffer;
connection->cmd_operands_fragmented_pos = 0;
connection->cmd_operands_fragmented_len = command_len;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
return ERROR_CODE_SUCCESS;
}

View File

@ -322,6 +322,18 @@ uint8_t avrcp_controller_set_addressed_player(uint16_t avrcp_cid, uint16_t addre
/* API_END */
/**
* @brief Send custom command
* @param avrcp_cid
* @param command_type
* @param subunit_type
* @param subunit ID
* @param command_opcode
* @param command_buffer
* @param command_len
*/
uint8_t avrcp_controller_send_custom_command(uint16_t avrcp_cid, avrcp_command_type_t command_type, avrcp_subunit_type_t subunit_type, avrcp_subunit_id_t subunit_id, avrcp_command_opcode_t command_opcode, const uint8_t * command_buffer, uint16_t command_len);
// Used by AVRCP controller and AVRCP browsing controller
extern avrcp_context_t avrcp_controller_context;

View File

@ -67,6 +67,9 @@ static const char * device_addr_string = "00:1B:DC:07:32:EF";
#endif
static bd_addr_t device_addr;
static const char * fragmented_message = "Hello PTS, this is the very special AVCRCP message to test the AVCTP framgentation";
typedef struct {
uint16_t charset;
uint8_t depth;
@ -578,6 +581,8 @@ static void show_usage(void){
printf("p5 - Add to now playing: first media item from virtual file system\n");
printf("p6 - Add to now playing: first media item from search folder\n");
printf("pf - Send fragmented (long) command\n");
printf("Ctrl-c - exit\n");
printf("---\n");
}
@ -1005,6 +1010,11 @@ static void stdin_process(char * cmd, int size){
status = avrcp_controller_add_item_from_scope_to_now_playing_list(avrcp_cid, media_element_items[0].uid, media_element_item_index, AVRCP_BROWSING_SEARCH);
break;
case 'f':
printf("Send fragmented command\n");
status = avrcp_controller_send_custom_command(avrcp_cid, AVRCP_CTYPE_CONTROL, AVRCP_SUBUNIT_TYPE_PANEL, AVRCP_SUBUNIT_ID, AVRCP_CMD_OPCODE_VENDOR_DEPENDENT, (const uint8_t*) fragmented_message, strlen(fragmented_message));
break;
default:
break;
}