avrcp: volume control

This commit is contained in:
Milanka Ringwald 2017-02-08 12:28:41 +01:00 committed by Matthias Ringwald
parent e57a25456e
commit c6906b0bac
3 changed files with 195 additions and 23 deletions

View File

@ -121,6 +121,30 @@ static const char * default_avrcp_target_service_provider_name = "BTstack AVRCP
static btstack_linked_list_t avrcp_connections; static btstack_linked_list_t avrcp_connections;
static btstack_packet_handler_t avrcp_callback; static btstack_packet_handler_t avrcp_callback;
static const char * avrcp_subunit_type_name[] = {
"MONITOR", "AUDIO", "PRINTER", "DISC", "TAPE_RECORDER_PLAYER", "TUNER",
"CA", "CAMERA", "RESERVED", "PANEL", "BULLETIN_BOARD", "CAMERA_STORAGE",
"VENDOR_UNIQUE", "RESERVED_FOR_ALL_SUBUNIT_TYPES",
"EXTENDED_TO_NEXT_BYTE", "UNIT", "ERROR"
};
static const char * subunit2str(uint16_t index){
if (index <= 11) return avrcp_subunit_type_name[index];
if (index >= 0x1C && index <= 0x1F) return avrcp_subunit_type_name[index - 0x10];
return avrcp_subunit_type_name[16];
}
static const char * avrcp_event_name[] = {
"ERROR", "PLAYBACK_STATUS_CHANGED",
"TRACK_CHANGED", "TRACK_REACHED_END", "TRACK_REACHED_START",
"PLAYBACK_POS_CHANGED", "BATT_STATUS_CHANGED", "SYSTEM_STATUS_CHANGED",
"PLAYER_APPLICATION_SETTING_CHANGED", "NOW_PLAYING_CONTENT_CHANGED",
"AVAILABLE_PLAYERS_CHANGED", "ADDRESSED_PLAYER_CHANGED", "UIDS_CHANGED", "VOLUME_CHANGED"
};
static const char * event2str(uint16_t index){
if (index <= 0x0d) return avrcp_event_name[index];
return avrcp_event_name[0];
}
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
static void avrcp_create_sdp_record(uint8_t controller, uint8_t * service, uint32_t service_record_handle, uint8_t browsing, uint16_t supported_features, const char * service_name, const char * service_provider_name){ static void avrcp_create_sdp_record(uint8_t controller, uint8_t * service, uint32_t service_record_handle, uint8_t browsing, uint16_t supported_features, const char * service_name, const char * service_provider_name){
@ -432,20 +456,18 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
uint8_t operands[20]; uint8_t operands[20];
uint8_t opcode; uint8_t opcode;
int pos = 0; int pos = 0;
uint8_t transport_header = packet[0];
uint8_t transport_header = packet[pos++];
uint8_t transaction_label = transport_header >> 4; uint8_t transaction_label = transport_header >> 4;
uint8_t packet_type = (transport_header & 0x0F) >> 2; uint8_t packet_type = (transport_header & 0x0F) >> 2;
uint8_t frame_type = (transport_header & 0x03) >> 1; uint8_t frame_type = (transport_header & 0x03) >> 1;
uint8_t ipid = transport_header & 0x01; uint8_t ipid = transport_header & 0x01;
uint8_t byte_value = packet[pos++]; uint8_t byte_value = packet[2];
uint16_t pid = (byte_value << 8) | packet[pos++]; uint16_t pid = (byte_value << 8) | packet[2];
pos = 3;
printf("L2CAP DATA, response: "); log_info(" Transport header 0x%02x (transaction_label %d, packet_type %d, frame_type %d, ipid %d), pid 0x%4x",
printf_hexdump(packet, size);
printf(" Transport header 0x%02x (transaction_label %d, packet_type %d, frame_type %d, ipid %d), pid 0x%4x\n",
transport_header, transaction_label, packet_type, frame_type, ipid, pid); transport_header, transaction_label, packet_type, frame_type, ipid, pid);
switch (connection->cmd_to_send){ switch (connection->cmd_to_send){
case AVRCP_CMD_OPCODE_UNIT_INFO:{ case AVRCP_CMD_OPCODE_UNIT_INFO:{
ctype = packet[pos++]; ctype = packet[pos++];
@ -459,7 +481,8 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
uint8_t unit_type = operands[1] >> 3; uint8_t unit_type = operands[1] >> 3;
uint8_t unit = operands[1] & 0x07; uint8_t unit = operands[1] & 0x07;
uint32_t company_id = operands[2] << 16 | operands[3] << 8 | operands[4]; uint32_t company_id = operands[2] << 16 | operands[3] << 8 | operands[4];
printf(" UNIT INFO response: ctype 0x%02x (0C), subunit_type 0x%02x (1F), subunit_id 0x%02x (07), opcode 0x%02x (30), unit_type 0x%02x, unit %d, company_id 0x%06x\n", printf(" UNIT INFO response: subunit type %s\n", subunit2str(subunit_type));
log_info(" UNIT INFO response: ctype 0x%02x (0C), subunit_type 0x%02x (1F), subunit_id 0x%02x (07), opcode 0x%02x (30), unit_type 0x%02x, unit %d, company_id 0x%06x",
ctype, subunit_type, subunit_id, opcode, unit_type, unit, company_id ); ctype, subunit_type, subunit_id, opcode, unit_type, unit, company_id );
break; break;
} }
@ -488,6 +511,33 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec
printf(" VENDOR DEPENDENT response: pdu id 0x%02x, param_length %d\n", pdu_id, param_length); printf(" VENDOR DEPENDENT response: pdu id 0x%02x, param_length %d\n", pdu_id, param_length);
switch (pdu_id){ switch (pdu_id){
case AVRCP_PDU_ID_GET_CAPABILITIES:{
printf_hexdump(packet+pos,size-pos);
avrcp_capability_id_t capability_id = packet[pos++];
uint8_t capability_count = packet[pos++];
printf(" capability id %02x, count %02x\n", capability_id, capability_count);
printf_hexdump(packet+pos,size-pos);
int i;
switch (capability_id){
case AVRCP_CAPABILITY_ID_COMPANY:
printf("Supported companies %d: \n", capability_count);
for (i = 0; i < capability_count; i++){
uint32_t company_id = big_endian_read_24(packet, pos);
pos += 3;
printf(" 0x%06x, \n", company_id);
}
printf("\n");
break;
case AVRCP_CAPABILITY_ID_EVENT:
printf("Supported events %d: \n", capability_count);
for (i = 0; i < capability_count; i++){
uint8_t event_id = packet[pos++];
printf(" 0x%02x %s\n", event_id, event2str(event_id));
}
break;
}
break;
}
case AVRCP_PDU_ID_GET_PLAY_STATUS:{ case AVRCP_PDU_ID_GET_PLAY_STATUS:{
uint32_t song_length = big_endian_read_32(packet, pos); uint32_t song_length = big_endian_read_32(packet, pos);
pos += 4; pos += 4;
@ -707,7 +757,7 @@ void avrcp_unit_info(uint16_t con_handle){
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid); avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
} }
void avrcp_get_capabilities(uint16_t con_handle){ static void avrcp_get_capabilities(uint16_t con_handle, uint8_t capability_id){
avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle); avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle);
if (!connection){ if (!connection){
log_error("avrcp_get_capabilities: coud not find a connection."); log_error("avrcp_get_capabilities: coud not find a connection.");
@ -724,12 +774,20 @@ void avrcp_get_capabilities(uint16_t con_handle){
big_endian_store_24(connection->cmd_operands, 0, BT_SIG_COMPANY_ID); big_endian_store_24(connection->cmd_operands, 0, BT_SIG_COMPANY_ID);
connection->cmd_operands[3] = AVRCP_PDU_ID_GET_CAPABILITIES; // PDU ID connection->cmd_operands[3] = AVRCP_PDU_ID_GET_CAPABILITIES; // PDU ID
connection->cmd_operands[4] = 0; connection->cmd_operands[4] = 0;
big_endian_store_16(connection->cmd_operands, 5, 1); big_endian_store_16(connection->cmd_operands, 5, 1); // parameter length
connection->cmd_operands[7] = 0x02; connection->cmd_operands[7] = capability_id; // capability ID
connection->cmd_operands_lenght = 8; connection->cmd_operands_lenght = 8;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid); avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
} }
void avrcp_get_supported_company_ids(uint16_t con_handle){
avrcp_get_capabilities(con_handle, AVRCP_CAPABILITY_ID_COMPANY);
}
void avrcp_get_supported_events(uint16_t con_handle){
avrcp_get_capabilities(con_handle, AVRCP_CAPABILITY_ID_EVENT);
}
void avrcp_play(uint16_t con_handle){ void avrcp_play(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_PLAY, 0); request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_PLAY, 0);
@ -755,6 +813,22 @@ void avrcp_start_rewind(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_REWIND, 0); request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_REWIND, 0);
} }
void avrcp_volume_up(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_VOLUME_UP, 0);
}
void avrcp_start_volume_down(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_VOLUME_DOWN, 0);
}
void avrcp_start_mute(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_MUTE, 0);
}
void avrcp_start_skip(uint16_t con_handle){
request_pass_through_press_control_cmd(con_handle, AVRCP_OPERATION_ID_SKIP, 0);
}
void avrcp_stop_rewind(uint16_t con_handle){ void avrcp_stop_rewind(uint16_t con_handle){
avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle); avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle);
if (!connection){ if (!connection){
@ -828,6 +902,7 @@ void avrcp_register_notification(uint16_t con_handle, avrcp_notification_event_i
connection->cmd_operands_lenght = pos; connection->cmd_operands_lenght = pos;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid); avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
// AVRCP_SPEC_V14.pdf 166 // AVRCP_SPEC_V14.pdf 166
// answer page 61
} }
void avrcp_get_now_playing_info(uint16_t con_handle){ void avrcp_get_now_playing_info(uint16_t con_handle){
@ -859,11 +934,42 @@ void avrcp_get_now_playing_info(uint16_t con_handle){
pos += 8; pos += 8;
connection->cmd_operands[pos++] = 0; // attribute count, if 0 get all attributes connection->cmd_operands[pos++] = 0; // attribute count, if 0 get all attributes
// every attribute is 4 bytes long
// big_endian_store_32(connection->cmd_operands, pos, TitleOfMedia); // 0x1
// pos += 4;
connection->cmd_operands_lenght = pos; connection->cmd_operands_lenght = pos;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid); avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
} }
void avrcp_set_absolute_volume(uint16_t con_handle, uint8_t volume){
avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle);
if (!connection){
log_error("avrcp_get_capabilities: coud not find a connection.");
return;
}
if (connection->state != AVCTP_CONNECTION_OPENED) return;
connection->state = AVCTP_W2_SEND_COMMAND;
connection->state = AVCTP_W2_SEND_COMMAND;
connection->transaction_label++;
connection->cmd_to_send = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT;
connection->command_type = AVRCP_CTYPE_CONTROL;
connection->subunit_type = AVRCP_SUBUNIT_TYPE_PANEL;
connection->subunit_id = 0;
int pos = 0;
big_endian_store_24(connection->cmd_operands, pos, BT_SIG_COMPANY_ID);
pos += 3;
connection->cmd_operands[pos++] = AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME; // PDU ID
connection->cmd_operands[pos++] = 0;
// Parameter Length
big_endian_store_16(connection->cmd_operands, pos, 1);
pos += 2;
connection->cmd_operands[pos++] = volume;
connection->cmd_operands_lenght = pos;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
}

