mirror of
https://github.com/bluekitchen/btstack.git
synced 2025-02-21 03:40:47 +00:00
avrcp: add avrcp_cover_art_client
This commit is contained in:
parent
6bee431c2a
commit
ac72606302
@ -115,6 +115,7 @@
|
||||
#include "classic/avrcp_browsing_controller.h"
|
||||
#include "classic/avrcp_browsing_target.h"
|
||||
#include "classic/avrcp_controller.h"
|
||||
#include "classic/avrcp_cover_art_client.h"
|
||||
#include "classic/avrcp_media_item_iterator.h"
|
||||
#include "classic/avrcp_target.h"
|
||||
#include "classic/bnep.h"
|
||||
|
@ -13,6 +13,7 @@ SRC_CLASSIC_FILES = \
|
||||
avrcp_browsing_controller.c \
|
||||
avrcp_browsing_target.c \
|
||||
avrcp_controller.c \
|
||||
avrcp_cover_art_client.c \
|
||||
avrcp_media_item_iterator.c \
|
||||
avrcp_target.c \
|
||||
bnep.c \
|
||||
|
578
src/classic/avrcp_cover_art_client.c
Normal file
578
src/classic/avrcp_cover_art_client.c
Normal file
@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (C) 2023 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
#define BTSTACK_FILE__ "avrcp_cover_art_client.c"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "btstack_debug.h"
|
||||
#include "btstack_event.h"
|
||||
#include "classic/avrcp.h"
|
||||
#include "classic/avrcp_cover_art_client.h"
|
||||
#include "classic/obex.h"
|
||||
#include "hci_event.h"
|
||||
|
||||
#ifdef ENABLE_AVRCP_COVER_ART
|
||||
|
||||
static const hci_event_t avrcp_cover_art_client_connected = {
|
||||
.event_code = HCI_EVENT_AVRCP_META,
|
||||
.subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_ESTABLISHED,
|
||||
.format = "1B22"
|
||||
};
|
||||
|
||||
static const hci_event_t avrcp_cover_art_client_disconnected = {
|
||||
.event_code = HCI_EVENT_AVRCP_META,
|
||||
.subevent_code = AVRCP_SUBEVENT_COVER_ART_CONNECTION_RELEASED,
|
||||
.format = "2"
|
||||
};
|
||||
|
||||
static const hci_event_t avrcp_cover_art_client_operation_complete = {
|
||||
.event_code = HCI_EVENT_AVRCP_META,
|
||||
.subevent_code = AVRCP_SUBEVENT_COVER_ART_OPERATION_COMPLETE,
|
||||
.format = "21"
|
||||
};
|
||||
|
||||
// 7163DD54-4A7E-11E2-B47C-0050C2490048
|
||||
static const uint8_t avrcp_cover_art_uuid[] = { 0x71, 0x63, 0xDD, 0x54, 0x4A, 0x7E, 0x11, 0xE2, 0xB4, 0x7C, 0x00, 0x50, 0xC2, 0x49, 0x00, 0x48 };
|
||||
|
||||
// OBEX types
|
||||
const char * avrcp_cover_art_image_properties_type = "x-bt/img-properties";
|
||||
const char * avrcp_cover_art_image_type = "x-bt/img-img";
|
||||
const char * avrcp_cover_art_linked_thumbnail_type = "x-bt/img-thm";
|
||||
|
||||
static btstack_linked_list_t avrcp_cover_art_client_connections;
|
||||
|
||||
static void avrcp_browsing_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
static uint16_t avrcp_cover_art_client_next_cid() {
|
||||
static uint16_t cid = 0;
|
||||
cid++;
|
||||
if (cid == 0){
|
||||
cid = 1;
|
||||
}
|
||||
return cid;
|
||||
}
|
||||
|
||||
static avrcp_cover_art_client_t * avrcp_cover_art_client_for_goep_cid(uint16_t goep_cid){
|
||||
btstack_linked_list_iterator_t it;
|
||||
btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
|
||||
while (btstack_linked_list_iterator_has_next(&it)){
|
||||
avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
|
||||
if (connection->goep_cid == goep_cid) {
|
||||
return connection;
|
||||
};
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static avrcp_cover_art_client_t * avrcp_cover_art_client_for_avrcp_cid(uint16_t avrcp_cid){
|
||||
btstack_linked_list_iterator_t it;
|
||||
btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
|
||||
while (btstack_linked_list_iterator_has_next(&it)){
|
||||
avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
|
||||
if (connection->avrcp_cid == avrcp_cid) {
|
||||
return connection;
|
||||
};
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static avrcp_cover_art_client_t * avrcp_cover_art_client_for_cover_art_cid(uint16_t cover_art_cid){
|
||||
btstack_linked_list_iterator_t it;
|
||||
btstack_linked_list_iterator_init(&it, (btstack_linked_list_t *) &avrcp_cover_art_client_connections);
|
||||
while (btstack_linked_list_iterator_has_next(&it)){
|
||||
avrcp_cover_art_client_t * connection = (avrcp_cover_art_client_t *)btstack_linked_list_iterator_next(&it);
|
||||
if (connection->cover_art_cid == cover_art_cid) {
|
||||
return connection;
|
||||
};
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_emit_connection_established(btstack_packet_handler_t packet_handler, uint8_t status,
|
||||
bd_addr_t addr, uint16_t avrcp_cid,
|
||||
uint16_t cover_art_cid) {
|
||||
uint8_t buffer[20];
|
||||
uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_connected, status, addr, avrcp_cid, cover_art_cid);
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
|
||||
}
|
||||
|
||||
static void cover_art_client_emit_operation_complete_event(avrcp_cover_art_client_t * cover_art_client, uint8_t status) {
|
||||
uint8_t buffer[20];
|
||||
uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_operation_complete, cover_art_client->cover_art_cid, status);
|
||||
(*cover_art_client->packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_emit_connection_released(btstack_packet_handler_t packet_handler, uint16_t cover_art_cid) {
|
||||
uint8_t buffer[20];
|
||||
uint16_t size = hci_event_create_from_template_and_arguments(buffer, sizeof(buffer), &avrcp_cover_art_client_disconnected, cover_art_cid);
|
||||
(*packet_handler)(HCI_EVENT_PACKET, 0, buffer, size);
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_finalize_connection(avrcp_cover_art_client_t *cover_art_client) {
|
||||
btstack_assert(cover_art_client != NULL);
|
||||
memset(cover_art_client, 0, sizeof(avrcp_cover_art_client_t));
|
||||
btstack_linked_list_remove(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client);
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_parser_callback_connect(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){
|
||||
avrcp_cover_art_client_t * client = (avrcp_cover_art_client_t *) user_data;
|
||||
switch (header_id){
|
||||
case OBEX_HEADER_CONNECTION_ID:
|
||||
if (obex_parser_header_store(client->obex_header_buffer, sizeof(client->obex_header_buffer), total_len, data_offset, data_buffer, data_len) == OBEX_PARSER_HEADER_COMPLETE){
|
||||
goep_client_set_connection_id(client->goep_cid, big_endian_read_32(client->obex_header_buffer, 0));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_prepare_srm_header(avrcp_cover_art_client_t * cover_art_client){
|
||||
if (cover_art_client->flow_control_enabled == false){
|
||||
goep_client_header_add_srm_enable(cover_art_client->goep_cid);
|
||||
cover_art_client->srm_state = SRM_W4_CONFIRM;
|
||||
}
|
||||
}
|
||||
|
||||
static void obex_srm_init(avrcp_cover_art_obex_srm_t * obex_srm){
|
||||
obex_srm->srm_value = OBEX_SRM_DISABLE;
|
||||
obex_srm->srmp_value = OBEX_SRMP_NEXT;
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_handle_srm_headers(avrcp_cover_art_client_t *context) {
|
||||
const avrcp_cover_art_obex_srm_t * obex_srm = &context->obex_srm;
|
||||
// Update SRM state based on SRM headers
|
||||
switch (context->srm_state){
|
||||
case SRM_W4_CONFIRM:
|
||||
switch (obex_srm->srm_value){
|
||||
case OBEX_SRM_ENABLE:
|
||||
switch (obex_srm->srmp_value){
|
||||
case OBEX_SRMP_WAIT:
|
||||
context->srm_state = SRM_ENABLED_BUT_WAITING;
|
||||
break;
|
||||
default:
|
||||
context->srm_state = SRM_ENABLED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
context->srm_state = SRM_DISABLED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SRM_ENABLED_BUT_WAITING:
|
||||
switch (obex_srm->srmp_value){
|
||||
case OBEX_SRMP_WAIT:
|
||||
context->srm_state = SRM_ENABLED_BUT_WAITING;
|
||||
break;
|
||||
default:
|
||||
context->srm_state = SRM_ENABLED;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
log_info("SRM state %u", context->srm_state);
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_parser_callback_get_operation(void * user_data, uint8_t header_id, uint16_t total_len, uint16_t data_offset, const uint8_t * data_buffer, uint16_t data_len){
|
||||
avrcp_cover_art_client_t *client = (avrcp_cover_art_client_t *) user_data;
|
||||
switch (header_id) {
|
||||
case OBEX_HEADER_SINGLE_RESPONSE_MODE:
|
||||
obex_parser_header_store(&client->obex_srm.srm_value, 1, total_len, data_offset, data_buffer, data_len);
|
||||
break;
|
||||
case OBEX_HEADER_SINGLE_RESPONSE_MODE_PARAMETER:
|
||||
obex_parser_header_store(&client->obex_srm.srmp_value, 1, total_len, data_offset, data_buffer, data_len);
|
||||
break;
|
||||
case OBEX_HEADER_BODY:
|
||||
case OBEX_HEADER_END_OF_BODY:
|
||||
switch(client->state){
|
||||
case AVRCP_COVER_ART_W4_OBJECT:
|
||||
client->packet_handler(BIP_DATA_PACKET, client->cover_art_cid, (uint8_t *) data_buffer, data_len);
|
||||
if (data_offset + data_len == total_len){
|
||||
client->flow_wait_for_user = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
btstack_unreachable();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ignore other headers
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_prepare_get_operation(avrcp_cover_art_client_t * cover_art_client){
|
||||
obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_GET, avrcp_cover_art_client_parser_callback_get_operation, cover_art_client);
|
||||
obex_srm_init(&cover_art_client->obex_srm);
|
||||
cover_art_client->obex_parser_waiting_for_response = true;
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_handle_can_send_now(avrcp_cover_art_client_t * cover_art_client){
|
||||
switch (cover_art_client->state) {
|
||||
case AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST:
|
||||
// prepare request
|
||||
goep_client_request_create_connect(cover_art_client->goep_cid, OBEX_VERSION, 0, OBEX_MAX_PACKETLEN_DEFAULT);
|
||||
goep_client_header_add_target(cover_art_client->goep_cid, avrcp_cover_art_uuid, 16);
|
||||
// state
|
||||
cover_art_client->state = AVRCP_COVER_ART_W4_CONNECT_RESPONSE;
|
||||
// prepare response
|
||||
obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_CONNECT,
|
||||
avrcp_cover_art_client_parser_callback_connect, cover_art_client);
|
||||
obex_srm_init(&cover_art_client->obex_srm);
|
||||
cover_art_client->obex_parser_waiting_for_response = true;
|
||||
// send packet
|
||||
goep_client_execute(cover_art_client->goep_cid);
|
||||
break;
|
||||
case AVRCP_COVER_ART_W2_SEND_GET_OBJECT:
|
||||
goep_client_request_create_get(cover_art_client->goep_cid);
|
||||
if (cover_art_client->first_request){
|
||||
cover_art_client->first_request = false;
|
||||
avrcp_cover_art_client_prepare_srm_header(cover_art_client);
|
||||
goep_client_header_add_type(cover_art_client->goep_cid, cover_art_client->object_type);
|
||||
if (cover_art_client->image_descriptor != NULL){
|
||||
goep_client_header_add_variable(cover_art_client->goep_cid, OBEX_HEADER_IMG_DESCRIPTOR, (const uint8_t *) cover_art_client->image_descriptor, strlen(cover_art_client->image_descriptor));
|
||||
}
|
||||
uint8_t image_handle_len = btstack_max(7, strlen(cover_art_client->image_handle));
|
||||
goep_client_header_add_unicode_prefix(cover_art_client->goep_cid, OBEX_HEADER_IMG_HANDLE, cover_art_client->image_handle, image_handle_len);
|
||||
}
|
||||
// state
|
||||
cover_art_client->state = AVRCP_COVER_ART_W4_OBJECT;
|
||||
cover_art_client->flow_next_triggered = 0;
|
||||
cover_art_client->flow_wait_for_user = 0;
|
||||
// prepare response
|
||||
avrcp_cover_art_client_prepare_get_operation(cover_art_client);
|
||||
// send packet
|
||||
goep_client_execute(cover_art_client->goep_cid);
|
||||
break;
|
||||
case AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST:
|
||||
// prepare request
|
||||
goep_client_request_create_disconnect(cover_art_client->goep_cid);
|
||||
// state
|
||||
cover_art_client->state = AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE;
|
||||
// prepare response
|
||||
obex_parser_init_for_response(&cover_art_client->obex_parser, OBEX_OPCODE_DISCONNECT, NULL, cover_art_client);
|
||||
cover_art_client->obex_parser_waiting_for_response = true;
|
||||
// send packet
|
||||
goep_client_execute(cover_art_client->goep_cid);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_goep_event_handler(const uint8_t *packet, uint16_t size) {
|
||||
UNUSED(size);
|
||||
uint8_t status;
|
||||
avrcp_cover_art_client_t * cover_art_client;
|
||||
btstack_packet_handler_t packet_handler;
|
||||
uint16_t cover_art_cid;
|
||||
|
||||
switch (hci_event_packet_get_type(packet)) {
|
||||
case HCI_EVENT_GOEP_META:
|
||||
switch (hci_event_goep_meta_get_subevent_code(packet)){
|
||||
case GOEP_SUBEVENT_CONNECTION_OPENED:
|
||||
cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
|
||||
btstack_assert(cover_art_client != NULL);
|
||||
status = goep_subevent_connection_opened_get_status(packet);
|
||||
if (status){
|
||||
log_info("connection failed %u", status);
|
||||
avrcp_cover_art_finalize_connection(cover_art_client);
|
||||
avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler, status,
|
||||
cover_art_client->addr,
|
||||
cover_art_client->avrcp_cid,
|
||||
cover_art_client->cover_art_cid);
|
||||
} else {
|
||||
log_info("connection established");
|
||||
cover_art_client->state = AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST;
|
||||
goep_client_request_can_send_now(cover_art_client->goep_cid);
|
||||
}
|
||||
break;
|
||||
case GOEP_SUBEVENT_CONNECTION_CLOSED:
|
||||
cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_connection_opened_get_goep_cid(packet));
|
||||
btstack_assert(cover_art_client != NULL);
|
||||
if (cover_art_client->state > AVRCP_COVER_ART_CONNECTED){
|
||||
cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_DISCONNECTED);
|
||||
}
|
||||
cover_art_cid = cover_art_client->cover_art_cid;
|
||||
packet_handler = cover_art_client->packet_handler;
|
||||
avrcp_cover_art_finalize_connection(cover_art_client);
|
||||
avrcp_cover_art_client_emit_connection_released(packet_handler, cover_art_cid);
|
||||
break;
|
||||
case GOEP_SUBEVENT_CAN_SEND_NOW:
|
||||
cover_art_client = avrcp_cover_art_client_for_goep_cid(goep_subevent_can_send_now_get_goep_cid(packet));
|
||||
btstack_assert(cover_art_client != NULL);
|
||||
avrcp_cover_art_client_handle_can_send_now(cover_art_client);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_client_goep_data_handler(avrcp_cover_art_client_t * cover_art_client, uint8_t *packet, uint16_t size){
|
||||
btstack_assert(cover_art_client->obex_parser_waiting_for_response);
|
||||
|
||||
obex_parser_object_state_t parser_state;
|
||||
parser_state = obex_parser_process_data(&cover_art_client->obex_parser, packet, size);
|
||||
if (parser_state == OBEX_PARSER_OBJECT_STATE_COMPLETE){
|
||||
cover_art_client->obex_parser_waiting_for_response = false;
|
||||
obex_parser_operation_info_t op_info;
|
||||
obex_parser_get_operation_info(&cover_art_client->obex_parser, &op_info);
|
||||
switch (cover_art_client->state){
|
||||
case AVRCP_COVER_ART_W4_CONNECT_RESPONSE:
|
||||
switch (op_info.response_code) {
|
||||
case OBEX_RESP_SUCCESS:
|
||||
cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
|
||||
avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
|
||||
ERROR_CODE_SUCCESS,
|
||||
cover_art_client->addr,
|
||||
cover_art_client->avrcp_cid,
|
||||
cover_art_client->cover_art_cid);
|
||||
break;
|
||||
default:
|
||||
log_info("pbap: obex connect failed, result 0x%02x", packet[0]);
|
||||
cover_art_client->state = AVRCP_COVER_ART_INIT;
|
||||
avrcp_cover_art_client_emit_connection_established(cover_art_client->packet_handler,
|
||||
OBEX_CONNECT_FAILED,
|
||||
cover_art_client->addr,
|
||||
cover_art_client->avrcp_cid,
|
||||
cover_art_client->cover_art_cid);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AVRCP_COVER_ART_W4_OBJECT:
|
||||
switch (op_info.response_code) {
|
||||
case OBEX_RESP_CONTINUE:
|
||||
avrcp_cover_art_client_handle_srm_headers(cover_art_client);
|
||||
if (cover_art_client->srm_state == SRM_ENABLED) {
|
||||
// prepare response
|
||||
avrcp_cover_art_client_prepare_get_operation(cover_art_client);
|
||||
break;
|
||||
}
|
||||
cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT;
|
||||
if (!cover_art_client->flow_control_enabled || !cover_art_client->flow_wait_for_user ||
|
||||
cover_art_client->flow_next_triggered) {
|
||||
goep_client_request_can_send_now(cover_art_client->goep_cid);
|
||||
}
|
||||
break;
|
||||
case OBEX_RESP_SUCCESS:
|
||||
cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
|
||||
cover_art_client_emit_operation_complete_event(cover_art_client, ERROR_CODE_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
log_info("unexpected response 0x%02x", packet[0]);
|
||||
cover_art_client->state = AVRCP_COVER_ART_CONNECTED;
|
||||
cover_art_client_emit_operation_complete_event(cover_art_client, OBEX_UNKNOWN_ERROR);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE:
|
||||
goep_client_disconnect(cover_art_client->goep_cid);
|
||||
break;
|
||||
default:
|
||||
btstack_unreachable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
|
||||
UNUSED(channel); // ok: there is no channel
|
||||
UNUSED(size); // ok: handling own goep events
|
||||
avrcp_cover_art_client_t * cover_art_client;
|
||||
switch (packet_type){
|
||||
case HCI_EVENT_PACKET:
|
||||
avrcp_cover_art_goep_event_handler(packet, size);
|
||||
break;
|
||||
case GOEP_DATA_PACKET:
|
||||
cover_art_client = avrcp_cover_art_client_for_goep_cid(channel);
|
||||
btstack_assert(cover_art_client != NULL);
|
||||
avrcp_cover_art_client_goep_data_handler(cover_art_client, packet, size);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t avrcp_cover_art_client_setup_connection(avrcp_cover_art_client_t * cover_art_client, uint16_t l2cap_psm){
|
||||
cover_art_client->state = AVRCP_COVER_ART_W4_GOEP_CONNECTION;
|
||||
return goep_client_connect_l2cap(&cover_art_client->goep_client,
|
||||
(l2cap_ertm_config_t *) cover_art_client->ertm_config,
|
||||
cover_art_client->ertm_buffer,
|
||||
cover_art_client->ertm_buffer_size,
|
||||
&avrcp_cover_art_packet_handler,
|
||||
cover_art_client->addr,
|
||||
l2cap_psm,
|
||||
&cover_art_client->goep_cid);
|
||||
}
|
||||
|
||||
static void avrcp_cover_art_handle_sdp_query_complete(avrcp_connection_t * connection, uint8_t status){
|
||||
avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_avrcp_cid(connection->avrcp_cid);
|
||||
|
||||
if (cover_art_client == NULL) {
|
||||
return;
|
||||
}
|
||||
if (cover_art_client->state != AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE){
|
||||
return;
|
||||
}
|
||||
|
||||
// l2cap available?
|
||||
if (status == ERROR_CODE_SUCCESS){
|
||||
if (connection->cover_art_psm == 0){
|
||||
status = SDP_SERVICE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
if (status == ERROR_CODE_SUCCESS) {
|
||||
// ready to connect
|
||||
cover_art_client->state = AVRCP_COVER_ART_W2_GOEP_CONNECT;
|
||||
avrcp_cover_art_client_setup_connection(cover_art_client, connection->cover_art_psm);
|
||||
} else {
|
||||
btstack_packet_handler_t packet_handler = cover_art_client->packet_handler;
|
||||
uint16_t cover_art_cid = cover_art_client->cover_art_cid;
|
||||
uint16_t avrcp_cid = cover_art_client->avrcp_cid;
|
||||
avrcp_cover_art_finalize_connection(cover_art_client);
|
||||
avrcp_cover_art_client_emit_connection_established(packet_handler, status, connection->remote_addr,
|
||||
avrcp_cid, cover_art_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void avrcp_cover_art_client_init(void){
|
||||
avrcp_register_cover_art_sdp_query_complete_handler(&avrcp_cover_art_handle_sdp_query_complete);
|
||||
}
|
||||
|
||||
uint8_t
|
||||
avrcp_cover_art_client_connect(avrcp_cover_art_client_t *cover_art_client, btstack_packet_handler_t packet_handler,
|
||||
bd_addr_t remote_addr, uint8_t *ertm_buffer, uint32_t ertm_buffer_size,
|
||||
const l2cap_ertm_config_t *ertm_config, uint16_t *avrcp_cover_art_cid) {
|
||||
|
||||
avrcp_connection_t * connection_controller = avrcp_get_connection_for_bd_addr_for_role(AVRCP_CONTROLLER, remote_addr);
|
||||
avrcp_connection_t * connection_target = avrcp_get_connection_for_bd_addr_for_role(AVRCP_TARGET, remote_addr);
|
||||
if ((connection_target == NULL) || (connection_controller == NULL)){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
|
||||
cover_art_client->cover_art_cid = avrcp_cover_art_client_next_cid();
|
||||
memcpy(cover_art_client->addr, remote_addr, 6);
|
||||
cover_art_client->avrcp_cid = connection_controller->avrcp_cid;
|
||||
cover_art_client->packet_handler = packet_handler;
|
||||
cover_art_client->flow_control_enabled = false;
|
||||
|
||||
// store ERTM config
|
||||
cover_art_client->ertm_config = ertm_config;
|
||||
cover_art_client->ertm_buffer = ertm_buffer;
|
||||
cover_art_client->ertm_buffer_size = ertm_buffer_size;
|
||||
|
||||
if (avrcp_cover_art_cid != NULL){
|
||||
*avrcp_cover_art_cid = cover_art_client->cover_art_cid;
|
||||
}
|
||||
|
||||
btstack_linked_list_add(&avrcp_cover_art_client_connections, (btstack_linked_item_t *) cover_art_client);
|
||||
|
||||
if (connection_controller->cover_art_psm == 0){
|
||||
cover_art_client->state = AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE;
|
||||
avrcp_trigger_sdp_query(connection_controller, connection_controller);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
} else {
|
||||
return avrcp_cover_art_client_setup_connection(cover_art_client, connection_controller->cover_art_psm);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t avrcp_cover_art_client_get_object(uint16_t avrcp_cover_art_cid, const char * object_type, const char * image_handle, const char * image_descriptor){
|
||||
avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid);
|
||||
if (cover_art_client == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (cover_art_client->state != AVRCP_COVER_ART_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
cover_art_client->state = AVRCP_COVER_ART_W2_SEND_GET_OBJECT;
|
||||
cover_art_client->first_request = true;
|
||||
cover_art_client->image_handle = image_handle;
|
||||
cover_art_client->image_descriptor = image_descriptor;
|
||||
cover_art_client->object_type = object_type;
|
||||
goep_client_request_can_send_now(cover_art_client->goep_cid);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t avrcp_cover_art_client_get_linked_thumbnail(uint16_t avrcp_cover_art_cid, const char * image_handle){
|
||||
return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
|
||||
avrcp_cover_art_linked_thumbnail_type,
|
||||
image_handle,
|
||||
NULL);
|
||||
}
|
||||
|
||||
uint8_t avrcp_cover_art_client_get_image(uint16_t avrcp_cover_art_cid, const char * image_handle, const char * image_descriptor){
|
||||
return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
|
||||
avrcp_cover_art_image_type,
|
||||
image_handle,
|
||||
image_descriptor);
|
||||
}
|
||||
|
||||
uint8_t avrcp_cover_art_client_get_image_properties(uint16_t avrcp_cover_art_cid, const char * image_handle){
|
||||
return avrcp_cover_art_client_get_object(avrcp_cover_art_cid,
|
||||
avrcp_cover_art_image_properties_type,
|
||||
image_handle,
|
||||
NULL);
|
||||
}
|
||||
|
||||
uint8_t avrcp_cover_art_client_disconnect(uint16_t avrcp_cover_art_cid){
|
||||
avrcp_cover_art_client_t * cover_art_client = avrcp_cover_art_client_for_cover_art_cid(avrcp_cover_art_cid);
|
||||
if (cover_art_client == NULL){
|
||||
return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
|
||||
}
|
||||
if (cover_art_client->state < AVRCP_COVER_ART_CONNECTED){
|
||||
return ERROR_CODE_COMMAND_DISALLOWED;
|
||||
}
|
||||
cover_art_client->state = AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST;
|
||||
goep_client_request_can_send_now(cover_art_client->goep_cid);
|
||||
return ERROR_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
void avrcp_cover_art_client_deinit(void){
|
||||
avrcp_cover_art_client_connections = NULL;
|
||||
}
|
||||
|
||||
#endif /* ENABLE_AVRCP_COVER_ART */
|
207
src/classic/avrcp_cover_art_client.h
Normal file
207
src/classic/avrcp_cover_art_client.h
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2023 BlueKitchen GmbH
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holders nor the names of
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
* 4. Any redistribution, use, or modification is done solely for
|
||||
* personal benefit and not for any commercial purpose or for
|
||||
* monetary gain.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
* GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* Please inquire about commercial licensing options at
|
||||
* contact@bluekitchen-gmbh.com
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @title AVRCP Cover Art Client
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AVRCP_COVER_ART_CLIENT_H
|
||||
#define AVRCP_COVER_ART_CLIENT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "bluetooth.h"
|
||||
#include "btstack_config.h"
|
||||
#include "btstack_defines.h"
|
||||
#include "classic/goep_client.h"
|
||||
#include "btstack_linked_list.h"
|
||||
#include "classic/obex_parser.h"
|
||||
#include "l2cap.h"
|
||||
|
||||
#if defined __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AVRCP_COVER_ART
|
||||
typedef enum {
|
||||
AVRCP_COVER_ART_INIT = 0,
|
||||
AVRCP_COVER_ART_W4_SDP_QUERY_COMPLETE,
|
||||
AVRCP_COVER_ART_W2_GOEP_CONNECT,
|
||||
AVRCP_COVER_ART_W4_GOEP_CONNECTION,
|
||||
AVRCP_COVER_ART_W2_SEND_CONNECT_REQUEST,
|
||||
AVRCP_COVER_ART_W4_CONNECT_RESPONSE,
|
||||
AVRCP_COVER_ART_W4_USER_AUTHENTICATION,
|
||||
AVRCP_COVER_ART_W2_SEND_AUTHENTICATED_CONNECT,
|
||||
AVRCP_COVER_ART_CONNECTED,
|
||||
// generic get object
|
||||
AVRCP_COVER_ART_W2_SEND_GET_OBJECT,
|
||||
AVRCP_COVER_ART_W4_OBJECT,
|
||||
// disconnect
|
||||
AVRCP_COVER_ART_W2_SEND_DISCONNECT_REQUEST,
|
||||
AVRCP_COVER_ART_W4_DISCONNECT_RESPONSE,
|
||||
// abort operation
|
||||
AVRCP_COVER_ART_W4_ABORT_COMPLETE,
|
||||
} avrcp_cover_art_state_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t srm_value;
|
||||
uint8_t srmp_value;
|
||||
} avrcp_cover_art_obex_srm_t;
|
||||
|
||||
typedef enum {
|
||||
SRM_DISABLED,
|
||||
SRM_W4_CONFIRM,
|
||||
SRM_ENABLED_BUT_WAITING,
|
||||
SRM_ENABLED
|
||||
} avrcp_cover_art_srm_state_t;
|
||||
|
||||
typedef struct {
|
||||
btstack_linked_item_t item;
|
||||
|
||||
uint16_t cover_art_cid;
|
||||
uint16_t avrcp_cid;
|
||||
bd_addr_t addr;
|
||||
|
||||
btstack_packet_handler_t packet_handler;
|
||||
|
||||
uint8_t *ertm_buffer;
|
||||
uint32_t ertm_buffer_size;
|
||||
const l2cap_ertm_config_t * ertm_config;
|
||||
|
||||
uint16_t goep_cid;
|
||||
goep_client_t goep_client;
|
||||
|
||||
avrcp_cover_art_state_t state;
|
||||
|
||||
//
|
||||
bool flow_control_enabled;
|
||||
bool flow_next_triggered;
|
||||
bool flow_wait_for_user;
|
||||
|
||||
// obex parser
|
||||
bool obex_parser_waiting_for_response;
|
||||
obex_parser_t obex_parser;
|
||||
uint8_t obex_header_buffer[4];
|
||||
|
||||
// obex srm
|
||||
avrcp_cover_art_obex_srm_t obex_srm;
|
||||
avrcp_cover_art_srm_state_t srm_state;
|
||||
|
||||
// request
|
||||
const char * object_type;
|
||||
const char * image_handle;
|
||||
const char * image_descriptor;
|
||||
bool first_request;
|
||||
|
||||
} avrcp_cover_art_client_t;
|
||||
|
||||
/* API_START */
|
||||
|
||||
/**
|
||||
* @brief Set up AVRCP Cover Art client
|
||||
*/
|
||||
void avrcp_cover_art_client_init(void);
|
||||
|
||||
/**
|
||||
* @brief Connect to AVRCP Cover Art service on a remote device, emits AVRCP_SUBEVENT_COVER_ART_CONNECTION_ESTABLISHED with status
|
||||
* @param packet_handler
|
||||
* @param remote_addr
|
||||
* @param ertm_buffer
|
||||
* @param ertm_buffer_size
|
||||
* @param ertm_config
|
||||
* @param avrcp_cover_art_cid outgoing parameter, valid if status == ERROR_CODE_SUCCESS
|
||||
* @return status
|
||||
*/
|
||||
uint8_t
|
||||
avrcp_cover_art_client_connect(avrcp_cover_art_client_t *cover_art_client, btstack_packet_handler_t packet_handler,
|
||||
bd_addr_t remote_addr, uint8_t *ertm_buffer, uint32_t ertm_buffer_size,
|
||||
const l2cap_ertm_config_t *ertm_config, uint16_t *avrcp_cover_art_cid);
|
||||
|
||||
/**
|
||||
* @brief Request cover art thumbnail for cover with a given image handle retrieved via
|
||||
* - avrcp_controller_get_now_playing_info or
|
||||
* - avrcp_controller_get_element_attributes(... AVRCP_MEDIA_ATTR_DEFAULT_COVER_ART ...)
|
||||
* @param avrcp_cover_art_cid
|
||||
* @param image_handle
|
||||
* @return status
|
||||
*/
|
||||
uint8_t avrcp_cover_art_client_get_linked_thumbnail(uint16_t avrcp_cover_art_cid, const char * image_handle);
|
||||
|
||||
/**
|
||||
* @brief Request cover art image for given image handle retrieved via
|
||||
* - avrcp_controller_get_now_playing_info or
|
||||
* - avrcp_controller_get_element_attributes(... AVRCP_MEDIA_ATTR_DEFAULT_COVER_ART ...)
|
||||
* and given image descriptor
|
||||
* @param avrcp_cover_art_cid
|
||||
* @param image_handle
|
||||
* @param image_descriptor
|
||||
* @return status
|
||||
*/
|
||||
uint8_t avrcp_cover_art_client_get_image(uint16_t avrcp_cover_art_cid, const char * image_handle, const char * image_descriptor);
|
||||
|
||||
/**
|
||||
* @brief Request image properties for given image handle retrieved via
|
||||
* - avrcp_controller_get_now_playing_info or
|
||||
* - avrcp_controller_get_element_attributes(... AVRCP_MEDIA_ATTR_DEFAULT_COVER_ART ...)
|
||||
* @param avrcp_cover_art_cid
|
||||
* @param image_handle
|
||||
* @return status
|
||||
*/
|
||||
uint8_t avrcp_cover_art_client_get_image_properties(uint16_t avrcp_cover_art_cid, const char * image_handle);
|
||||
|
||||
/**
|
||||
* @brief Disconnect from AVRCP Cover Art service
|
||||
* @param avrcp_cover_art_cid
|
||||
* @return status
|
||||
*/
|
||||
uint8_t avrcp_cover_art_client_disconnect(uint16_t avrcp_cover_art_cid);
|
||||
|
||||
/**
|
||||
* @brief De-Init AVRCP Cover Art Client
|
||||
*/
|
||||
void avrcp_cover_art_client_deinit(void);
|
||||
|
||||
/* API_END */
|
||||
|
||||
#endif
|
||||
|
||||
#if defined __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // AVRCP_COVER_ART_CLIENT_H
|
Loading…
x
Reference in New Issue
Block a user