--- audio/avrcp.c | 215 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 158 insertions(+), 57 deletions(-) diff --git a/audio/avrcp.c b/audio/avrcp.c index 1babb94..3c1b752 100644 --- a/audio/avrcp.c +++ b/audio/avrcp.c @@ -64,6 +64,12 @@ #define E_PARAM_NOT_FOUND 0x02 #define E_INTERNAL 0x03 +/* Packet types */ +#define AVRCP_PACKET_TYPE_SINGLE 0x00 +#define AVRCP_PACKET_TYPE_START 0x01 +#define AVRCP_PACKET_TYPE_CONTINUING 0x02 +#define AVRCP_PACKET_TYPE_END 0x03 + /* PDU types for metadata transfer */ #define AVRCP_GET_CAPABILITIES 0x10 #define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 @@ -77,6 +83,7 @@ #define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 #define AVRCP_GET_PLAY_STATUS 0x30 #define AVRCP_REGISTER_NOTIFICATION 0x31 +#define AVRCP_REQUEST_CONTINUING 0x40 #define AVRCP_ABORT_CONTINUING 0x41 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */ @@ -399,75 +406,119 @@ int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data) } /* - * Copy media_info field to a buffer, intended to be used in a response to - * GetElementAttributes message. - * - * It assumes there's enough space in the buffer and on success it returns the - * size written. + * Write media attribute to buffer, taking care of writing the header when + * necessary, not writing more than @param maxlen and starting string from + * @param last_idx. * - * If @param id is not valid, -EINVAL is returned. If there's no space left on - * the buffer -ENOBUFS is returned. + * If there isn't enough space in the buffer even for the header, it returns + * -ENOBUFS. Otherwise it returns the size written to buffer and the position + * within the media attribute in @param last_idx. */ -static int player_get_media_attribute(struct avrcp_player *player, +static int player_write_media_attribute(struct avrcp_player *player, uint32_t id, uint8_t *buf, - uint16_t maxlen) + uint16_t maxlen, + unsigned int *last_idx) { - struct media_info_elem { + struct media_attribute_header { uint32_t id; uint16_t charset; uint16_t len; - uint8_t val[]; }; - struct media_info_elem *elem = (void *)buf; + uint8_t *val; + struct media_attribute_header *hdr; uint16_t len; char valstr[20]; void *value; - if (maxlen < sizeof(struct media_info_elem)) - return -ENOBUFS; + if (*last_idx == 0) { + /* + * Write header first, but bail out if even that + * doesn't fit in PDU. + */ + if (maxlen < sizeof(*hdr)) + return -ENOBUFS; - /* Subtract the size of elem header from the available space */ - maxlen -= sizeof(struct media_info_elem); + maxlen -= sizeof(*hdr); + hdr = (void *) buf; + hdr->id = htonl(id); + hdr->charset = htons(0x6A); /* Always use UTF-8 */ + val = ((uint8_t *) hdr) + sizeof(*hdr); + } else { + hdr = NULL; + val = buf; + } DBG("Get media attribute: %u", id); - if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || - id > AVRCP_MEDIA_ATTRIBUTE_LAST) - return -ENOENT; - value = player->cb->get_metadata(id, player->user_data); if (value == NULL) { - len = 0; - goto done; + hdr->len = 0; + return sizeof(*hdr); } switch (id) { - case AVRCP_MEDIA_ATTRIBUTE_TITLE: - case AVRCP_MEDIA_ATTRIBUTE_ARTIST: - case AVRCP_MEDIA_ATTRIBUTE_ALBUM: - case AVRCP_MEDIA_ATTRIBUTE_GENRE: - len = strlen((char *) value); - if (len > maxlen) - return -ENOBUFS; - memcpy(elem->val, value, len); - break; case AVRCP_MEDIA_ATTRIBUTE_TRACK: case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS: case AVRCP_MEDIA_ATTRIBUTE_DURATION: snprintf(valstr, 20, "%u", GPOINTER_TO_UINT(value)); - len = strlen(valstr); - if (len > maxlen) - return -ENOBUFS; - memcpy(elem->val, valstr, len); + value = valstr; break; } -done: - elem->id = htonl(id); - elem->charset = htons(0x6A); /* Always use UTF-8 */ - elem->len = htons(len); + len = strlen(value); + if (hdr != NULL) + hdr->len = htons(len); + + value = ((char *) value) + *last_idx; + len -= *last_idx; + + if (len > maxlen) { + *last_idx += maxlen; + len = maxlen; + } else { + *last_idx = 0; + } + + memcpy(val, value, len); + + return val - buf + len; +} - return sizeof(struct media_info_elem) + len; +static GList *player_fill_media_attribute(struct avrcp_player *player, + GList *attr_ids, uint8_t *buf, + uint16_t *pos, unsigned int *last_idx) +{ + GList *l; + + for (l = attr_ids; l != NULL; l = g_list_delete_link(l, l)) { + uint32_t attr = GPOINTER_TO_UINT(l->data); + int size; + + size = player_write_media_attribute(player, attr, &buf[*pos], + AVRCP_PDU_MTU - *pos, + last_idx); + if (size < 0) + break; + + *pos += size; + + if (*last_idx) + break; + } + + return l; +} + +static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids, + unsigned int last_idx) +{ + struct pending_pdu *pending = g_new(struct pending_pdu, 1); + + pending->pdu_id = pdu_id; + pending->attr_ids = attr_ids; + pending->last_idx = last_idx; + + return pending; } static gboolean player_abort_pending_pdu(struct avrcp_player *player) @@ -611,8 +662,8 @@ static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, uint64_t *identifier = (uint64_t *) &pdu->params[0]; uint16_t pos; uint8_t nattr; - int size; - GList *l, *attr_ids; + GList *attr_ids; + unsigned int last_idx; if (len < 9 || *identifier != 0) goto err; @@ -628,12 +679,20 @@ static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, * title must be returned if there's a track selected. */ attr_ids = player->cb->list_metadata(player->user_data); + len = g_list_length(attr_ids); } else { unsigned int i; uint32_t *attr = (uint32_t *) &pdu->params[9]; - for (i = 0, attr_ids = NULL; i < nattr; i++, attr++) { + for (i = 0, len = 0, attr_ids = NULL; i < nattr; i++, attr++) { uint32_t id = ntohl(bt_get_unaligned(attr)); + + /* Don't add invalid attributes */ + if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || + id > AVRCP_MEDIA_ATTRIBUTE_LAST) + continue; + + len++; attr_ids = g_list_prepend(attr_ids, GUINT_TO_POINTER(id)); } @@ -641,24 +700,21 @@ static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, attr_ids = g_list_reverse(attr_ids); } - for (l = attr_ids, len = 0, pos = 1; l != NULL; l = l->next) { - uint32_t attr = GPOINTER_TO_UINT(l->data); + if (!len) + goto err; - size = player_get_media_attribute(player, attr, - &pdu->params[pos], - AVRCP_PDU_MTU - pos); + player_abort_pending_pdu(player); + pos = 1; + last_idx = 0; + attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params, + &pos, &last_idx); - if (size >= 0) { - len++; - pos += size; - } + if (attr_ids != NULL) { + player->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, + last_idx); + pdu->packet_type = AVRCP_PACKET_TYPE_START; } - g_list_free(attr_ids); - - if (!len) - goto err; - pdu->params[0] = len; pdu->params_len = htons(pos); @@ -889,6 +945,46 @@ err: return AVC_CTYPE_REJECTED; } +static uint8_t avrcp_handle_request_continuing(struct avrcp_player *player, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + struct pending_pdu *pending; + + if (len != 1 || player->pending_pdu == NULL) + goto err; + + pending = player->pending_pdu; + + if (pending->pdu_id != pdu->params[0]) + goto err; + + + len = 0; + pending->attr_ids = player_fill_media_attribute(player, + pending->attr_ids, + pdu->params, &len, + &pending->last_idx); + pdu->pdu_id = pending->pdu_id; + + if (pending->attr_ids == NULL) { + g_free(player->pending_pdu); + player->pending_pdu = NULL; + pdu->packet_type = AVRCP_PACKET_TYPE_END; + } else { + pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING; + } + + pdu->params_len = htons(len); + + return AVC_CTYPE_STABLE; +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + static uint8_t avrcp_handle_abort_continuing(struct avrcp_player *player, struct avrcp_header *pdu, uint8_t transaction) @@ -947,8 +1043,11 @@ static struct pdu_handler { avrcp_handle_get_play_status }, { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, avrcp_handle_register_notification }, + { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL, + avrcp_handle_request_continuing }, { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL, avrcp_handle_abort_continuing }, + { }, }; @@ -997,6 +1096,8 @@ static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, *code = handler->func(player, pdu, transaction); if (*code != AVC_CTYPE_REJECTED && + pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES && + pdu->pdu_id != AVRCP_REQUEST_CONTINUING && pdu->pdu_id != AVRCP_ABORT_CONTINUING) player_abort_pending_pdu(player); -- 1.7.7 -- To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html