View File

@ -54,6 +54,11 @@ extern "C" {
#define BT_SIG_COMPANY_ID 0x001958 #define BT_SIG_COMPANY_ID 0x001958
/* API_START */ /* API_START */
typedef enum {
AVRCP_CAPABILITY_ID_COMPANY = 0x02,
AVRCP_CAPABILITY_ID_EVENT = 0x03
} avrcp_capability_id_t;
typedef enum { typedef enum {
AVRCP_MEDIA_ATTR_TITLE = 1, AVRCP_MEDIA_ATTR_TITLE = 1,
AVRCP_MEDIA_ATTR_ARTIST, AVRCP_MEDIA_ATTR_ARTIST,
@ -67,7 +72,8 @@ typedef enum {
AVRCP_PDU_ID_GET_CAPABILITIES = 0x10, AVRCP_PDU_ID_GET_CAPABILITIES = 0x10,
AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES = 0x20, AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES = 0x20,
AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30, AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30,
AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31 AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31,
AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME = 0x50
} avrcp_pdu_id_t; } avrcp_pdu_id_t;
typedef enum { typedef enum {
@ -144,6 +150,11 @@ typedef enum {
} avrcp_command_opcode_t; } avrcp_command_opcode_t;
typedef enum { typedef enum {
AVRCP_OPERATION_ID_SKIP = 0x3C,
AVRCP_OPERATION_ID_VOLUME_UP = 0x41,
AVRCP_OPERATION_ID_VOLUME_DOWN = 0x42,
AVRCP_OPERATION_ID_MUTE = 0x43,
AVRCP_OPERATION_ID_PLAY = 0x44, AVRCP_OPERATION_ID_PLAY = 0x44,
AVRCP_OPERATION_ID_STOP = 0x45, AVRCP_OPERATION_ID_STOP = 0x45,
AVRCP_OPERATION_ID_PAUSE = 0x46, AVRCP_OPERATION_ID_PAUSE = 0x46,
@ -245,7 +256,9 @@ void avrcp_unit_info(uint16_t con_handle);
* @brief Get capabilities. * @brief Get capabilities.
* @param con_handle * @param con_handle
*/ */
void avrcp_get_capabilities(uint16_t con_handle); void avrcp_get_supported_company_ids(uint16_t con_handle);
void avrcp_get_supported_events(uint16_t con_handle);
/** /**
* @brief Play. * @brief Play.
@ -312,6 +325,36 @@ void avrcp_register_notification(uint16_t con_handle, avrcp_notification_event_i
*/ */
void avrcp_get_now_playing_info(uint16_t con_handle); void avrcp_get_now_playing_info(uint16_t con_handle);
/**
* @brief Set absolute volume 0-127 (corresponds to 0-100%)
* @param con_handle
*/
void avrcp_set_absolute_volume(uint16_t con_handle, uint8_t volume);
/**
* @brief Turns the volume to high.
* @param con_handle
*/
void avrcp_volume_up(uint16_t con_handle);
/**
* @brief Turns the volume to low.
* @param con_handle
*/
void avrcp_start_volume_down(uint16_t con_handle);
/**
* @brief Puts the sound out.
* @param con_handle
*/
void avrcp_start_mute(uint16_t con_handle);
/**
* @brief Skip to next playing media.
* @param con_handle
*/
void avrcp_start_skip(uint16_t con_handle);
/* API_END */ /* API_END */
#if defined __cplusplus #if defined __cplusplus
} }

View File

@ -80,6 +80,8 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
printf("--- avrcp_test: HCI_EVENT_DISCONNECTION_COMPLETE\n"); printf("--- avrcp_test: HCI_EVENT_DISCONNECTION_COMPLETE\n");
break; break;
case HCI_EVENT_AVRCP_META: case HCI_EVENT_AVRCP_META:
printf("app: ");
switch (packet[2]){ switch (packet[2]){
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED: case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED:
con_handle = avrcp_subevent_connection_established_get_con_handle(packet); con_handle = avrcp_subevent_connection_established_get_con_handle(packet);
@ -116,8 +118,8 @@ static void show_usage(void){
printf("\n--- Bluetooth AVRCP Test Console %s ---\n", bd_addr_to_str(iut_address)); printf("\n--- Bluetooth AVRCP Test Console %s ---\n", bd_addr_to_str(iut_address));
printf("c - create connection to addr %s\n", bd_addr_to_str(remote)); printf("c - create connection to addr %s\n", bd_addr_to_str(remote));
printf("C - disconnect\n"); printf("C - disconnect\n");
printf("u - get unit info\n"); printf("i - get unit info\n");
printf("t - get capabilities\n"); printf("e - get capabilities\n");
printf("l - avrcp_play\n"); printf("l - avrcp_play\n");
printf("s - avrcp_stop\n"); printf("s - avrcp_stop\n");
printf("p - avrcp_pause\n"); printf("p - avrcp_pause\n");
@ -129,6 +131,11 @@ static void show_usage(void){
printf("S - get play status\n"); printf("S - get play status\n");
printf("N - register notification, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED\n"); printf("N - register notification, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED\n");
printf("I - get now playing info\n"); printf("I - get now playing info\n");
printf("u - volume up\n");
printf("d - volume down\n");
printf("a - absolute volume of %d percent\n", 5000/127);
printf("m - mute\n");
printf("k - skip\n");
printf("Ctrl-c - exit\n"); printf("Ctrl-c - exit\n");
printf("---\n"); printf("---\n");
} }
@ -143,11 +150,11 @@ static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callbac
case 'c': case 'c':
avrcp_connect(remote); avrcp_connect(remote);
break; break;
case 'u': case 'i':
avrcp_unit_info(con_handle); avrcp_unit_info(con_handle);
break; break;
case 't': case 'e':
avrcp_get_capabilities(con_handle); avrcp_get_supported_events(con_handle);
break; break;
case 'l': case 'l':
avrcp_play(con_handle); avrcp_play(con_handle);
@ -185,6 +192,22 @@ static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callbac
case 'I': case 'I':
avrcp_get_now_playing_info(con_handle); avrcp_get_now_playing_info(con_handle);
break; break;
case 'a':
avrcp_set_absolute_volume(con_handle, 50);
break;
case 'u':
avrcp_volume_up(con_handle);
break;
case 'd':
avrcp_start_volume_down(con_handle);
break;
case 'm':
avrcp_start_mute(con_handle);
break;
case 'k':
avrcp_start_skip(con_handle);
break;
default: default:
show_usage(); show_usage();
break; break;