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_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 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 opcode;
int pos = 0;
uint8_t transport_header = packet[pos++];
uint8_t transport_header = packet[0];
uint8_t transaction_label = transport_header >> 4;
uint8_t packet_type = (transport_header & 0x0F) >> 2;
uint8_t frame_type = (transport_header & 0x03) >> 1;
uint8_t ipid = transport_header & 0x01;
uint8_t byte_value = packet[pos++];
uint16_t pid = (byte_value << 8) | packet[pos++];
uint8_t byte_value = packet[2];
uint16_t pid = (byte_value << 8) | packet[2];
pos = 3;
printf("L2CAP DATA, response: ");
printf_hexdump(packet, size);
printf(" Transport header 0x%02x (transaction_label %d, packet_type %d, frame_type %d, ipid %d), pid 0x%4x\n",
log_info(" Transport header 0x%02x (transaction_label %d, packet_type %d, frame_type %d, ipid %d), pid 0x%4x",
transport_header, transaction_label, packet_type, frame_type, ipid, pid);
switch (connection->cmd_to_send){
case AVRCP_CMD_OPCODE_UNIT_INFO:{
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 = operands[1] & 0x07;
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 );
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);
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:{
uint32_t song_length = big_endian_read_32(packet, pos);
pos += 4;
@ -707,7 +757,7 @@ void avrcp_unit_info(uint16_t con_handle){
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);
if (!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);
connection->cmd_operands[3] = AVRCP_PDU_ID_GET_CAPABILITIES; // PDU ID
connection->cmd_operands[4] = 0;
big_endian_store_16(connection->cmd_operands, 5, 1);
connection->cmd_operands[7] = 0x02;
big_endian_store_16(connection->cmd_operands, 5, 1); // parameter length
connection->cmd_operands[7] = capability_id; // capability ID
connection->cmd_operands_lenght = 8;
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){
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);
}
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){
avrcp_connection_t * connection = get_avrcp_connection_for_con_handle(con_handle);
if (!connection){
@ -828,6 +902,7 @@ void avrcp_register_notification(uint16_t con_handle, avrcp_notification_event_i
connection->cmd_operands_lenght = pos;
avrcp_request_can_send_now(connection, connection->l2cap_signaling_cid);
// AVRCP_SPEC_V14.pdf 166
// answer page 61
}
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;
connection->cmd_operands[pos++] = 0; // attribute count, if 0 get all attributes
// big_endian_store_32(connection->cmd_operands, pos, TitleOfMedia); // 0x1
// pos += 4;
// every attribute is 4 bytes long
connection->cmd_operands_lenght = pos;
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
/* API_START */
typedef enum {
AVRCP_CAPABILITY_ID_COMPANY = 0x02,
AVRCP_CAPABILITY_ID_EVENT = 0x03
} avrcp_capability_id_t;
typedef enum {
AVRCP_MEDIA_ATTR_TITLE = 1,
AVRCP_MEDIA_ATTR_ARTIST,
@ -67,7 +72,8 @@ typedef enum {
AVRCP_PDU_ID_GET_CAPABILITIES = 0x10,
AVRCP_PDU_ID_GET_ELEMENT_ATTRIBUTES = 0x20,
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;
typedef enum {
@ -144,6 +150,11 @@ typedef enum {
} avrcp_command_opcode_t;
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_STOP = 0x45,
AVRCP_OPERATION_ID_PAUSE = 0x46,
@ -245,7 +256,9 @@ void avrcp_unit_info(uint16_t con_handle);
* @brief Get capabilities.
* @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.
@ -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);
/**
* @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 */
#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");
break;
case HCI_EVENT_AVRCP_META:
printf("app: ");
switch (packet[2]){
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED:
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("c - create connection to addr %s\n", bd_addr_to_str(remote));
printf("C - disconnect\n");
printf("u - get unit info\n");
printf("t - get capabilities\n");
printf("i - get unit info\n");
printf("e - get capabilities\n");
printf("l - avrcp_play\n");
printf("s - avrcp_stop\n");
printf("p - avrcp_pause\n");
@ -129,6 +131,11 @@ static void show_usage(void){
printf("S - get play status\n");
printf("N - register notification, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED\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("---\n");
}
@ -143,11 +150,11 @@ static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callbac
case 'c':
avrcp_connect(remote);
break;
case 'u':
case 'i':
avrcp_unit_info(con_handle);
break;
case 't':
avrcp_get_capabilities(con_handle);
case 'e':
avrcp_get_supported_events(con_handle);
break;
case 'l':
avrcp_play(con_handle);
@ -185,6 +192,22 @@ static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callbac
case 'I':
avrcp_get_now_playing_info(con_handle);
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:
show_usage();
break;