avrcp: add avrcp_cover_art_client

This commit is contained in:
Matthias Ringwald 2023-04-21 15:03:07 +02:00
parent 6bee431c2a
commit ac72606302
4 changed files with 787 additions and 0 deletions

View File

@ -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"

View File

@ -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 \

View 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 */

View 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