diff --git a/src/classic/avrcp_browsing_target.c b/src/classic/avrcp_browsing_target.c index 993633094..3f1dd3995 100644 --- a/src/classic/avrcp_browsing_target.c +++ b/src/classic/avrcp_browsing_target.c @@ -51,6 +51,63 @@ static void avrcp_browser_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size, avrcp_context_t * context); static void avrcp_browsing_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); +static void avrcp_browsing_target_request_can_send_now(avrcp_browsing_connection_t * connection, uint16_t l2cap_cid){ + connection->wait_to_send = 1; + l2cap_request_can_send_now_event(l2cap_cid); +} + +static int avrcp_browsing_target_handle_can_send_now(avrcp_browsing_connection_t * connection){ + + int pos = 0; + l2cap_reserve_packet_buffer(); + uint8_t * packet = l2cap_get_outgoing_buffer(); + + // transport header + // Transaction label | Packet_type | C/R | IPID (1 == invalid profile identifier) + + // TODO: check for fragmentation + connection->packet_type = AVRCP_SINGLE_PACKET; + printf("ttransaction %d \n", connection->transaction_label); + 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; + // operands + // company id is 3 bytes long + // big_endian_store_24(packet, pos, BT_SIG_COMPANY_ID); + // pos += 3; + memcpy(packet+pos, connection->cmd_operands, connection->cmd_operands_length); + // printf_hexdump(packet+pos, connection->cmd_operands_length); + + pos += connection->cmd_operands_length; + connection->wait_to_send = 0; + printf(" send reject \n"); + printf_hexdump(packet, pos); + return l2cap_send_prepared(connection->l2cap_browsing_cid, pos); +} + + +static uint8_t avrcp_browsing_target_response_reject(avrcp_browsing_connection_t * connection, avrcp_status_code_t status){ + // AVRCP_CTYPE_RESPONSE_REJECTED + int pos = 0; + connection->cmd_operands[pos++] = AVRCP_PDU_ID_GENERAL_REJECT; + // connection->cmd_operands[pos++] = 0; + // param length + big_endian_store_16(connection->cmd_operands, pos, 1); + pos += 2; + connection->cmd_operands[pos++] = status; + connection->cmd_operands_length = 4; + connection->state = AVCTP_W2_SEND_RESPONSE; + avrcp_browsing_target_request_can_send_now(connection, connection->l2cap_browsing_cid); + return ERROR_CODE_SUCCESS; +} + static avrcp_connection_t * get_avrcp_connection_for_browsing_cid(uint16_t browsing_cid, avrcp_context_t * context){ btstack_linked_list_iterator_t it; btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &context->connections); @@ -84,6 +141,21 @@ static avrcp_browsing_connection_t * get_avrcp_browsing_connection_for_l2cap_cid return NULL; } +static void avrcp_browsing_target_emit_get_folder_items(btstack_packet_handler_t callback, uint16_t browsing_cid, avrcp_browsing_connection_t * connection){ + if (!callback) return; + uint8_t event[10]; + int pos = 0; + event[pos++] = HCI_EVENT_AVRCP_META; + event[pos++] = sizeof(event) - 2; + event[pos++] = AVRCP_SUBEVENT_BROWSING_GET_FOLDER_ITEMS; + little_endian_store_16(event, pos, browsing_cid); + pos += 2; + event[pos++] = connection->scope; + big_endian_store_32(event, pos, connection->attr_bitmap); + pos += 4; + (*callback)(HCI_EVENT_PACKET, 0, event, sizeof(event)); +} + static void avrcp_emit_browsing_connection_established(btstack_packet_handler_t callback, uint16_t browsing_cid, bd_addr_t addr, uint8_t status){ if (!callback) return; uint8_t event[12]; @@ -249,7 +321,6 @@ static void avrcp_browser_packet_handler(uint8_t packet_type, uint16_t channel, } - static void avrcp_browsing_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ avrcp_browsing_connection_t * browsing_connection; @@ -257,14 +328,84 @@ static void avrcp_browsing_target_packet_handler(uint8_t packet_type, uint16_t c case L2CAP_DATA_PACKET:{ browsing_connection = get_avrcp_browsing_connection_for_l2cap_cid(channel, &avrcp_target_context); if (!browsing_connection) break; + printf_hexdump(packet,size); + + int pos = 0; + uint8_t transport_header = packet[pos++]; + // Transaction label | Packet_type | C/R | IPID (1 == invalid profile identifier) + browsing_connection->transaction_label = transport_header >> 4; + avrcp_packet_type_t avctp_packet_type = (transport_header & 0x0F) >> 2; + printf("L2CAP_DATA_PACKET, transaction_label %d\n", browsing_connection->transaction_label); + switch (avctp_packet_type){ + case AVRCP_SINGLE_PACKET: + case AVRCP_START_PACKET: + // uint8_t frame_type = (transport_header & 0x03) >> 1; + // uint8_t ipid = transport_header & 0x01; + browsing_connection->subunit_type = packet[pos++] >> 2; + browsing_connection->subunit_id = 0; + browsing_connection->command_opcode = packet[pos++]; + // printf("subunit_id") + // pos += 2; + browsing_connection->num_packets = 1; + if (avctp_packet_type == AVRCP_START_PACKET){ + browsing_connection->num_packets = packet[pos++]; + } + browsing_connection->pdu_id = packet[pos++]; + // uint16_t length = big_endian_read_16(packet, pos); + // pos += 2; + break; + default: + break; + } + printf("pdu id 0x%2x\n", browsing_connection->pdu_id); + // uint32_t i; + switch(browsing_connection->pdu_id){ + case AVRCP_PDU_ID_GET_FOLDER_ITEMS: + printf("\n"); + browsing_connection->scope = packet[pos++]; + browsing_connection->start_item = big_endian_read_32(packet, pos); + pos += 4; + browsing_connection->end_item = big_endian_read_32(packet, pos); + pos += 4; + uint8_t attr_count = packet[pos++]; + + while (attr_count){ + uint32_t attr_id = big_endian_read_32(packet, pos); + pos += 4; + browsing_connection->attr_bitmap |= (1 << attr_id); + attr_count--; + } + avrcp_browsing_target_emit_get_folder_items(avrcp_target_context.browsing_avrcp_callback, channel, browsing_connection); + + break; + default: + printf(" not parsed pdu ID 0x%02x\n", browsing_connection->pdu_id); + break; + } + + switch (avctp_packet_type){ + case AVRCP_SINGLE_PACKET: + case AVRCP_END_PACKET: + printf("send avrcp_browsing_target_response_reject\n"); + browsing_connection->state = AVCTP_CONNECTION_OPENED; + avrcp_browsing_target_response_reject(browsing_connection, AVRCP_STATUS_INVALID_COMMAND); + // avrcp_browsing_target_emit_done_with_uid_counter(avrcp_target_context.browsing_avrcp_callback, channel, browsing_connection->uid_counter, browsing_connection->browsing_status, ERROR_CODE_SUCCESS); + break; + default: + break; + } + // printf(" paket done\n"); break; } + case HCI_EVENT_PACKET: switch (hci_event_packet_get_type(packet)){ case L2CAP_EVENT_CAN_SEND_NOW: browsing_connection = get_avrcp_browsing_connection_for_l2cap_cid(channel, &avrcp_target_context); if (!browsing_connection) break; - // avrcp_browsing_target_handle_can_send_now(browsing_connection); + if (browsing_connection->state != AVCTP_W2_SEND_RESPONSE) return; + browsing_connection->state = AVCTP_CONNECTION_OPENED; + avrcp_browsing_target_handle_can_send_now(browsing_connection); break; default: avrcp_browser_packet_handler(packet_type, channel, packet, size, &avrcp_target_context); diff --git a/src/classic/avrcp_target.c b/src/classic/avrcp_target.c index d3b89e0d0..bb4b4d8f3 100644 --- a/src/classic/avrcp_target.c +++ b/src/classic/avrcp_target.c @@ -395,6 +395,32 @@ static uint8_t avrcp_target_response_vendor_dependent_interim(avrcp_connection_t return ERROR_CODE_SUCCESS; } +static uint8_t avrcp_target_response_addressed_player_changed_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){ + connection->command_type = AVRCP_CTYPE_RESPONSE_INTERIM; + connection->subunit_type = subunit_type; + connection->subunit_id = subunit_id; + connection->command_opcode = opcode; + printf("avrcp_target_response_addressed_player_changed_interim \n"); + // 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, 5); + pos += 2; + connection->cmd_operands[pos++] = AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED; + big_endian_read_16( &connection->cmd_operands[pos], connection->addressed_player_id); + pos += 2; + big_endian_read_16( &connection->cmd_operands[pos], connection->uid_counter); + pos += 2; + + 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; @@ -659,7 +685,7 @@ uint8_t avrcp_target_track_changed(uint16_t avrcp_cid, uint8_t * track_id){ 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."); + log_error("avrcp_target_playing_content_changed: could not find a connection."); return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; } if (connection->notifications_enabled & (1 << AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED)) { @@ -669,6 +695,22 @@ uint8_t avrcp_target_playing_content_changed(uint16_t avrcp_cid){ return ERROR_CODE_SUCCESS; } +uint8_t avrcp_target_addressed_player_changed(uint16_t avrcp_cid, uint16_t player_id, uint16_t uid_counter){ + 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->notifications_enabled & (1 << AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED)) { + printf("send AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED\n"); + // connection->addressed_player_changed = 1; + // connection->uid_counter = uid_counter; + // connection->addressed_player_id = player_id; + // 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){ @@ -803,10 +845,22 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec case AVRCP_CMD_OPCODE_VENDOR_DEPENDENT: pdu_id = pdu[0]; // 1 - reserved - // 2-3 param length, + // 2-3 param length, + printf_hexdump(packet, size); + + uint16_t length = big_endian_read_16(pdu, 1); memcpy(connection->cmd_operands, company_id, 3); connection->cmd_operands_length = 3; switch (pdu_id){ + case AVRCP_PDU_ID_SET_ADDRESSED_PLAYER:{ + if (length == 0){ + printf(" reject id\n"); + avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_PLAYER_ID); + break; + } + avrcp_target_response_accept(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_SUCCESS); + break; + } case AVRCP_PDU_ID_GET_CAPABILITIES:{ avrcp_capability_id_t capability_id = (avrcp_capability_id_t) pdu[pos]; switch (capability_id){ @@ -898,10 +952,14 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec 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; + case AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED: + connection->notifications_enabled |= event_mask; + printf("respond with interim AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED\n"); + avrcp_target_response_addressed_player_changed_interim(connection, subunit_type, subunit_id, opcode, pdu_id); + return; default: avrcp_target_response_reject(connection, subunit_type, subunit_id, opcode, pdu_id, AVRCP_STATUS_INVALID_PARAMETER); return; @@ -938,6 +996,53 @@ static void avrcp_handle_l2cap_data_packet_for_signaling_connection(avrcp_connec } } +static int avrcp_target_send_addressed_player_changed_notification(uint16_t cid, avrcp_connection_t * connection, uint16_t uid, uint16_t uid_counter){ + if (!connection){ + log_error("avrcp tartget: could not find a connection."); + return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; + } + printf("avrcp_target_send_addressed_player_changed_notification \n"); + 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(); + + 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; + big_endian_store_16(packet, pos, 5); + pos += 2; + packet[pos++] = AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED; + big_endian_store_16(packet, pos, uid); + pos += 2; + big_endian_store_16(packet, pos, uid_counter); + pos += 2; + + connection->wait_to_send = 0; + return l2cap_send_prepared(cid, pos); +} + + 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."); @@ -990,6 +1095,7 @@ static void avrcp_target_reset_notification(avrcp_connection_t * connection, uin log_error("avrcp tartget: could not find a connection."); return; } + printf("reset notification\n"); connection->notifications_enabled &= ~(1 << notification_id); connection->command_opcode = AVRCP_CMD_OPCODE_VENDOR_DEPENDENT; @@ -1041,6 +1147,14 @@ static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, u break; } + // if (connection->addressed_player_changed){ + // connection->playback_status_changed = 0; + // avrcp_target_send_addressed_player_changed_notification(connection->l2cap_signaling_cid, connection, connection->addressed_player_id, connection->uid_counter); + // avrcp_target_reset_notification(connection, AVRCP_NOTIFICATION_EVENT_ADDRESSED_PLAYER_CHANGED); + // 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); diff --git a/src/classic/avrcp_target.h b/src/classic/avrcp_target.h index 9458ca408..e8dede525 100644 --- a/src/classic/avrcp_target.h +++ b/src/classic/avrcp_target.h @@ -85,6 +85,8 @@ void avrcp_target_set_unit_info(uint16_t avrcp_cid, avrcp_subunit_type_t unit_ty 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_addressed_player_changed(uint16_t avrcp_cid, uint16_t player_id, uint16_t uid_counter); + 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_track_changed(uint16_t avrcp_cid, uint8_t * trackID); diff --git a/test/pts/avrcp_target_test.c b/test/pts/avrcp_target_test.c index 9ed31dc68..f584ab2f2 100644 --- a/test/pts/avrcp_target_test.c +++ b/test/pts/avrcp_target_test.c @@ -547,6 +547,10 @@ static void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, u printf("AVRCP Browsing Client connected\n"); return; } + case AVRCP_SUBEVENT_BROWSING_GET_FOLDER_ITEMS: + printf(" AVRCP_SUBEVENT_BROWSING_GET_FOLDER_ITEMS \n"); + break; + case AVRCP_SUBEVENT_BROWSING_CONNECTION_RELEASED: printf("AVRCP Browsing Controller released\n"); browsing_cid = 0;