First 2 patches are the same as sent previously to the mailing list, but now
rebased on top of the recent code move.
Last patch just adds VOL_UP and VOL_DOWN OPs so we can handle those passthrough
commands.
Lucas De Marchi (3):
avrcp: limit avrcp packet size to the MTU of AV/C
avrcp: implement pdu continuation request and abort
avrcp: handle volume up/down passthroughs as TG
audio/avctp.c | 4 +-
audio/avctp.h | 3 +
audio/avrcp.c | 310 +++++++++++++++++++++++++++++++++++++++++++++------------
3 files changed, 253 insertions(+), 64 deletions(-)
--
1.7.6.1
Hi guys...
-----Original Message-----
From: Lucas De Marchi
Sent: Tuesday, September 27, 2011 5:15 AM
To: Luiz Augusto von Dentz
Cc: [email protected]
Subject: Re: [PATCH 3/3] avrcp: handle volume up/down passthroughs as TG
Hi Luiz,
On Fri, Sep 23, 2011 at 4:48 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
> Hi Lucas,
>
> On Thu, Sep 22, 2011 at 9:02 PM, Lucas De Marchi
> <[email protected]> wrote:
>> Hi Luiz,
>>
>> On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
>> <[email protected]> wrote:
>>> Hi Lucas,
>>>
>>> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
>>> <[email protected]> wrote:
>>>> ---
>>>> audio/avctp.c | 2 ++
>>>> 1 files changed, 2 insertions(+), 0 deletions(-)
>>>>
>>>> diff --git a/audio/avctp.c b/audio/avctp.c
>>>> index e9b8e40..59fbed9 100644
>>>> --- a/audio/avctp.c
>>>> +++ b/audio/avctp.c
>>>> @@ -155,6 +155,8 @@ static struct {
>>>> { "BACKWARD", BACKWARD_OP,
>>>> KEY_PREVIOUSSONG },
>>>> { "REWIND", REWIND_OP, KEY_REWIND },
>>>> { "FAST FORWARD", FAST_FORWARD_OP,
>>>> KEY_FASTFORWARD },
>>>> + { "VOLUME UP", VOL_UP_OP, KEY_VOLUMEUP },
>>>> + { "VOLUME DOWN", VOL_DOWN_OP,
>>>> KEY_VOLUMEDOWN },
>>>> { NULL }
>>>> };
>>>
>>> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
>>> white paper:
>>>
>>> Recommendation 16:
>>> If volume is changed on the RD, the RD should not send an AVRCP volume
>>> command to the MP device.
>>> Motivation 16:
>>> Sending an AVRCP volume command to the MP may cause the MP to send
>>> again an AVRCP volume
>>> command to the RD device which could lead to an endless loop of AVRCP
>>> volume commands.
>>
>> So this recommendation says that if we are the rendering device (and
>> therefore
>> the CT) and the volume was changed on the rendering device, we should not
>> send
>> the AVRCP volume command. In BlueZ parlance means we are on CT role and
>> org.bluez.Control.{VolumeUp(), VolumeDown()} should not be called, right?
>
> I don't think MP and RD refer to CT or TG AVRCP roles, so a
> phone/tablet/pc would be consider a MP (source/encoder) and
> headsets/carkits/speakers RD (sink/decoder) and yes we should probably
> deprecate VolumeUp and VolumeDown because they cannot be synchronized
> properly and use absolute volume control if supported by the remote
> device, Im also planning to move volume control per transport so the
> endpoints can do the volume control since they are the ones
> generating/consuming the data.
What I was not convinced of is that if we have two devices running
BlueZ (one as CT and the other as TG), CT would send the volume
up/down commands and TG would always ignore.
It just doesn't seem right CT not being able to talk to TG when both
use BlueZ. However if you are going to deprecate the calls for CT in
favor of something better, go for it.
+++++
I was out of town on business, and too busy to check this list. Notice that
even my apology is in-posted...so no flames this time, please ;-)
I think it is a bad idea to just deprecate this function.
Not every system uses Bluetooth speakers, and in any event, the CT talks to
the TG and not directly to the speakers. So, the TG should accept the CT
command and do whatever it needs to to change the volume as the CT
commanded.
If the TG is playing through BT speakers, it could pass the command through
(maybe as CT to TG/RD). However, that would be an application function, a
layer above what we should be doing in BlueZ, IMO. It would be a mistake to
get too clever and over-encapsulate everything into BlueZ, locking down its
uses to a severely limited set of use-cases.
+++++
Lucas De Marchi
Hey,
On Wed, Sep 21, 2011 at 7:51 PM, Lucas De Marchi
<[email protected]> wrote:
>
> GetElementAttributes is the only one that can possibly overflow the MTU
> of AVC. When this command is received, if the response is larger than
> the MTU split the messages and queue them for later processing.
> ---
>
> v2: add missing call to cancel_pending_pdus()
Ignore this. I'll send a v3 later on top of other changes I have queued.
Lucas De Marchi
Hi Luiz,
On Fri, Sep 23, 2011 at 4:48 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
> Hi Lucas,
>
> On Thu, Sep 22, 2011 at 9:02 PM, Lucas De Marchi
> <[email protected]> wrote:
>> Hi Luiz,
>>
>> On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
>> <[email protected]> wrote:
>>> Hi Lucas,
>>>
>>> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
>>> <[email protected]> wrote:
>>>> ---
>>>> ?audio/avctp.c | ? ?2 ++
>>>> ?1 files changed, 2 insertions(+), 0 deletions(-)
>>>>
>>>> diff --git a/audio/avctp.c b/audio/avctp.c
>>>> index e9b8e40..59fbed9 100644
>>>> --- a/audio/avctp.c
>>>> +++ b/audio/avctp.c
>>>> @@ -155,6 +155,8 @@ static struct {
>>>> ? ? ? ?{ "BACKWARD", ? ? ? ? ? BACKWARD_OP, ? ? ? ? ? ?KEY_PREVIOUSSONG },
>>>> ? ? ? ?{ "REWIND", ? ? ? ? ? ? REWIND_OP, ? ? ? ? ? ? ?KEY_REWIND },
>>>> ? ? ? ?{ "FAST FORWARD", ? ? ? FAST_FORWARD_OP, ? ? ? ?KEY_FASTFORWARD },
>>>> + ? ? ? { "VOLUME UP", ? ? ? ? ?VOL_UP_OP, ? ? ? ? ? ? ?KEY_VOLUMEUP },
>>>> + ? ? ? { "VOLUME DOWN", ? ? ? ?VOL_DOWN_OP, ? ? ? ? ? ?KEY_VOLUMEDOWN },
>>>> ? ? ? ?{ NULL }
>>>> ?};
>>>
>>> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
>>> white paper:
>>>
>>> Recommendation 16:
>>> If volume is changed on the RD, the RD should not send an AVRCP volume
>>> command to the MP device.
>>> Motivation 16:
>>> Sending an AVRCP volume command to the MP may cause the MP to send
>>> again an AVRCP volume
>>> command to the RD device which could lead to an endless loop of AVRCP
>>> volume commands.
>>
>> So this recommendation says that if we are the rendering device (and therefore
>> the CT) and the volume was changed on the rendering device, we should not send
>> the AVRCP volume command. In BlueZ parlance means we are on CT role and
>> org.bluez.Control.{VolumeUp(), VolumeDown()} should not be called, right?
>
> I don't think MP and RD refer to CT or TG AVRCP roles, so a
> phone/tablet/pc would be consider a MP (source/encoder) and
> headsets/carkits/speakers RD (sink/decoder) and yes we should probably
> deprecate VolumeUp and VolumeDown because they cannot be synchronized
> properly and use absolute volume control if supported by the remote
> device, Im also planning to move volume control per transport so the
> endpoints can do the volume control since they are the ones
> generating/consuming the data.
What I was not convinced of is that if we have two devices running
BlueZ (one as CT and the other as TG), CT would send the volume
up/down commands and TG would always ignore.
It just doesn't seem right CT not being able to talk to TG when both
use BlueZ. However if you are going to deprecate the calls for CT in
favor of something better, go for it.
Lucas De Marchi
Hi Lucas,
On Thu, Sep 22, 2011 at 9:02 PM, Lucas De Marchi
<[email protected]> wrote:
> Hi Luiz,
>
> On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
> <[email protected]> wrote:
>> Hi Lucas,
>>
>> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
>> <[email protected]> wrote:
>>> ---
>>> audio/avctp.c | 2 ++
>>> 1 files changed, 2 insertions(+), 0 deletions(-)
>>>
>>> diff --git a/audio/avctp.c b/audio/avctp.c
>>> index e9b8e40..59fbed9 100644
>>> --- a/audio/avctp.c
>>> +++ b/audio/avctp.c
>>> @@ -155,6 +155,8 @@ static struct {
>>> { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
>>> { "REWIND", REWIND_OP, KEY_REWIND },
>>> { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
>>> + { "VOLUME UP", VOL_UP_OP, KEY_VOLUMEUP },
>>> + { "VOLUME DOWN", VOL_DOWN_OP, KEY_VOLUMEDOWN },
>>> { NULL }
>>> };
>>
>> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
>> white paper:
>>
>> Recommendation 16:
>> If volume is changed on the RD, the RD should not send an AVRCP volume
>> command to the MP device.
>> Motivation 16:
>> Sending an AVRCP volume command to the MP may cause the MP to send
>> again an AVRCP volume
>> command to the RD device which could lead to an endless loop of AVRCP
>> volume commands.
>
> So this recommendation says that if we are the rendering device (and therefore
> the CT) and the volume was changed on the rendering device, we should not send
> the AVRCP volume command. In BlueZ parlance means we are on CT role and
> org.bluez.Control.{VolumeUp(), VolumeDown()} should not be called, right?
I don't think MP and RD refer to CT or TG AVRCP roles, so a
phone/tablet/pc would be consider a MP (source/encoder) and
headsets/carkits/speakers RD (sink/decoder) and yes we should probably
deprecate VolumeUp and VolumeDown because they cannot be synchronized
properly and use absolute volume control if supported by the remote
device, Im also planning to move volume control per transport so the
endpoints can do the volume control since they are the ones
generating/consuming the data.
> Since handle_passthrough_command() is called when we *receive* a command, I
> think looking at recommendation 17 makes more sense, doesn't it?
>
> Recomentation 17: If a device receives an AVRCP volume command, it shall not
> send back an AVRCP volume command.
> Motivation 17: This will also ensure that endless loop does not happen with
> existing devices which do not comply with the recommendation.
Yep, with panel volume commands it impossible to synchronize the
volumes, so with AVRCP 1.0 the only thing that really works is to
increase/decrease volume of the stream itself, but that is also not
recommended see recommendation 27.
> So, we should not send back (through AVRCP) the volume command. However BlueZ
> *should* forward this command to the application to the application, otherwise
> the application will just not respond to the command.
Actually the solution that creates less problems is to just ignore the
volume commands all together, if we receive we cannot indicate to
applications because we don't what level the remote side is so it is
very likely it will be out of sync and we cannot send anything back
either.
>
> Currently, the following scenario doesn't work (WARNING: I'm not good at ASCII
> art :-) )
>
>
> Media Player
> ============
> PC
> running <---------------------------> Bluetooth speaker
> BlueZ
> ^ ↑
> | ↑
> | ↑ AVRCP_volume_up
> | ↑
> v ↑
> Remote
> Controller
>
Well this separate the speaker for the controller, but the normal case
is that they sit together so when the user presses volume up it
changes its speaker gain and there is nothing we can do about it, in
fact if we change the stream volume it will actually cause a double
volume change.
To summarize:
1. Local volume changes: stream volume level is changed without
sending any command
2. Remote volume changes: volume commands are ignored
> What do you think?
We need absolute volume control to make this work properly.
--
Luiz Augusto von Dentz
Hi Luiz,
On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
> Hi Lucas,
>
> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
> <[email protected]> wrote:
>> ---
>> audio/avctp.c | 2 ++
>> 1 files changed, 2 insertions(+), 0 deletions(-)
>>
>> diff --git a/audio/avctp.c b/audio/avctp.c
>> index e9b8e40..59fbed9 100644
>> --- a/audio/avctp.c
>> +++ b/audio/avctp.c
>> @@ -155,6 +155,8 @@ static struct {
>> { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
>> { "REWIND", REWIND_OP, KEY_REWIND },
>> { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
>> + { "VOLUME UP", VOL_UP_OP, KEY_VOLUMEUP },
>> + { "VOLUME DOWN", VOL_DOWN_OP, KEY_VOLUMEDOWN },
>> { NULL }
>> };
>
> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
> white paper:
>
> Recommendation 16:
> If volume is changed on the RD, the RD should not send an AVRCP volume
> command to the MP device.
> Motivation 16:
> Sending an AVRCP volume command to the MP may cause the MP to send
> again an AVRCP volume
> command to the RD device which could lead to an endless loop of AVRCP
> volume commands.
So this recommendation says that if we are the rendering device (and therefore
the CT) and the volume was changed on the rendering device, we should not send
the AVRCP volume command. In BlueZ parlance means we are on CT role and
org.bluez.Control.{VolumeUp(), VolumeDown()} should not be called, right?
Since handle_passthrough_command() is called when we *receive* a command, I
think looking at recommendation 17 makes more sense, doesn't it?
Recomentation 17: If a device receives an AVRCP volume command, it shall not
send back an AVRCP volume command.
Motivation 17: This will also ensure that endless loop does not happen with
existing devices which do not comply with the recommendation.
So, we should not send back (through AVRCP) the volume command. However BlueZ
*should* forward this command to the application to the application, otherwise
the application will just not respond to the command.
Currently, the following scenario doesn't work (WARNING: I'm not good at ASCII
art :-) )
Media Player
============
PC
running <---------------------------> Bluetooth speaker
BlueZ
^ ↑
| ↑
| ↑ AVRCP_volume_up
| ↑
v ↑
Remote
Controller
What do you think?
Lucas De Marchi
GetElementAttributes is the only one that can possibly overflow the MTU
of AVC. When this command is received, if the response is larger than
the MTU split the messages and queue them for later processing.
---
v2: add missing call to cancel_pending_pdus()
audio/avrcp.c | 283 ++++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 230 insertions(+), 53 deletions(-)
diff --git a/audio/avrcp.c b/audio/avrcp.c
index ba04a12..f23d294 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,8 @@
#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
/* Notification events */
#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED 0x01
@@ -201,6 +209,7 @@ struct media_player {
struct media_info mi;
GTimer *timer;
+ GQueue *pending_pdus;
unsigned int handler;
uint16_t registered_events;
uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
@@ -645,15 +654,17 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
* 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.
+ * If there isn't enough space in the buffer, the available space is filled,
+ * the remainder (both header and value) is allocated in @param remainder and
+ * its size is written to remainder_len.
*
- * If @param id is not valid, -EINVAL is returned. If there's no such media
- * attribute, -ENOENT is returned.
+ * On success, it returns the size written. Otherwise it returns -EINVAL if
+ * @param id is not valid or -ENOENT if there's no such media attribute.
*/
static int mp_get_media_attribute(struct media_player *mp,
- uint32_t id, uint8_t *buf,
- uint16_t maxlen)
+ uint32_t id, uint8_t *buf,
+ uint16_t maxlen, uint8_t **remainder,
+ int *remainder_len)
{
struct media_info_elem {
uint32_t id;
@@ -662,16 +673,16 @@ static int mp_get_media_attribute(struct media_player *mp,
uint8_t val[];
};
const struct media_info *mi = &mp->mi;
- struct media_info_elem *elem = (void *)buf;
+ struct media_info_elem *elem;
uint16_t len;
char valstr[20];
char *valp;
+ int ret;
- if (maxlen < sizeof(struct media_info_elem))
- return -ENOBUFS;
+ assert(remainder != NULL);
+ assert(remainder_len != NULL);
- /* Subtract the size of elem header from the available space */
- maxlen -= sizeof(struct media_info_elem);
+ DBG("id=%u buf=%p maxlen=%u", id, buf, maxlen);
switch (id) {
case MEDIA_INFO_TITLE:
@@ -720,22 +731,50 @@ static int mp_get_media_attribute(struct media_player *mp,
return -EINVAL;
}
- if (valp) {
+ if (valp)
len = strlen(valp);
+ else
+ len = 0;
+
+ if (maxlen < sizeof(struct media_info_elem)) {
+ /*
+ * There isn't space even for the header: put both header and
+ * value in remainder.
+ */
+ *remainder_len = sizeof(struct media_info_elem) + len;
+ elem = g_malloc(*remainder_len);
+ *remainder = (uint8_t *) elem;
- if (len > maxlen)
- return -ENOBUFS;
+ if (len)
+ memcpy(elem->val, valp, len);
- memcpy(elem->val, valp, len);
+ ret = 0;
} else {
- len = 0;
+ /* Put at least header on current PDU */
+ elem = (struct media_info_elem *) buf;
+ maxlen -= sizeof(struct media_info_elem);
+
+ if (maxlen < len) {
+ /* Split value between current PDU and remainder. */
+ memcpy(elem->val, valp, maxlen);
+ *remainder_len = len - maxlen;
+ *remainder = g_memdup(valp + maxlen, *remainder_len);
+ ret = sizeof(struct media_info_elem) + maxlen;
+ } else {
+ /* Header and value fit in current PDU */
+ if (len)
+ memcpy(elem->val, valp, len);
+
+ *remainder = NULL;
+ ret = sizeof(struct media_info_elem) + len;
+ }
}
elem->id = htonl(id);
elem->charset = htons(0x6A); /* Always use UTF-8 */
elem->len = htons(len);
- return sizeof(struct media_info_elem) + len;
+ return ret;
}
static void mp_set_attribute(struct media_player *mp,
@@ -890,64 +929,116 @@ err:
return AVC_CTYPE_REJECTED;
}
+static struct avrcp_header *avrcp_split_pdu(struct media_player *mp,
+ struct avrcp_header *last_pdu,
+ uint8_t *remainder,
+ int remainder_len,
+ uint16_t *pos)
+{
+ struct avrcp_header *pdu = last_pdu;
+ uint16_t len;
+
+ assert(remainder != NULL);
+ assert(pos != NULL);
+
+ if (!mp->pending_pdus)
+ mp->pending_pdus = g_queue_new();
+
+ for (len = *pos; remainder_len; remainder_len -= len) {
+ /* Close last pdu - keep host endiannes */
+ pdu->params_len = len;
+
+ /* Create a new PDU */
+ pdu = g_malloc(AVRCP_MTU);
+
+ memcpy(pdu, last_pdu, sizeof(struct avrcp_header));
+ g_queue_push_tail(mp->pending_pdus, pdu);
+
+ if (remainder_len > AVRCP_PDU_MTU)
+ len = AVRCP_PDU_MTU;
+ else
+ len = remainder_len;
+
+ memcpy(&pdu->params[0], remainder, len);
+ }
+
+ *pos = len;
+
+ return pdu;
+}
+
static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
- struct avrcp_header *pdu,
- uint8_t transaction)
+ struct avrcp_header *first_pdu,
+ uint8_t transaction)
{
- uint16_t len = ntohs(pdu->params_len);
+ struct avrcp_header *pdu = first_pdu;
uint64_t *identifier = (void *) &pdu->params[0];
+ uint32_t attr_ids[MEDIA_INFO_LAST];
+ uint16_t len = ntohs(pdu->params_len);
uint16_t pos;
uint8_t nattr;
- int size;
unsigned int i;
if (len < 8 || *identifier != 0)
goto err;
- len = 0;
- pos = 1; /* Keep track of current position in reponse */
nattr = pdu->params[8];
- if (!nattr) {
- /*
- * Return all available information, at least
- * title must be returned.
- */
- for (i = 1; i < MEDIA_INFO_LAST; i++) {
- size = mp_get_media_attribute(mp, i, &pdu->params[pos],
- AVRCP_PDU_MTU - pos);
-
- if (size > 0) {
- len++;
- pos += size;
- }
+ DBG("nattr %u", nattr);
+
+ /* Copy the requested attribute ids to our private vector */
+ if (nattr) {
+ uint32_t *attr = (uint32_t *) &pdu->params[9];
+ if (nattr > MEDIA_INFO_LAST) {
+ error("Got number of attributes %u > %u",
+ nattr, MEDIA_INFO_LAST);
+ nattr = MEDIA_INFO_LAST;
}
+
+ for (i = 0; attr < (uint32_t *)&pdu->params[9] +
+ nattr * sizeof(uint32_t);
+ attr++, i++)
+ attr_ids[i] = ntohl(*attr);
} else {
- uint32_t *attr_ids;
+ nattr = MEDIA_INFO_LAST - 1;
- attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
+ for (i = 1; i < MEDIA_INFO_LAST; i++)
+ attr_ids[i - 1] = i;
+ }
- for (i = 0; i < nattr; i++) {
- uint32_t attr = ntohl(attr_ids[i]);
+ for (i = 0, len = 0, pos = 1; i < nattr; i++) {
+ uint8_t *remainder;
+ int size, remainder_len;
- size = mp_get_media_attribute(mp, attr,
- &pdu->params[pos],
- AVRCP_PDU_MTU - pos);
+ size = mp_get_media_attribute(mp, attr_ids[i],
+ &pdu->params[pos],
+ AVRCP_PDU_MTU - pos,
+ &remainder, &remainder_len);
- if (size > 0) {
- len++;
- pos += size;
- }
- }
+ if (size < 0)
+ continue;
- g_free(attr_ids);
+ pos += size;
+ len++;
- if (!len)
- goto err;
+ if (remainder) {
+ pdu = avrcp_split_pdu(mp, pdu, remainder,
+ remainder_len, &pos);
+ g_free(remainder);
+ }
}
- pdu->params[0] = len;
- pdu->params_len = htons(pos);
+ if (!len)
+ goto err;
+
+ /* Close last pdu - keep host endiannes */
+ pdu->params_len = pos;
+
+ if (first_pdu != pdu)
+ first_pdu->packet_type = AVRCP_PACKET_TYPE_START;
+
+ first_pdu->params[0] = len;
+ first_pdu->params_len = htons(first_pdu->params_len);
return AVC_CTYPE_STABLE;
err:
@@ -1192,6 +1283,83 @@ err:
return AVC_CTYPE_REJECTED;
}
+static void cancel_pending_pdus(struct media_player *mp)
+{
+ struct avrcp_header *pdu;
+
+ if (!mp || !mp->pending_pdus)
+ return;
+
+ DBG("%u PDUs cancelled.", g_queue_get_length(mp->pending_pdus));
+
+ while ((pdu = g_queue_pop_head(mp->pending_pdus)))
+ g_free(pdu);
+
+ g_queue_free(mp->pending_pdus);
+ mp->pending_pdus = NULL;
+}
+
+static uint8_t avrcp_handle_request_continuing(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ struct avrcp_header *saved_pdu;
+ int len;
+
+ if (!mp || !mp->pending_pdus) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+ }
+
+ DBG("");
+
+ saved_pdu = g_queue_pop_head(mp->pending_pdus);
+
+ len = saved_pdu->params_len;
+ memcpy(pdu, saved_pdu, sizeof(struct avrcp_header) + len);
+ pdu->params_len = htons(len);
+
+ g_free(saved_pdu);
+
+ if (g_queue_is_empty(mp->pending_pdus)) {
+ pdu->packet_type = AVRCP_PACKET_TYPE_END;
+ g_queue_free(mp->pending_pdus);
+ mp->pending_pdus = NULL;
+ } else {
+ pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING;
+ }
+
+ return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_abort_continuing(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ struct avrcp_header *saved_pdu;
+
+ if (len < 1 || mp->pending_pdus == NULL)
+ goto err;
+
+ saved_pdu = g_queue_peek_head(mp->pending_pdus);
+
+ if (saved_pdu->pdu_id != pdu->params[0])
+ goto err;
+
+ cancel_pending_pdus(mp);
+ pdu->params_len = 0;
+
+ return AVC_CTYPE_STABLE;
+
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+}
+
+
static struct pdu_handler {
uint8_t pdu_id;
uint8_t code;
@@ -1223,6 +1391,10 @@ 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 },
{ },
};
@@ -1263,6 +1435,11 @@ static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
goto err_metadata;
}
+ /* We have to cancel pending pdus before processing new messages */
+ if (pdu->pdu_id != AVRCP_REQUEST_CONTINUING
+ && pdu->pdu_id != AVRCP_ABORT_CONTINUING)
+ cancel_pending_pdus(mp);
+
if (!handler->func) {
pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
--
1.7.6.1
Hi Luis
On Wed, Sep 21, 2011 at 7:14 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
> Hi Lucas,
>
> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
> <[email protected]> wrote:
>> GetElementAttributes is the only one that can possibly overflow the MTU
>> of AVC. When this command is received, if the response is larger than
>> the MTU split the messages and queue them for later processing.
>> ---
>> ?audio/avrcp.c | ?282 ++++++++++++++++++++++++++++++++++++++++++++++-----------
>> ?1 files changed, 229 insertions(+), 53 deletions(-)
>>
>> diff --git a/audio/avrcp.c b/audio/avrcp.c
>> index ba04a12..8fccede 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,8 @@
>> ?#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
>>
>> ?/* Notification events */
>> ?#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED ? ? ? ? ? ?0x01
>> @@ -201,6 +209,7 @@ struct media_player {
>>
>> ? ? ? ?struct media_info mi;
>> ? ? ? ?GTimer *timer;
>> + ? ? ? GQueue *pending_pdus;
>> ? ? ? ?unsigned int handler;
>> ? ? ? ?uint16_t registered_events;
>> ? ? ? ?uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
>> @@ -645,15 +654,17 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
>> ?* 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.
>> + * If there isn't enough space in the buffer, the available space is filled,
>> + * the remainder (both header and value) is allocated in @param remainder and
>> + * its size is written to remainder_len.
>> ?*
>> - * If @param id is not valid, -EINVAL is returned. If there's no such media
>> - * attribute, -ENOENT is returned.
>> + * On success, it returns the size written. Otherwise it returns -EINVAL if
>> + * @param id is not valid or -ENOENT if there's no such media attribute.
>> ?*/
>> ?static int mp_get_media_attribute(struct media_player *mp,
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf,
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t maxlen)
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t maxlen, uint8_t **remainder,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int *remainder_len)
>> ?{
>> ? ? ? ?struct media_info_elem {
>> ? ? ? ? ? ? ? ?uint32_t id;
>> @@ -662,16 +673,16 @@ static int mp_get_media_attribute(struct media_player *mp,
>> ? ? ? ? ? ? ? ?uint8_t val[];
>> ? ? ? ?};
>> ? ? ? ?const struct media_info *mi = &mp->mi;
>> - ? ? ? struct media_info_elem *elem = (void *)buf;
>> + ? ? ? struct media_info_elem *elem;
>> ? ? ? ?uint16_t len;
>> ? ? ? ?char valstr[20];
>> ? ? ? ?char *valp;
>> + ? ? ? int ret;
>>
>> - ? ? ? if (maxlen < sizeof(struct media_info_elem))
>> - ? ? ? ? ? ? ? return -ENOBUFS;
>> + ? ? ? assert(remainder != NULL);
>> + ? ? ? assert(remainder_len != NULL);
>
> I guess you probably assert(remainder_len > 0) since remainder_len is
> int, well perhaps size_t fits better here.
No... I'm asserting I was not dumb to call this function with
remainder_len being a NULL pointer. If *remainder_len == 0 we're
better dealing with it instead of aborting the entire process.
>
>> - ? ? ? /* Subtract the size of elem header from the available space */
>> - ? ? ? maxlen -= sizeof(struct media_info_elem);
>> + ? ? ? DBG("id=%u buf=%p maxlen=%u", id, buf, maxlen);
>>
>> ? ? ? ?switch (id) {
>> ? ? ? ?case MEDIA_INFO_TITLE:
>> @@ -720,22 +731,50 @@ static int mp_get_media_attribute(struct media_player *mp,
>> ? ? ? ? ? ? ? ?return -EINVAL;
>> ? ? ? ?}
>>
>> - ? ? ? if (valp) {
>> + ? ? ? if (valp)
>> ? ? ? ? ? ? ? ?len = strlen(valp);
>> + ? ? ? else
>> + ? ? ? ? ? ? ? len = 0;
>> +
>> + ? ? ? if (maxlen < sizeof(struct media_info_elem)) {
>> + ? ? ? ? ? ? ? /*
>> + ? ? ? ? ? ? ? ?* There isn't space even for the header: put both header and
>> + ? ? ? ? ? ? ? ?* value in remainder.
>> + ? ? ? ? ? ? ? ?*/
>> + ? ? ? ? ? ? ? *remainder_len = sizeof(struct media_info_elem) + len;
>> + ? ? ? ? ? ? ? elem = g_malloc(*remainder_len);
>> + ? ? ? ? ? ? ? *remainder = (uint8_t *) elem;
>>
>> - ? ? ? ? ? ? ? if (len > maxlen)
>> - ? ? ? ? ? ? ? ? ? ? ? return -ENOBUFS;
>> + ? ? ? ? ? ? ? if (len)
>> + ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
>>
>> - ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
>> + ? ? ? ? ? ? ? ret = 0;
>> ? ? ? ?} else {
>> - ? ? ? ? ? ? ? len = 0;
>> + ? ? ? ? ? ? ? /* Put at least header on current PDU */
>> + ? ? ? ? ? ? ? elem = (struct media_info_elem *) buf;
>> + ? ? ? ? ? ? ? maxlen -= sizeof(struct media_info_elem);
>> +
>> + ? ? ? ? ? ? ? if (maxlen < len) {
>> + ? ? ? ? ? ? ? ? ? ? ? /* Split value between current PDU and remainder. */
>> + ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, maxlen);
>> + ? ? ? ? ? ? ? ? ? ? ? *remainder_len = len - maxlen;
>> + ? ? ? ? ? ? ? ? ? ? ? *remainder = g_memdup(valp + maxlen, *remainder_len);
>> + ? ? ? ? ? ? ? ? ? ? ? ret = sizeof(struct media_info_elem) + maxlen;
>> + ? ? ? ? ? ? ? } else {
>> + ? ? ? ? ? ? ? ? ? ? ? /* Header and value fit in current PDU */
>> + ? ? ? ? ? ? ? ? ? ? ? if (len)
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
>> +
>> + ? ? ? ? ? ? ? ? ? ? ? *remainder = NULL;
>> + ? ? ? ? ? ? ? ? ? ? ? ret = sizeof(struct media_info_elem) + len;
>> + ? ? ? ? ? ? ? }
>> ? ? ? ?}
>
> memcpy, hmm bad sign.
Notice that I removed a memcpy, too. More on this below.
>
>> ? ? ? ?elem->id = htonl(id);
>> ? ? ? ?elem->charset = htons(0x6A); /* Always use UTF-8 */
>> ? ? ? ?elem->len = htons(len);
>>
>> - ? ? ? return sizeof(struct media_info_elem) + len;
>> + ? ? ? return ret;
>> ?}
>>
>> ?static void mp_set_attribute(struct media_player *mp,
>> @@ -890,64 +929,116 @@ err:
>> ? ? ? ?return AVC_CTYPE_REJECTED;
>> ?}
>>
>> +static struct avrcp_header *avrcp_split_pdu(struct media_player *mp,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *last_pdu,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t *remainder,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int remainder_len,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t *pos)
>> +{
>> + ? ? ? struct avrcp_header *pdu = last_pdu;
>> + ? ? ? uint16_t len;
>> +
>> + ? ? ? assert(remainder != NULL);
>> + ? ? ? assert(pos != NULL);
>> +
>> + ? ? ? if (!mp->pending_pdus)
>> + ? ? ? ? ? ? ? mp->pending_pdus = g_queue_new();
>> +
>> + ? ? ? for (len = *pos; remainder_len; remainder_len -= len) {
>> + ? ? ? ? ? ? ? /* Close last pdu - keep host endiannes */
>> + ? ? ? ? ? ? ? pdu->params_len = len;
>> +
>> + ? ? ? ? ? ? ? /* Create a new PDU */
>> + ? ? ? ? ? ? ? pdu = g_malloc(AVRCP_MTU);
>> +
>> + ? ? ? ? ? ? ? memcpy(pdu, last_pdu, sizeof(struct avrcp_header));
>> + ? ? ? ? ? ? ? g_queue_push_tail(mp->pending_pdus, pdu);
>> +
>> + ? ? ? ? ? ? ? if (remainder_len > AVRCP_PDU_MTU)
>> + ? ? ? ? ? ? ? ? ? ? ? len = AVRCP_PDU_MTU;
>> + ? ? ? ? ? ? ? else
>> + ? ? ? ? ? ? ? ? ? ? ? len = remainder_len;
>> +
>> + ? ? ? ? ? ? ? memcpy(&pdu->params[0], remainder, len);
>> + ? ? ? }
>
> Second memcpy, this does not look good.
>
>> +
>> + ? ? ? *pos = len;
>> +
>> + ? ? ? return pdu;
>> +}
>> +
>> ?static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *first_pdu,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
>> ?{
>> - ? ? ? uint16_t len = ntohs(pdu->params_len);
>> + ? ? ? struct avrcp_header *pdu = first_pdu;
>> ? ? ? ?uint64_t *identifier = (void *) &pdu->params[0];
>> + ? ? ? uint32_t attr_ids[MEDIA_INFO_LAST];
>> + ? ? ? uint16_t len = ntohs(pdu->params_len);
>> ? ? ? ?uint16_t pos;
>> ? ? ? ?uint8_t nattr;
>> - ? ? ? int size;
>> ? ? ? ?unsigned int i;
>>
>> ? ? ? ?if (len < 8 || *identifier != 0)
>> ? ? ? ? ? ? ? ?goto err;
>>
>> - ? ? ? len = 0;
>> - ? ? ? pos = 1; /* Keep track of current position in reponse */
>> ? ? ? ?nattr = pdu->params[8];
>>
>> - ? ? ? if (!nattr) {
>> - ? ? ? ? ? ? ? /*
>> - ? ? ? ? ? ? ? ?* Return all available information, at least
>> - ? ? ? ? ? ? ? ?* title must be returned.
>> - ? ? ? ? ? ? ? ?*/
>> - ? ? ? ? ? ? ? for (i = 1; i < MEDIA_INFO_LAST; i++) {
>> - ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, i, &pdu->params[pos],
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
>> -
>> - ? ? ? ? ? ? ? ? ? ? ? if (size > 0) {
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? len++;
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pos += size;
>> - ? ? ? ? ? ? ? ? ? ? ? }
>> + ? ? ? DBG("nattr %u", nattr);
>> +
>> + ? ? ? /* Copy the requested attribute ids to our private vector */
>> + ? ? ? if (nattr) {
>> + ? ? ? ? ? ? ? uint32_t *attr = (uint32_t *) &pdu->params[9];
>> + ? ? ? ? ? ? ? if (nattr > MEDIA_INFO_LAST) {
>> + ? ? ? ? ? ? ? ? ? ? ? error("Got number of attributes %u > %u",
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nattr, MEDIA_INFO_LAST);
>> + ? ? ? ? ? ? ? ? ? ? ? nattr = MEDIA_INFO_LAST;
>> ? ? ? ? ? ? ? ?}
>> +
>> + ? ? ? ? ? ? ? for (i = 0; attr < (uint32_t *)&pdu->params[9] +
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nattr * sizeof(uint32_t);
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attr++, i++)
>> + ? ? ? ? ? ? ? ? ? ? ? attr_ids[i] = ntohl(*attr);
>> ? ? ? ?} else {
>> - ? ? ? ? ? ? ? uint32_t *attr_ids;
>> + ? ? ? ? ? ? ? nattr = MEDIA_INFO_LAST - 1;
>>
>> - ? ? ? ? ? ? ? attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
>> + ? ? ? ? ? ? ? for (i = 1; i < MEDIA_INFO_LAST; i++)
>> + ? ? ? ? ? ? ? ? ? ? ? attr_ids[i - 1] = i;
>> + ? ? ? }
>>
>> - ? ? ? ? ? ? ? for (i = 0; i < nattr; i++) {
>> - ? ? ? ? ? ? ? ? ? ? ? uint32_t attr = ntohl(attr_ids[i]);
>> + ? ? ? for (i = 0, len = 0, pos = 1; i < nattr; i++) {
>> + ? ? ? ? ? ? ? uint8_t *remainder;
>> + ? ? ? ? ? ? ? int size, remainder_len;
>>
>> - ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, attr,
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos],
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
>> + ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, attr_ids[i],
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos],
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &remainder, &remainder_len);
>>
>> - ? ? ? ? ? ? ? ? ? ? ? if (size > 0) {
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? len++;
>> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pos += size;
>> - ? ? ? ? ? ? ? ? ? ? ? }
>> - ? ? ? ? ? ? ? }
>> + ? ? ? ? ? ? ? if (size < 0)
>> + ? ? ? ? ? ? ? ? ? ? ? continue;
>>
>> - ? ? ? ? ? ? ? g_free(attr_ids);
>> + ? ? ? ? ? ? ? pos += size;
>> + ? ? ? ? ? ? ? len++;
>>
>> - ? ? ? ? ? ? ? if (!len)
>> - ? ? ? ? ? ? ? ? ? ? ? goto err;
>> + ? ? ? ? ? ? ? if (remainder) {
>> + ? ? ? ? ? ? ? ? ? ? ? pdu = avrcp_split_pdu(mp, pdu, remainder,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? remainder_len, &pos);
>> + ? ? ? ? ? ? ? ? ? ? ? g_free(remainder);
>> + ? ? ? ? ? ? ? }
>> ? ? ? ?}
>>
>> - ? ? ? pdu->params[0] = len;
>> - ? ? ? pdu->params_len = htons(pos);
>> + ? ? ? if (!len)
>> + ? ? ? ? ? ? ? goto err;
>> +
>> + ? ? ? /* Close last pdu - keep host endiannes */
>> + ? ? ? pdu->params_len = pos;
>> +
>> + ? ? ? if (first_pdu != pdu)
>> + ? ? ? ? ? ? ? first_pdu->packet_type = AVRCP_PACKET_TYPE_START;
>> +
>> + ? ? ? first_pdu->params[0] = len;
>> + ? ? ? first_pdu->params_len = htons(first_pdu->params_len);
>>
>> ? ? ? ?return AVC_CTYPE_STABLE;
>> ?err:
>> @@ -1192,6 +1283,82 @@ err:
>> ? ? ? ?return AVC_CTYPE_REJECTED;
>> ?}
>>
>> +static void cancel_pending_pdus(struct media_player *mp)
>> +{
>> + ? ? ? struct avrcp_header *pdu;
>> +
>> + ? ? ? if (!mp || !mp->pending_pdus)
>> + ? ? ? ? ? ? ? return;
>> +
>> + ? ? ? DBG("%u PDUs cancelled.", g_queue_get_length(mp->pending_pdus));
>> +
>> + ? ? ? while ((pdu = g_queue_pop_head(mp->pending_pdus)))
>> + ? ? ? ? ? ? ? g_free(pdu);
>> +
>> + ? ? ? g_queue_free(mp->pending_pdus);
>> + ? ? ? mp->pending_pdus = NULL;
>> +}
>> +
>> +static uint8_t avrcp_handle_request_continuing(struct media_player *mp,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
>> +{
>> + ? ? ? struct avrcp_header *saved_pdu;
>> + ? ? ? int len;
>> +
>> + ? ? ? if (!mp || !mp->pending_pdus) {
>> + ? ? ? ? ? ? ? pdu->params_len = htons(1);
>> + ? ? ? ? ? ? ? pdu->params[0] = E_INVALID_PARAM;
>> + ? ? ? ? ? ? ? return AVC_CTYPE_REJECTED;
>> + ? ? ? }
>> +
>> + ? ? ? DBG("");
>> +
>> + ? ? ? saved_pdu = g_queue_pop_head(mp->pending_pdus);
>> +
>> + ? ? ? len = saved_pdu->params_len;
>> + ? ? ? memcpy(pdu, saved_pdu, sizeof(struct avrcp_header) + len);
>> + ? ? ? pdu->params_len = htons(len);
>
> Third memcpy, I guess we need to fix this somehow.
>
>> + ? ? ? g_free(saved_pdu);
>> +
>> + ? ? ? if (g_queue_is_empty(mp->pending_pdus)) {
>> + ? ? ? ? ? ? ? pdu->packet_type = AVRCP_PACKET_TYPE_END;
>> + ? ? ? ? ? ? ? g_queue_free(mp->pending_pdus);
>> + ? ? ? ? ? ? ? mp->pending_pdus = NULL;
>> + ? ? ? } else {
>> + ? ? ? ? ? ? ? pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING;
>> + ? ? ? }
>> +
>> + ? ? ? return AVC_CTYPE_STABLE;
>> +}
>> +
>> +static uint8_t avrcp_handle_abort_continuing(struct media_player *mp,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
>> +{
>> + ? ? ? uint16_t len = ntohs(pdu->params_len);
>> + ? ? ? struct avrcp_header *saved_pdu;
>> +
>> + ? ? ? if (len < 1 || mp->pending_pdus == NULL)
>> + ? ? ? ? ? ? ? goto err;
>> +
>> + ? ? ? saved_pdu = g_queue_peek_head(mp->pending_pdus);
>> +
>> + ? ? ? if (saved_pdu->pdu_id != pdu->params[0])
>> + ? ? ? ? ? ? ? goto err;
>
> There is something missing here, don't you have to free the queue after abort?
Yes, I forgot it when rebased. I'll fix that.
> I wonder if you have tried a more simplistic approach without parsing
> and storing all the pdus in a queue since the beginning, we could
> store just store the list of pending attributes to be sent and
> generate the pdu only when requested/needed, that would probably
> eliminate most of the memcpy.
Yes, but it's not so simple as it looks at first:
1) The attributes may split in the middle. So we would need to store
the attribute, and the position within it that might be already sent.
Actually splitting on attribute boundaries will happen only in the
rare case where the avrcp header doesn't fit in the current pdu. Heck,
the attribute can even span to more than 2 PDUs.
2) We would need to save the list of requested attributes too, since
it's not true that we're always responding with all attributes of the
track. This would mean to parse the requested attributes as we are
doing now, but also validating (CT might ask for invalid attributes)
and storing them somewhere.
3) The attributes may change while responding to RequestContinuing
commands. What if the user called ChangeTrack() before we finish the
pending requests? Since (i) we can't simply continue, (ii) the CT
might still want to know the track that was playing and (iii) it's
CT's role to abort the previous request, the only solution that I see
would be to save the media_info struct too.
While implementing this I considered this
RequestContinuing/AbortContinuing will have very few usage since 512
bytes is probably enough most of the time (this may not be true if we
consider we are transferring UTF-8 strings that might span more bytes
per character). So we only start allocating space (and memcpy'ing) if
we reach this situation. We could remove 1 memcpy if we share the
header among the PDUs but it didn't feel appealing for such a small
amount of bytes 'cause it would complicate things when continuing.
thanks
Lucas De Marchi
Hi Lucas,
On Wed, Sep 21, 2011 at 3:15 PM, Lucas De Marchi
<[email protected]> wrote:
> Hi Luis,
>
> On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
> <[email protected]> wrote:
>> Hi Lucas,
>>
>> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
>> <[email protected]> wrote:
>>> ---
>>> ?audio/avctp.c | ? ?2 ++
>>> ?1 files changed, 2 insertions(+), 0 deletions(-)
>>>
>>> diff --git a/audio/avctp.c b/audio/avctp.c
>>> index e9b8e40..59fbed9 100644
>>> --- a/audio/avctp.c
>>> +++ b/audio/avctp.c
>>> @@ -155,6 +155,8 @@ static struct {
>>> ? ? ? ?{ "BACKWARD", ? ? ? ? ? BACKWARD_OP, ? ? ? ? ? ?KEY_PREVIOUSSONG },
>>> ? ? ? ?{ "REWIND", ? ? ? ? ? ? REWIND_OP, ? ? ? ? ? ? ?KEY_REWIND },
>>> ? ? ? ?{ "FAST FORWARD", ? ? ? FAST_FORWARD_OP, ? ? ? ?KEY_FASTFORWARD },
>>> + ? ? ? { "VOLUME UP", ? ? ? ? ?VOL_UP_OP, ? ? ? ? ? ? ?KEY_VOLUMEUP },
>>> + ? ? ? { "VOLUME DOWN", ? ? ? ?VOL_DOWN_OP, ? ? ? ? ? ?KEY_VOLUMEDOWN },
>>> ? ? ? ?{ NULL }
>>> ?};
>>
>> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
>> white paper:
>>
>> Recommendation 16:
>> If volume is changed on the RD, the RD should not send an AVRCP volume
>> command to the MP device.
>> Motivation 16:
>> Sending an AVRCP volume command to the MP may cause the MP to send
>> again an AVRCP volume
>> command to the RD device which could lead to an endless loop of AVRCP
>> volume commands.
>>
>
> Humnn... ok
>
>> We don't support those because it is very likely that it would create
>> the endless loop with those devices as we can only act as a MP we just
>> ignore, for RD this would probably be useful so the handling of the
>> keys need to take into account the role of the device.
>
> You mean, it's simply a matter of checking we are only TG before
> calling send_key?
Yep, or maybe have a different table for TG and CT.
--
Luiz Augusto von Dentz
Hi Luis,
On Wed, Sep 21, 2011 at 7:33 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
> Hi Lucas,
>
> On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
> <[email protected]> wrote:
>> ---
>> ?audio/avctp.c | ? ?2 ++
>> ?1 files changed, 2 insertions(+), 0 deletions(-)
>>
>> diff --git a/audio/avctp.c b/audio/avctp.c
>> index e9b8e40..59fbed9 100644
>> --- a/audio/avctp.c
>> +++ b/audio/avctp.c
>> @@ -155,6 +155,8 @@ static struct {
>> ? ? ? ?{ "BACKWARD", ? ? ? ? ? BACKWARD_OP, ? ? ? ? ? ?KEY_PREVIOUSSONG },
>> ? ? ? ?{ "REWIND", ? ? ? ? ? ? REWIND_OP, ? ? ? ? ? ? ?KEY_REWIND },
>> ? ? ? ?{ "FAST FORWARD", ? ? ? FAST_FORWARD_OP, ? ? ? ?KEY_FASTFORWARD },
>> + ? ? ? { "VOLUME UP", ? ? ? ? ?VOL_UP_OP, ? ? ? ? ? ? ?KEY_VOLUMEUP },
>> + ? ? ? { "VOLUME DOWN", ? ? ? ?VOL_DOWN_OP, ? ? ? ? ? ?KEY_VOLUMEDOWN },
>> ? ? ? ?{ NULL }
>> ?};
>
> Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
> white paper:
>
> Recommendation 16:
> If volume is changed on the RD, the RD should not send an AVRCP volume
> command to the MP device.
> Motivation 16:
> Sending an AVRCP volume command to the MP may cause the MP to send
> again an AVRCP volume
> command to the RD device which could lead to an endless loop of AVRCP
> volume commands.
>
Humnn... ok
> We don't support those because it is very likely that it would create
> the endless loop with those devices as we can only act as a MP we just
> ignore, for RD this would probably be useful so the handling of the
> keys need to take into account the role of the device.
You mean, it's simply a matter of checking we are only TG before
calling send_key?
Lucas De Marchi
Hi Lucas,
On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
<[email protected]> wrote:
> ---
> ?audio/avctp.c | ? ?2 ++
> ?1 files changed, 2 insertions(+), 0 deletions(-)
>
> diff --git a/audio/avctp.c b/audio/avctp.c
> index e9b8e40..59fbed9 100644
> --- a/audio/avctp.c
> +++ b/audio/avctp.c
> @@ -155,6 +155,8 @@ static struct {
> ? ? ? ?{ "BACKWARD", ? ? ? ? ? BACKWARD_OP, ? ? ? ? ? ?KEY_PREVIOUSSONG },
> ? ? ? ?{ "REWIND", ? ? ? ? ? ? REWIND_OP, ? ? ? ? ? ? ?KEY_REWIND },
> ? ? ? ?{ "FAST FORWARD", ? ? ? FAST_FORWARD_OP, ? ? ? ?KEY_FASTFORWARD },
> + ? ? ? { "VOLUME UP", ? ? ? ? ?VOL_UP_OP, ? ? ? ? ? ? ?KEY_VOLUMEUP },
> + ? ? ? { "VOLUME DOWN", ? ? ? ?VOL_DOWN_OP, ? ? ? ? ? ?KEY_VOLUMEDOWN },
> ? ? ? ?{ NULL }
> ?};
Have a look at the SIMULTANEOUS USE OF HFP, A2DP, AND AVRCP PROFILES
white paper:
Recommendation 16:
If volume is changed on the RD, the RD should not send an AVRCP volume
command to the MP device.
Motivation 16:
Sending an AVRCP volume command to the MP may cause the MP to send
again an AVRCP volume
command to the RD device which could lead to an endless loop of AVRCP
volume commands.
We don't support those because it is very likely that it would create
the endless loop with those devices as we can only act as a MP we just
ignore, for RD this would probably be useful so the handling of the
keys need to take into account the role of the device.
--
Luiz Augusto von Dentz
Hi Lucas,
On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
<[email protected]> wrote:
> GetElementAttributes is the only one that can possibly overflow the MTU
> of AVC. When this command is received, if the response is larger than
> the MTU split the messages and queue them for later processing.
> ---
> ?audio/avrcp.c | ?282 ++++++++++++++++++++++++++++++++++++++++++++++-----------
> ?1 files changed, 229 insertions(+), 53 deletions(-)
>
> diff --git a/audio/avrcp.c b/audio/avrcp.c
> index ba04a12..8fccede 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,8 @@
> ?#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
>
> ?/* Notification events */
> ?#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED ? ? ? ? ? ?0x01
> @@ -201,6 +209,7 @@ struct media_player {
>
> ? ? ? ?struct media_info mi;
> ? ? ? ?GTimer *timer;
> + ? ? ? GQueue *pending_pdus;
> ? ? ? ?unsigned int handler;
> ? ? ? ?uint16_t registered_events;
> ? ? ? ?uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
> @@ -645,15 +654,17 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
> ?* 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.
> + * If there isn't enough space in the buffer, the available space is filled,
> + * the remainder (both header and value) is allocated in @param remainder and
> + * its size is written to remainder_len.
> ?*
> - * If @param id is not valid, -EINVAL is returned. If there's no such media
> - * attribute, -ENOENT is returned.
> + * On success, it returns the size written. Otherwise it returns -EINVAL if
> + * @param id is not valid or -ENOENT if there's no such media attribute.
> ?*/
> ?static int mp_get_media_attribute(struct media_player *mp,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t maxlen)
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t maxlen, uint8_t **remainder,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int *remainder_len)
> ?{
> ? ? ? ?struct media_info_elem {
> ? ? ? ? ? ? ? ?uint32_t id;
> @@ -662,16 +673,16 @@ static int mp_get_media_attribute(struct media_player *mp,
> ? ? ? ? ? ? ? ?uint8_t val[];
> ? ? ? ?};
> ? ? ? ?const struct media_info *mi = &mp->mi;
> - ? ? ? struct media_info_elem *elem = (void *)buf;
> + ? ? ? struct media_info_elem *elem;
> ? ? ? ?uint16_t len;
> ? ? ? ?char valstr[20];
> ? ? ? ?char *valp;
> + ? ? ? int ret;
>
> - ? ? ? if (maxlen < sizeof(struct media_info_elem))
> - ? ? ? ? ? ? ? return -ENOBUFS;
> + ? ? ? assert(remainder != NULL);
> + ? ? ? assert(remainder_len != NULL);
I guess you probably assert(remainder_len > 0) since remainder_len is
int, well perhaps size_t fits better here.
> - ? ? ? /* Subtract the size of elem header from the available space */
> - ? ? ? maxlen -= sizeof(struct media_info_elem);
> + ? ? ? DBG("id=%u buf=%p maxlen=%u", id, buf, maxlen);
>
> ? ? ? ?switch (id) {
> ? ? ? ?case MEDIA_INFO_TITLE:
> @@ -720,22 +731,50 @@ static int mp_get_media_attribute(struct media_player *mp,
> ? ? ? ? ? ? ? ?return -EINVAL;
> ? ? ? ?}
>
> - ? ? ? if (valp) {
> + ? ? ? if (valp)
> ? ? ? ? ? ? ? ?len = strlen(valp);
> + ? ? ? else
> + ? ? ? ? ? ? ? len = 0;
> +
> + ? ? ? if (maxlen < sizeof(struct media_info_elem)) {
> + ? ? ? ? ? ? ? /*
> + ? ? ? ? ? ? ? ?* There isn't space even for the header: put both header and
> + ? ? ? ? ? ? ? ?* value in remainder.
> + ? ? ? ? ? ? ? ?*/
> + ? ? ? ? ? ? ? *remainder_len = sizeof(struct media_info_elem) + len;
> + ? ? ? ? ? ? ? elem = g_malloc(*remainder_len);
> + ? ? ? ? ? ? ? *remainder = (uint8_t *) elem;
>
> - ? ? ? ? ? ? ? if (len > maxlen)
> - ? ? ? ? ? ? ? ? ? ? ? return -ENOBUFS;
> + ? ? ? ? ? ? ? if (len)
> + ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
>
> - ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
> + ? ? ? ? ? ? ? ret = 0;
> ? ? ? ?} else {
> - ? ? ? ? ? ? ? len = 0;
> + ? ? ? ? ? ? ? /* Put at least header on current PDU */
> + ? ? ? ? ? ? ? elem = (struct media_info_elem *) buf;
> + ? ? ? ? ? ? ? maxlen -= sizeof(struct media_info_elem);
> +
> + ? ? ? ? ? ? ? if (maxlen < len) {
> + ? ? ? ? ? ? ? ? ? ? ? /* Split value between current PDU and remainder. */
> + ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, maxlen);
> + ? ? ? ? ? ? ? ? ? ? ? *remainder_len = len - maxlen;
> + ? ? ? ? ? ? ? ? ? ? ? *remainder = g_memdup(valp + maxlen, *remainder_len);
> + ? ? ? ? ? ? ? ? ? ? ? ret = sizeof(struct media_info_elem) + maxlen;
> + ? ? ? ? ? ? ? } else {
> + ? ? ? ? ? ? ? ? ? ? ? /* Header and value fit in current PDU */
> + ? ? ? ? ? ? ? ? ? ? ? if (len)
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
> +
> + ? ? ? ? ? ? ? ? ? ? ? *remainder = NULL;
> + ? ? ? ? ? ? ? ? ? ? ? ret = sizeof(struct media_info_elem) + len;
> + ? ? ? ? ? ? ? }
> ? ? ? ?}
memcpy, hmm bad sign.
> ? ? ? ?elem->id = htonl(id);
> ? ? ? ?elem->charset = htons(0x6A); /* Always use UTF-8 */
> ? ? ? ?elem->len = htons(len);
>
> - ? ? ? return sizeof(struct media_info_elem) + len;
> + ? ? ? return ret;
> ?}
>
> ?static void mp_set_attribute(struct media_player *mp,
> @@ -890,64 +929,116 @@ err:
> ? ? ? ?return AVC_CTYPE_REJECTED;
> ?}
>
> +static struct avrcp_header *avrcp_split_pdu(struct media_player *mp,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *last_pdu,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t *remainder,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int remainder_len,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t *pos)
> +{
> + ? ? ? struct avrcp_header *pdu = last_pdu;
> + ? ? ? uint16_t len;
> +
> + ? ? ? assert(remainder != NULL);
> + ? ? ? assert(pos != NULL);
> +
> + ? ? ? if (!mp->pending_pdus)
> + ? ? ? ? ? ? ? mp->pending_pdus = g_queue_new();
> +
> + ? ? ? for (len = *pos; remainder_len; remainder_len -= len) {
> + ? ? ? ? ? ? ? /* Close last pdu - keep host endiannes */
> + ? ? ? ? ? ? ? pdu->params_len = len;
> +
> + ? ? ? ? ? ? ? /* Create a new PDU */
> + ? ? ? ? ? ? ? pdu = g_malloc(AVRCP_MTU);
> +
> + ? ? ? ? ? ? ? memcpy(pdu, last_pdu, sizeof(struct avrcp_header));
> + ? ? ? ? ? ? ? g_queue_push_tail(mp->pending_pdus, pdu);
> +
> + ? ? ? ? ? ? ? if (remainder_len > AVRCP_PDU_MTU)
> + ? ? ? ? ? ? ? ? ? ? ? len = AVRCP_PDU_MTU;
> + ? ? ? ? ? ? ? else
> + ? ? ? ? ? ? ? ? ? ? ? len = remainder_len;
> +
> + ? ? ? ? ? ? ? memcpy(&pdu->params[0], remainder, len);
> + ? ? ? }
Second memcpy, this does not look good.
> +
> + ? ? ? *pos = len;
> +
> + ? ? ? return pdu;
> +}
> +
> ?static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *first_pdu,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
> ?{
> - ? ? ? uint16_t len = ntohs(pdu->params_len);
> + ? ? ? struct avrcp_header *pdu = first_pdu;
> ? ? ? ?uint64_t *identifier = (void *) &pdu->params[0];
> + ? ? ? uint32_t attr_ids[MEDIA_INFO_LAST];
> + ? ? ? uint16_t len = ntohs(pdu->params_len);
> ? ? ? ?uint16_t pos;
> ? ? ? ?uint8_t nattr;
> - ? ? ? int size;
> ? ? ? ?unsigned int i;
>
> ? ? ? ?if (len < 8 || *identifier != 0)
> ? ? ? ? ? ? ? ?goto err;
>
> - ? ? ? len = 0;
> - ? ? ? pos = 1; /* Keep track of current position in reponse */
> ? ? ? ?nattr = pdu->params[8];
>
> - ? ? ? if (!nattr) {
> - ? ? ? ? ? ? ? /*
> - ? ? ? ? ? ? ? ?* Return all available information, at least
> - ? ? ? ? ? ? ? ?* title must be returned.
> - ? ? ? ? ? ? ? ?*/
> - ? ? ? ? ? ? ? for (i = 1; i < MEDIA_INFO_LAST; i++) {
> - ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, i, &pdu->params[pos],
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
> -
> - ? ? ? ? ? ? ? ? ? ? ? if (size > 0) {
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? len++;
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pos += size;
> - ? ? ? ? ? ? ? ? ? ? ? }
> + ? ? ? DBG("nattr %u", nattr);
> +
> + ? ? ? /* Copy the requested attribute ids to our private vector */
> + ? ? ? if (nattr) {
> + ? ? ? ? ? ? ? uint32_t *attr = (uint32_t *) &pdu->params[9];
> + ? ? ? ? ? ? ? if (nattr > MEDIA_INFO_LAST) {
> + ? ? ? ? ? ? ? ? ? ? ? error("Got number of attributes %u > %u",
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nattr, MEDIA_INFO_LAST);
> + ? ? ? ? ? ? ? ? ? ? ? nattr = MEDIA_INFO_LAST;
> ? ? ? ? ? ? ? ?}
> +
> + ? ? ? ? ? ? ? for (i = 0; attr < (uint32_t *)&pdu->params[9] +
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? nattr * sizeof(uint32_t);
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attr++, i++)
> + ? ? ? ? ? ? ? ? ? ? ? attr_ids[i] = ntohl(*attr);
> ? ? ? ?} else {
> - ? ? ? ? ? ? ? uint32_t *attr_ids;
> + ? ? ? ? ? ? ? nattr = MEDIA_INFO_LAST - 1;
>
> - ? ? ? ? ? ? ? attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
> + ? ? ? ? ? ? ? for (i = 1; i < MEDIA_INFO_LAST; i++)
> + ? ? ? ? ? ? ? ? ? ? ? attr_ids[i - 1] = i;
> + ? ? ? }
>
> - ? ? ? ? ? ? ? for (i = 0; i < nattr; i++) {
> - ? ? ? ? ? ? ? ? ? ? ? uint32_t attr = ntohl(attr_ids[i]);
> + ? ? ? for (i = 0, len = 0, pos = 1; i < nattr; i++) {
> + ? ? ? ? ? ? ? uint8_t *remainder;
> + ? ? ? ? ? ? ? int size, remainder_len;
>
> - ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, attr,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos],
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
> + ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, attr_ids[i],
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos],
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &remainder, &remainder_len);
>
> - ? ? ? ? ? ? ? ? ? ? ? if (size > 0) {
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? len++;
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pos += size;
> - ? ? ? ? ? ? ? ? ? ? ? }
> - ? ? ? ? ? ? ? }
> + ? ? ? ? ? ? ? if (size < 0)
> + ? ? ? ? ? ? ? ? ? ? ? continue;
>
> - ? ? ? ? ? ? ? g_free(attr_ids);
> + ? ? ? ? ? ? ? pos += size;
> + ? ? ? ? ? ? ? len++;
>
> - ? ? ? ? ? ? ? if (!len)
> - ? ? ? ? ? ? ? ? ? ? ? goto err;
> + ? ? ? ? ? ? ? if (remainder) {
> + ? ? ? ? ? ? ? ? ? ? ? pdu = avrcp_split_pdu(mp, pdu, remainder,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? remainder_len, &pos);
> + ? ? ? ? ? ? ? ? ? ? ? g_free(remainder);
> + ? ? ? ? ? ? ? }
> ? ? ? ?}
>
> - ? ? ? pdu->params[0] = len;
> - ? ? ? pdu->params_len = htons(pos);
> + ? ? ? if (!len)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? /* Close last pdu - keep host endiannes */
> + ? ? ? pdu->params_len = pos;
> +
> + ? ? ? if (first_pdu != pdu)
> + ? ? ? ? ? ? ? first_pdu->packet_type = AVRCP_PACKET_TYPE_START;
> +
> + ? ? ? first_pdu->params[0] = len;
> + ? ? ? first_pdu->params_len = htons(first_pdu->params_len);
>
> ? ? ? ?return AVC_CTYPE_STABLE;
> ?err:
> @@ -1192,6 +1283,82 @@ err:
> ? ? ? ?return AVC_CTYPE_REJECTED;
> ?}
>
> +static void cancel_pending_pdus(struct media_player *mp)
> +{
> + ? ? ? struct avrcp_header *pdu;
> +
> + ? ? ? if (!mp || !mp->pending_pdus)
> + ? ? ? ? ? ? ? return;
> +
> + ? ? ? DBG("%u PDUs cancelled.", g_queue_get_length(mp->pending_pdus));
> +
> + ? ? ? while ((pdu = g_queue_pop_head(mp->pending_pdus)))
> + ? ? ? ? ? ? ? g_free(pdu);
> +
> + ? ? ? g_queue_free(mp->pending_pdus);
> + ? ? ? mp->pending_pdus = NULL;
> +}
> +
> +static uint8_t avrcp_handle_request_continuing(struct media_player *mp,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
> +{
> + ? ? ? struct avrcp_header *saved_pdu;
> + ? ? ? int len;
> +
> + ? ? ? if (!mp || !mp->pending_pdus) {
> + ? ? ? ? ? ? ? pdu->params_len = htons(1);
> + ? ? ? ? ? ? ? pdu->params[0] = E_INVALID_PARAM;
> + ? ? ? ? ? ? ? return AVC_CTYPE_REJECTED;
> + ? ? ? }
> +
> + ? ? ? DBG("");
> +
> + ? ? ? saved_pdu = g_queue_pop_head(mp->pending_pdus);
> +
> + ? ? ? len = saved_pdu->params_len;
> + ? ? ? memcpy(pdu, saved_pdu, sizeof(struct avrcp_header) + len);
> + ? ? ? pdu->params_len = htons(len);
Third memcpy, I guess we need to fix this somehow.
> + ? ? ? g_free(saved_pdu);
> +
> + ? ? ? if (g_queue_is_empty(mp->pending_pdus)) {
> + ? ? ? ? ? ? ? pdu->packet_type = AVRCP_PACKET_TYPE_END;
> + ? ? ? ? ? ? ? g_queue_free(mp->pending_pdus);
> + ? ? ? ? ? ? ? mp->pending_pdus = NULL;
> + ? ? ? } else {
> + ? ? ? ? ? ? ? pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING;
> + ? ? ? }
> +
> + ? ? ? return AVC_CTYPE_STABLE;
> +}
> +
> +static uint8_t avrcp_handle_abort_continuing(struct media_player *mp,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? struct avrcp_header *pdu,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint8_t transaction)
> +{
> + ? ? ? uint16_t len = ntohs(pdu->params_len);
> + ? ? ? struct avrcp_header *saved_pdu;
> +
> + ? ? ? if (len < 1 || mp->pending_pdus == NULL)
> + ? ? ? ? ? ? ? goto err;
> +
> + ? ? ? saved_pdu = g_queue_peek_head(mp->pending_pdus);
> +
> + ? ? ? if (saved_pdu->pdu_id != pdu->params[0])
> + ? ? ? ? ? ? ? goto err;
There is something missing here, don't you have to free the queue after abort?
> + ? ? ? pdu->params_len = 0;
> +
> + ? ? ? return AVC_CTYPE_STABLE;
> +
> +err:
> + ? ? ? pdu->params_len = htons(1);
> + ? ? ? pdu->params[0] = E_INVALID_PARAM;
> + ? ? ? return AVC_CTYPE_REJECTED;
> +}
> +
> +
> ?static struct pdu_handler {
> ? ? ? ?uint8_t pdu_id;
> ? ? ? ?uint8_t code;
> @@ -1223,6 +1390,10 @@ 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 },
> ? ? ? ? ? ? ? ?{ },
> ?};
>
> @@ -1263,6 +1434,11 @@ static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
> ? ? ? ? ? ? ? ?goto err_metadata;
> ? ? ? ?}
>
> + ? ? ? /* We have to cancel pending pdus before processing new messages */
> + ? ? ? if (pdu->pdu_id != AVRCP_REQUEST_CONTINUING
> + ? ? ? ? ? ? ? ? ? ? ? && pdu->pdu_id != AVRCP_ABORT_CONTINUING)
> + ? ? ? ? ? ? ? cancel_pending_pdus(mp);
> +
> ? ? ? ?if (!handler->func) {
> ? ? ? ? ? ? ? ?pdu->params[0] = E_INVALID_PARAM;
> ? ? ? ? ? ? ? ?goto err_metadata;
> --
> 1.7.6.1
I wonder if you have tried a more simplistic approach without parsing
and storing all the pdus in a queue since the beginning, we could
store just store the list of pending attributes to be sent and
generate the pdu only when requested/needed, that would probably
eliminate most of the memcpy.
--
Luiz Augusto von Dentz
Hi Lucas,
On Tue, Sep 20, 2011 at 9:31 PM, Lucas De Marchi
<[email protected]> wrote:
> AVRCP is an extension of AV/C spec which has a limit of 512 bytes. The
> only place where it can exceed this value is in the response to
> GetElementAttributes command.
>
> Now we simply don't add the attributes that would overflow the available
> buffer space.
> ---
> ?audio/avctp.c | ? ?2 -
> ?audio/avctp.h | ? ?3 ++
> ?audio/avrcp.c | ? 58 +++++++++++++++++++++++++++++++++-----------------------
> ?3 files changed, 37 insertions(+), 26 deletions(-)
>
> diff --git a/audio/avctp.c b/audio/avctp.c
> index 2800a16..e9b8e40 100644
> --- a/audio/avctp.c
> +++ b/audio/avctp.c
> @@ -84,7 +84,6 @@ struct avc_header {
> ? ? ? ?uint8_t subunit_type:5;
> ? ? ? ?uint8_t opcode;
> ?} __attribute__ ((packed));
> -#define AVC_HEADER_LENGTH 3
>
> ?#elif __BYTE_ORDER == __BIG_ENDIAN
>
> @@ -104,7 +103,6 @@ struct avc_header {
> ? ? ? ?uint8_t subunit_id:3;
> ? ? ? ?uint8_t opcode;
> ?} __attribute__ ((packed));
> -#define AVC_HEADER_LENGTH 3
>
> ?#else
> ?#error "Unknown byte order"
> diff --git a/audio/avctp.h b/audio/avctp.h
> index 2dab8fa..9727485 100644
> --- a/audio/avctp.h
> +++ b/audio/avctp.h
> @@ -24,6 +24,9 @@
>
> ?#define AVCTP_PSM 23
>
> +#define AVC_MTU 512
> +#define AVC_HEADER_LENGTH 3
> +
> ?/* ctype entries */
> ?#define AVC_CTYPE_CONTROL ? ? ? ? ? ? ?0x0
> ?#define AVC_CTYPE_STATUS ? ? ? ? ? ? ? 0x1
> diff --git a/audio/avrcp.c b/audio/avrcp.c
> index f962946..ba04a12 100644
> --- a/audio/avrcp.c
> +++ b/audio/avrcp.c
> @@ -173,6 +173,9 @@ struct avrcp_header {
> ?#error "Unknown byte order"
> ?#endif
>
> +#define AVRCP_MTU ? ? ?(AVC_MTU - AVC_HEADER_LENGTH)
> +#define AVRCP_PDU_MTU ?(AVRCP_MTU - AVRCP_HEADER_LENGTH)
> +
> ?struct avrcp_server {
> ? ? ? ?bdaddr_t src;
> ? ? ? ?uint32_t tg_record_id;
> @@ -649,7 +652,8 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
> ?* attribute, -ENOENT is returned.
> ?*/
> ?static int mp_get_media_attribute(struct media_player *mp,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf)
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t id, uint8_t *buf,
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint16_t maxlen)
> ?{
> ? ? ? ?struct media_info_elem {
> ? ? ? ? ? ? ? ?uint32_t id;
> @@ -661,67 +665,72 @@ static int mp_get_media_attribute(struct media_player *mp,
> ? ? ? ?struct media_info_elem *elem = (void *)buf;
> ? ? ? ?uint16_t len;
> ? ? ? ?char valstr[20];
> + ? ? ? char *valp;
> +
> + ? ? ? if (maxlen < sizeof(struct media_info_elem))
> + ? ? ? ? ? ? ? return -ENOBUFS;
> +
> + ? ? ? /* Subtract the size of elem header from the available space */
> + ? ? ? maxlen -= sizeof(struct media_info_elem);
>
> ? ? ? ?switch (id) {
> ? ? ? ?case MEDIA_INFO_TITLE:
> - ? ? ? ? ? ? ? if (mi->title) {
> - ? ? ? ? ? ? ? ? ? ? ? len = strlen(mi->title);
> - ? ? ? ? ? ? ? ? ? ? ? memcpy(elem->val, mi->title, len);
> - ? ? ? ? ? ? ? } else {
> - ? ? ? ? ? ? ? ? ? ? ? len = 0;
> - ? ? ? ? ? ? ? }
> -
> + ? ? ? ? ? ? ? valp = mi->title;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?case MEDIA_INFO_ARTIST:
> ? ? ? ? ? ? ? ?if (mi->artist == NULL)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> - ? ? ? ? ? ? ? len = strlen(mi->artist);
> - ? ? ? ? ? ? ? memcpy(elem->val, mi->artist, len);
> + ? ? ? ? ? ? ? valp = mi->artist;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?case MEDIA_INFO_ALBUM:
> ? ? ? ? ? ? ? ?if (mi->album == NULL)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> - ? ? ? ? ? ? ? len = strlen(mi->album);
> - ? ? ? ? ? ? ? memcpy(elem->val, mi->album, len);
> + ? ? ? ? ? ? ? valp = mi->album;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?case MEDIA_INFO_GENRE:
> ? ? ? ? ? ? ? ?if (mi->genre == NULL)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> - ? ? ? ? ? ? ? len = strlen(mi->genre);
> - ? ? ? ? ? ? ? memcpy(elem->val, mi->genre, len);
> + ? ? ? ? ? ? ? valp = mi->genre;
> ? ? ? ? ? ? ? ?break;
> -
> ? ? ? ?case MEDIA_INFO_TRACK:
> ? ? ? ? ? ? ? ?if (!mi->track)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> ? ? ? ? ? ? ? ?snprintf(valstr, 20, "%u", mi->track);
> - ? ? ? ? ? ? ? len = strlen(valstr);
> - ? ? ? ? ? ? ? memcpy(elem->val, valstr, len);
> + ? ? ? ? ? ? ? valp = valstr;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?case MEDIA_INFO_N_TRACKS:
> ? ? ? ? ? ? ? ?if (!mi->ntracks)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> ? ? ? ? ? ? ? ?snprintf(valstr, 20, "%u", mi->ntracks);
> - ? ? ? ? ? ? ? len = strlen(valstr);
> - ? ? ? ? ? ? ? memcpy(elem->val, valstr, len);
> + ? ? ? ? ? ? ? valp = valstr;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?case MEDIA_INFO_PLAYING_TIME:
> ? ? ? ? ? ? ? ?if (mi->track_len == 0xFFFFFFFF)
> ? ? ? ? ? ? ? ? ? ? ? ?return -ENOENT;
>
> ? ? ? ? ? ? ? ?snprintf(valstr, 20, "%u", mi->track_len);
> - ? ? ? ? ? ? ? len = strlen(valstr);
> - ? ? ? ? ? ? ? memcpy(elem->val, valstr, len);
> + ? ? ? ? ? ? ? valp = valstr;
> ? ? ? ? ? ? ? ?break;
> ? ? ? ?default:
> ? ? ? ? ? ? ? ?return -EINVAL;
> ? ? ? ?}
>
> + ? ? ? if (valp) {
> + ? ? ? ? ? ? ? len = strlen(valp);
> +
> + ? ? ? ? ? ? ? if (len > maxlen)
> + ? ? ? ? ? ? ? ? ? ? ? return -ENOBUFS;
> +
> + ? ? ? ? ? ? ? memcpy(elem->val, valp, len);
> + ? ? ? } else {
> + ? ? ? ? ? ? ? len = 0;
> + ? ? ? }
> +
> ? ? ? ?elem->id = htonl(id);
> ? ? ? ?elem->charset = htons(0x6A); /* Always use UTF-8 */
> ? ? ? ?elem->len = htons(len);
> @@ -905,8 +914,8 @@ static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
> ? ? ? ? ? ? ? ? * title must be returned.
> ? ? ? ? ? ? ? ? */
> ? ? ? ? ? ? ? ?for (i = 1; i < MEDIA_INFO_LAST; i++) {
> - ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, i,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos]);
> + ? ? ? ? ? ? ? ? ? ? ? size = mp_get_media_attribute(mp, i, &pdu->params[pos],
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
>
> ? ? ? ? ? ? ? ? ? ? ? ?if (size > 0) {
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?len++;
> @@ -922,7 +931,8 @@ static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
> ? ? ? ? ? ? ? ? ? ? ? ?uint32_t attr = ntohl(attr_ids[i]);
>
> ? ? ? ? ? ? ? ? ? ? ? ?size = mp_get_media_attribute(mp, attr,
> - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos]);
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &pdu->params[pos],
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_PDU_MTU - pos);
>
> ? ? ? ? ? ? ? ? ? ? ? ?if (size > 0) {
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?len++;
> --
> 1.7.6.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
> the body of a message to [email protected]
> More majordomo info at ?http://vger.kernel.org/majordomo-info.html
>
Ack
--
Luiz Augusto von Dentz
---
audio/avctp.c | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/audio/avctp.c b/audio/avctp.c
index e9b8e40..59fbed9 100644
--- a/audio/avctp.c
+++ b/audio/avctp.c
@@ -155,6 +155,8 @@ static struct {
{ "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
{ "REWIND", REWIND_OP, KEY_REWIND },
{ "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
+ { "VOLUME UP", VOL_UP_OP, KEY_VOLUMEUP },
+ { "VOLUME DOWN", VOL_DOWN_OP, KEY_VOLUMEDOWN },
{ NULL }
};
--
1.7.6.1
GetElementAttributes is the only one that can possibly overflow the MTU
of AVC. When this command is received, if the response is larger than
the MTU split the messages and queue them for later processing.
---
audio/avrcp.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 229 insertions(+), 53 deletions(-)
diff --git a/audio/avrcp.c b/audio/avrcp.c
index ba04a12..8fccede 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,8 @@
#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
/* Notification events */
#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED 0x01
@@ -201,6 +209,7 @@ struct media_player {
struct media_info mi;
GTimer *timer;
+ GQueue *pending_pdus;
unsigned int handler;
uint16_t registered_events;
uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
@@ -645,15 +654,17 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
* 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.
+ * If there isn't enough space in the buffer, the available space is filled,
+ * the remainder (both header and value) is allocated in @param remainder and
+ * its size is written to remainder_len.
*
- * If @param id is not valid, -EINVAL is returned. If there's no such media
- * attribute, -ENOENT is returned.
+ * On success, it returns the size written. Otherwise it returns -EINVAL if
+ * @param id is not valid or -ENOENT if there's no such media attribute.
*/
static int mp_get_media_attribute(struct media_player *mp,
- uint32_t id, uint8_t *buf,
- uint16_t maxlen)
+ uint32_t id, uint8_t *buf,
+ uint16_t maxlen, uint8_t **remainder,
+ int *remainder_len)
{
struct media_info_elem {
uint32_t id;
@@ -662,16 +673,16 @@ static int mp_get_media_attribute(struct media_player *mp,
uint8_t val[];
};
const struct media_info *mi = &mp->mi;
- struct media_info_elem *elem = (void *)buf;
+ struct media_info_elem *elem;
uint16_t len;
char valstr[20];
char *valp;
+ int ret;
- if (maxlen < sizeof(struct media_info_elem))
- return -ENOBUFS;
+ assert(remainder != NULL);
+ assert(remainder_len != NULL);
- /* Subtract the size of elem header from the available space */
- maxlen -= sizeof(struct media_info_elem);
+ DBG("id=%u buf=%p maxlen=%u", id, buf, maxlen);
switch (id) {
case MEDIA_INFO_TITLE:
@@ -720,22 +731,50 @@ static int mp_get_media_attribute(struct media_player *mp,
return -EINVAL;
}
- if (valp) {
+ if (valp)
len = strlen(valp);
+ else
+ len = 0;
+
+ if (maxlen < sizeof(struct media_info_elem)) {
+ /*
+ * There isn't space even for the header: put both header and
+ * value in remainder.
+ */
+ *remainder_len = sizeof(struct media_info_elem) + len;
+ elem = g_malloc(*remainder_len);
+ *remainder = (uint8_t *) elem;
- if (len > maxlen)
- return -ENOBUFS;
+ if (len)
+ memcpy(elem->val, valp, len);
- memcpy(elem->val, valp, len);
+ ret = 0;
} else {
- len = 0;
+ /* Put at least header on current PDU */
+ elem = (struct media_info_elem *) buf;
+ maxlen -= sizeof(struct media_info_elem);
+
+ if (maxlen < len) {
+ /* Split value between current PDU and remainder. */
+ memcpy(elem->val, valp, maxlen);
+ *remainder_len = len - maxlen;
+ *remainder = g_memdup(valp + maxlen, *remainder_len);
+ ret = sizeof(struct media_info_elem) + maxlen;
+ } else {
+ /* Header and value fit in current PDU */
+ if (len)
+ memcpy(elem->val, valp, len);
+
+ *remainder = NULL;
+ ret = sizeof(struct media_info_elem) + len;
+ }
}
elem->id = htonl(id);
elem->charset = htons(0x6A); /* Always use UTF-8 */
elem->len = htons(len);
- return sizeof(struct media_info_elem) + len;
+ return ret;
}
static void mp_set_attribute(struct media_player *mp,
@@ -890,64 +929,116 @@ err:
return AVC_CTYPE_REJECTED;
}
+static struct avrcp_header *avrcp_split_pdu(struct media_player *mp,
+ struct avrcp_header *last_pdu,
+ uint8_t *remainder,
+ int remainder_len,
+ uint16_t *pos)
+{
+ struct avrcp_header *pdu = last_pdu;
+ uint16_t len;
+
+ assert(remainder != NULL);
+ assert(pos != NULL);
+
+ if (!mp->pending_pdus)
+ mp->pending_pdus = g_queue_new();
+
+ for (len = *pos; remainder_len; remainder_len -= len) {
+ /* Close last pdu - keep host endiannes */
+ pdu->params_len = len;
+
+ /* Create a new PDU */
+ pdu = g_malloc(AVRCP_MTU);
+
+ memcpy(pdu, last_pdu, sizeof(struct avrcp_header));
+ g_queue_push_tail(mp->pending_pdus, pdu);
+
+ if (remainder_len > AVRCP_PDU_MTU)
+ len = AVRCP_PDU_MTU;
+ else
+ len = remainder_len;
+
+ memcpy(&pdu->params[0], remainder, len);
+ }
+
+ *pos = len;
+
+ return pdu;
+}
+
static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
- struct avrcp_header *pdu,
- uint8_t transaction)
+ struct avrcp_header *first_pdu,
+ uint8_t transaction)
{
- uint16_t len = ntohs(pdu->params_len);
+ struct avrcp_header *pdu = first_pdu;
uint64_t *identifier = (void *) &pdu->params[0];
+ uint32_t attr_ids[MEDIA_INFO_LAST];
+ uint16_t len = ntohs(pdu->params_len);
uint16_t pos;
uint8_t nattr;
- int size;
unsigned int i;
if (len < 8 || *identifier != 0)
goto err;
- len = 0;
- pos = 1; /* Keep track of current position in reponse */
nattr = pdu->params[8];
- if (!nattr) {
- /*
- * Return all available information, at least
- * title must be returned.
- */
- for (i = 1; i < MEDIA_INFO_LAST; i++) {
- size = mp_get_media_attribute(mp, i, &pdu->params[pos],
- AVRCP_PDU_MTU - pos);
-
- if (size > 0) {
- len++;
- pos += size;
- }
+ DBG("nattr %u", nattr);
+
+ /* Copy the requested attribute ids to our private vector */
+ if (nattr) {
+ uint32_t *attr = (uint32_t *) &pdu->params[9];
+ if (nattr > MEDIA_INFO_LAST) {
+ error("Got number of attributes %u > %u",
+ nattr, MEDIA_INFO_LAST);
+ nattr = MEDIA_INFO_LAST;
}
+
+ for (i = 0; attr < (uint32_t *)&pdu->params[9] +
+ nattr * sizeof(uint32_t);
+ attr++, i++)
+ attr_ids[i] = ntohl(*attr);
} else {
- uint32_t *attr_ids;
+ nattr = MEDIA_INFO_LAST - 1;
- attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
+ for (i = 1; i < MEDIA_INFO_LAST; i++)
+ attr_ids[i - 1] = i;
+ }
- for (i = 0; i < nattr; i++) {
- uint32_t attr = ntohl(attr_ids[i]);
+ for (i = 0, len = 0, pos = 1; i < nattr; i++) {
+ uint8_t *remainder;
+ int size, remainder_len;
- size = mp_get_media_attribute(mp, attr,
- &pdu->params[pos],
- AVRCP_PDU_MTU - pos);
+ size = mp_get_media_attribute(mp, attr_ids[i],
+ &pdu->params[pos],
+ AVRCP_PDU_MTU - pos,
+ &remainder, &remainder_len);
- if (size > 0) {
- len++;
- pos += size;
- }
- }
+ if (size < 0)
+ continue;
- g_free(attr_ids);
+ pos += size;
+ len++;
- if (!len)
- goto err;
+ if (remainder) {
+ pdu = avrcp_split_pdu(mp, pdu, remainder,
+ remainder_len, &pos);
+ g_free(remainder);
+ }
}
- pdu->params[0] = len;
- pdu->params_len = htons(pos);
+ if (!len)
+ goto err;
+
+ /* Close last pdu - keep host endiannes */
+ pdu->params_len = pos;
+
+ if (first_pdu != pdu)
+ first_pdu->packet_type = AVRCP_PACKET_TYPE_START;
+
+ first_pdu->params[0] = len;
+ first_pdu->params_len = htons(first_pdu->params_len);
return AVC_CTYPE_STABLE;
err:
@@ -1192,6 +1283,82 @@ err:
return AVC_CTYPE_REJECTED;
}
+static void cancel_pending_pdus(struct media_player *mp)
+{
+ struct avrcp_header *pdu;
+
+ if (!mp || !mp->pending_pdus)
+ return;
+
+ DBG("%u PDUs cancelled.", g_queue_get_length(mp->pending_pdus));
+
+ while ((pdu = g_queue_pop_head(mp->pending_pdus)))
+ g_free(pdu);
+
+ g_queue_free(mp->pending_pdus);
+ mp->pending_pdus = NULL;
+}
+
+static uint8_t avrcp_handle_request_continuing(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ struct avrcp_header *saved_pdu;
+ int len;
+
+ if (!mp || !mp->pending_pdus) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+ }
+
+ DBG("");
+
+ saved_pdu = g_queue_pop_head(mp->pending_pdus);
+
+ len = saved_pdu->params_len;
+ memcpy(pdu, saved_pdu, sizeof(struct avrcp_header) + len);
+ pdu->params_len = htons(len);
+
+ g_free(saved_pdu);
+
+ if (g_queue_is_empty(mp->pending_pdus)) {
+ pdu->packet_type = AVRCP_PACKET_TYPE_END;
+ g_queue_free(mp->pending_pdus);
+ mp->pending_pdus = NULL;
+ } else {
+ pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING;
+ }
+
+ return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_abort_continuing(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ struct avrcp_header *saved_pdu;
+
+ if (len < 1 || mp->pending_pdus == NULL)
+ goto err;
+
+ saved_pdu = g_queue_peek_head(mp->pending_pdus);
+
+ if (saved_pdu->pdu_id != pdu->params[0])
+ goto err;
+
+ pdu->params_len = 0;
+
+ return AVC_CTYPE_STABLE;
+
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+}
+
+
static struct pdu_handler {
uint8_t pdu_id;
uint8_t code;
@@ -1223,6 +1390,10 @@ 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 },
{ },
};
@@ -1263,6 +1434,11 @@ static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
goto err_metadata;
}
+ /* We have to cancel pending pdus before processing new messages */
+ if (pdu->pdu_id != AVRCP_REQUEST_CONTINUING
+ && pdu->pdu_id != AVRCP_ABORT_CONTINUING)
+ cancel_pending_pdus(mp);
+
if (!handler->func) {
pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
--
1.7.6.1
AVRCP is an extension of AV/C spec which has a limit of 512 bytes. The
only place where it can exceed this value is in the response to
GetElementAttributes command.
Now we simply don't add the attributes that would overflow the available
buffer space.
---
audio/avctp.c | 2 -
audio/avctp.h | 3 ++
audio/avrcp.c | 58 +++++++++++++++++++++++++++++++++-----------------------
3 files changed, 37 insertions(+), 26 deletions(-)
diff --git a/audio/avctp.c b/audio/avctp.c
index 2800a16..e9b8e40 100644
--- a/audio/avctp.c
+++ b/audio/avctp.c
@@ -84,7 +84,6 @@ struct avc_header {
uint8_t subunit_type:5;
uint8_t opcode;
} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
#elif __BYTE_ORDER == __BIG_ENDIAN
@@ -104,7 +103,6 @@ struct avc_header {
uint8_t subunit_id:3;
uint8_t opcode;
} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
#else
#error "Unknown byte order"
diff --git a/audio/avctp.h b/audio/avctp.h
index 2dab8fa..9727485 100644
--- a/audio/avctp.h
+++ b/audio/avctp.h
@@ -24,6 +24,9 @@
#define AVCTP_PSM 23
+#define AVC_MTU 512
+#define AVC_HEADER_LENGTH 3
+
/* ctype entries */
#define AVC_CTYPE_CONTROL 0x0
#define AVC_CTYPE_STATUS 0x1
diff --git a/audio/avrcp.c b/audio/avrcp.c
index f962946..ba04a12 100644
--- a/audio/avrcp.c
+++ b/audio/avrcp.c
@@ -173,6 +173,9 @@ struct avrcp_header {
#error "Unknown byte order"
#endif
+#define AVRCP_MTU (AVC_MTU - AVC_HEADER_LENGTH)
+#define AVRCP_PDU_MTU (AVRCP_MTU - AVRCP_HEADER_LENGTH)
+
struct avrcp_server {
bdaddr_t src;
uint32_t tg_record_id;
@@ -649,7 +652,8 @@ static void mp_set_playback_status(struct media_player *mp, uint8_t status,
* attribute, -ENOENT is returned.
*/
static int mp_get_media_attribute(struct media_player *mp,
- uint32_t id, uint8_t *buf)
+ uint32_t id, uint8_t *buf,
+ uint16_t maxlen)
{
struct media_info_elem {
uint32_t id;
@@ -661,67 +665,72 @@ static int mp_get_media_attribute(struct media_player *mp,
struct media_info_elem *elem = (void *)buf;
uint16_t len;
char valstr[20];
+ char *valp;
+
+ if (maxlen < sizeof(struct media_info_elem))
+ return -ENOBUFS;
+
+ /* Subtract the size of elem header from the available space */
+ maxlen -= sizeof(struct media_info_elem);
switch (id) {
case MEDIA_INFO_TITLE:
- if (mi->title) {
- len = strlen(mi->title);
- memcpy(elem->val, mi->title, len);
- } else {
- len = 0;
- }
-
+ valp = mi->title;
break;
case MEDIA_INFO_ARTIST:
if (mi->artist == NULL)
return -ENOENT;
- len = strlen(mi->artist);
- memcpy(elem->val, mi->artist, len);
+ valp = mi->artist;
break;
case MEDIA_INFO_ALBUM:
if (mi->album == NULL)
return -ENOENT;
- len = strlen(mi->album);
- memcpy(elem->val, mi->album, len);
+ valp = mi->album;
break;
case MEDIA_INFO_GENRE:
if (mi->genre == NULL)
return -ENOENT;
- len = strlen(mi->genre);
- memcpy(elem->val, mi->genre, len);
+ valp = mi->genre;
break;
-
case MEDIA_INFO_TRACK:
if (!mi->track)
return -ENOENT;
snprintf(valstr, 20, "%u", mi->track);
- len = strlen(valstr);
- memcpy(elem->val, valstr, len);
+ valp = valstr;
break;
case MEDIA_INFO_N_TRACKS:
if (!mi->ntracks)
return -ENOENT;
snprintf(valstr, 20, "%u", mi->ntracks);
- len = strlen(valstr);
- memcpy(elem->val, valstr, len);
+ valp = valstr;
break;
case MEDIA_INFO_PLAYING_TIME:
if (mi->track_len == 0xFFFFFFFF)
return -ENOENT;
snprintf(valstr, 20, "%u", mi->track_len);
- len = strlen(valstr);
- memcpy(elem->val, valstr, len);
+ valp = valstr;
break;
default:
return -EINVAL;
}
+ if (valp) {
+ len = strlen(valp);
+
+ if (len > maxlen)
+ return -ENOBUFS;
+
+ memcpy(elem->val, valp, len);
+ } else {
+ len = 0;
+ }
+
elem->id = htonl(id);
elem->charset = htons(0x6A); /* Always use UTF-8 */
elem->len = htons(len);
@@ -905,8 +914,8 @@ static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
* title must be returned.
*/
for (i = 1; i < MEDIA_INFO_LAST; i++) {
- size = mp_get_media_attribute(mp, i,
- &pdu->params[pos]);
+ size = mp_get_media_attribute(mp, i, &pdu->params[pos],
+ AVRCP_PDU_MTU - pos);
if (size > 0) {
len++;
@@ -922,7 +931,8 @@ static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
uint32_t attr = ntohl(attr_ids[i]);
size = mp_get_media_attribute(mp, attr,
- &pdu->params[pos]);
+ &pdu->params[pos],
+ AVRCP_PDU_MTU - pos);
if (size > 0) {
len++;
--
1.7.6.1