2011-07-19 19:49:11

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 00/19] Implement AVRCP 1.3 profile - TG role

This series implement all the mandatory and some optional features in AVRCP 1.3
profile, target role.

It passed all the related PTS tests and it's slightly tested with a Sony
MEX-BT2900 car kit.

Current limitation:
* It does not support message continuation in case the metadata
doesn't fit one AV/C packet.
* The D-Bus API doesn't use an agent. Thus it's possible that two applications
screw up the data passed to the control interface. I made this on purpose,
since it allows me to easily test the interface. Future versions shall
remove this limitation.

I preferred the API written on doc/control-api.txt rather than the past tentatives
of using mpris. I'm not sure it's worth supporting it, but I'd like to hear
opinions. Considering both approaches need the client to be modified in
order to support the agent, maybe using mpris would just be too much hassle.

regards,
Lucas De Marchi



Lucas De Marchi (19):
avrcp: handle query for company ids
avrcp: implement ChangeSetting() method for TG role
avrcp: handle ListPlayerApplicationSettingAttributes pdu
avrcp: handle ListPlayerApplicationSettingValues pdu
avrcp: handle GetCurrentPlayerAplicationSettingValue pdu
avrcp: handle SetPlayerApplicationSettingValue pdu
avrcp: handle commands for future extension
avrcp: handle InformDisplayableCharacterSet pdu
avrcp: handle InformBatteryStatusOfCT pdu
avrcp: implement ChangePlayback() method
avrcp: handle GetPlayStatus pdu
avrcp: implement ChangeTrack() method
Add script to test control interface
avrcp: handle RegisterNotification pdu
avrcp: answer query for supported events
avrcp: handle GetElementAttributes pdu
avrcp: send response for registered events
avrcp: change TG record to use version 1.3
avrcp: update copyright

audio/control.c | 1193 ++++++++++++++++++++++++++++++++++++++++++++++++++-
doc/control-api.txt | 18 +-
test/test-control | 67 +++
3 files changed, 1268 insertions(+), 10 deletions(-)
create mode 100755 test/test-control

--
1.7.6



2011-07-21 07:11:56

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [RFC 00/19] Implement AVRCP 1.3 profile - TG role

Hi Lucas,

On Wed, Jul 20, 2011 at 3:55 PM, Lucas De Marchi
<[email protected]> wrote:
>> I think we might need an agent API anyway, since MPRIS players might
>> be siting in the session bus so bluetoothd cannot really connect to
>> them directly, in the other hand there is nothing preventing someone
>> to write an agent that uses MPRIS to talk to players.
>
> In that case, the agent could perfectly talk to our D-Bus interface
> and use MPRIS to talk to players. Or use MPRIS on both ends.

Yep, but I think we don't want to use agents as proxy because D-Bus
round trips are expensive and sometimes we need to contact the agent
to respond to a request.

--
Luiz Augusto von Dentz

2011-07-20 12:55:04

by Lucas De Marchi

[permalink] [raw]
Subject: Re: [RFC 00/19] Implement AVRCP 1.3 profile - TG role

Hi Luiz

On Wed, Jul 20, 2011 at 5:21 AM, Luiz Augusto von Dentz
<[email protected]> wrote:
>
> Hi Lucas,
>
> On Tue, Jul 19, 2011 at 10:49 PM, Lucas De Marchi
> <[email protected]> wrote:
> > This series implement all the mandatory and some optional features in AVRCP 1.3
> > profile, target role.
> >
> > It passed all the related PTS tests and it's slightly tested with a Sony
> > MEX-BT2900 car kit.
> >
> > Current limitation:
> > ?* It does not support message continuation in case the metadata
> > ? doesn't fit one AV/C packet.
> > ?* The D-Bus API doesn't use an agent. Thus it's possible that two applications
> > ? screw up the data passed to the control interface. I made this on purpose,
> > ? since it allows me to easily test the interface. Future versions shall
> > ? remove this limitation.
>
> I don't think it is a good idea to introduce a new API knowing its
> limitation, also in some systems we can have different players e.g.
> video player and music player and iirc some pdus can address those
> players separately. So if you really want this for testing purpose I
> would suggest using another interface which should be disabled by
> default, this way applications know it is unstable and we can
> experiment with it.

What I tried to say is that the interface is as is because it's still
for testing. I'm not intending to upstream it without the agent.

> > I preferred the API written on doc/control-api.txt rather than the past tentatives
> > of using mpris. I'm not sure it's worth supporting it, but I'd like to hear
> > opinions. Considering both approaches need the client to be modified in
> > order to support the agent, maybe using mpris would just be too much hassle.
>
> I think we might need an agent API anyway, since MPRIS players might
> be siting in the session bus so bluetoothd cannot really connect to
> them directly, in the other hand there is nothing preventing someone
> to write an agent that uses MPRIS to talk to players.

In that case, the agent could perfectly talk to our D-Bus interface
and use MPRIS to talk to players. Or use MPRIS on both ends.

2011-07-20 08:25:31

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [RFC 01/19] avrcp: handle query for company ids

Hi Lucas,

On Tue, Jul 19, 2011 at 10:49 PM, Lucas De Marchi
<[email protected]> wrote:
> ---
> ?audio/control.c | ? 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
> ?1 files changed, 86 insertions(+), 2 deletions(-)
>
> diff --git a/audio/control.c b/audio/control.c
> index c3ef737..983c8cd 100644
> --- a/audio/control.c
> +++ b/audio/control.c
> @@ -102,6 +102,22 @@
> ?#define FORWARD_OP ? ? ? ? ? ? 0x4b
> ?#define BACKWARD_OP ? ? ? ? ? ?0x4c
>
> +/* Company IDs for vendor dependent commands */
> +#define IEEEID_BTSIG ? ? ? ? ? 0x001958
> +
> +/* Error codes for metadata transfer */
> +#define E_INVALID_COMMAND ? ? ?0x00
> +#define E_INVALID_PARAM ? ? ? ? ? ? ? ?0x01
> +#define E_PARAM_NOT_FOUND ? ? ?0x02
> +#define E_INTERNAL ? ? ? ? ? ? 0x03
> +
> +/* PDU types for metadata transfer */
> +#define AVRCP_GET_CAPABILITIES ? ? ? ? 0x10
> +
> +/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
> +#define CAP_COMPANY_ID ? ? ? ? 0x02
> +#define CAP_EVENTS_SUPPORTED ? 0x03
> +
> ?#define QUIRK_NO_RELEASE ? ? ? 1 << 0
>
> ?static DBusConnection *connection = NULL;
> @@ -217,6 +233,11 @@ static struct {
> ? ? ? ?{ NULL }
> ?};
>
> +/* Company IDs supported by this device */
> +static uint32_t company_ids[] = {
> + ? ? ? IEEEID_BTSIG,
> +};
> +
> ?static GSList *avctp_callbacks = NULL;
>
> ?static void auth_cb(DBusError *derr, void *user_data);
> @@ -424,8 +445,71 @@ static int handle_vendordep_pdu(struct control *control,
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?struct avrcp_header *avrcp,
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?int operand_count)
> ?{
> - ? ? ? avrcp->code = CTYPE_NOT_IMPLEMENTED;
> - ? ? ? return AVRCP_HEADER_LENGTH;
> + ? ? ? struct avrcp_spec_avc_pdu *pdu = (void *) avrcp + AVRCP_HEADER_LENGTH;
> + ? ? ? uint32_t company_id = (pdu->company_id[0] << 16) |
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (pdu->company_id[1] << 8) |
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (pdu->company_id[2]);
> + ? ? ? uint16_t len;
> + ? ? ? unsigned int i;
> +
> + ? ? ? if (company_id != IEEEID_BTSIG ||
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pdu->packet_type != AVCTP_PACKET_SINGLE) {
> + ? ? ? ? ? ? ? avrcp->code = CTYPE_NOT_IMPLEMENTED;
> + ? ? ? ? ? ? ? return AVRCP_HEADER_LENGTH;
> + ? ? ? }
> +
> + ? ? ? pdu->packet_type = 0;
> + ? ? ? pdu->rsvd = 0;
> +
> + ? ? ? if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH) {
> + ? ? ? ? ? ? ? pdu->params[0] = E_INVALID_COMMAND;
> + ? ? ? ? ? ? ? goto err_metadata;
> + ? ? ? }
> +
> + ? ? ? len = ntohs(pdu->params_len);

I would consider doing the each pdu handling in its own function,
otherwise handle_vendordep_pdu might become a little too big.

> + ? ? ? switch (pdu->pdu_id) {
> + ? ? ? case AVRCP_GET_CAPABILITIES:
> + ? ? ? ? ? ? ? if (len != 1 || avrcp->code != CTYPE_STATUS)
> + ? ? ? ? ? ? ? ? ? ? ? break;
> +
> + ? ? ? ? ? ? ? DBG("GET_CAPABILITIES id=%u", pdu->params[0]);
> +
> + ? ? ? ? ? ? ? switch (pdu->params[0]) { /* capability id */
> + ? ? ? ? ? ? ? case CAP_COMPANY_ID:
> + ? ? ? ? ? ? ? ? ? ? ? avrcp->code = CTYPE_STABLE;
> + ? ? ? ? ? ? ? ? ? ? ? pdu->params_len = htons(1 +
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 3 * G_N_ELEMENTS(company_ids));
> + ? ? ? ? ? ? ? ? ? ? ? pdu->params[1] = G_N_ELEMENTS(company_ids);
> +
> + ? ? ? ? ? ? ? ? ? ? ? for (i = 0; i < G_N_ELEMENTS(company_ids); i++) {
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pdu->params[2 + i * 3] = company_ids[i] >> 16;
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pdu->params[3 + i * 3] = (company_ids[i] >> 8)
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? & 0xFF;
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? pdu->params[4 + i * 3] = company_ids[i] & 0xFF;
> + ? ? ? ? ? ? ? ? ? ? ? }
> +
> + ? ? ? ? ? ? ? ? ? ? ? return AVRCP_HEADER_LENGTH +
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVRCP_SPECAVCPDU_HEADER_LENGTH + 1 +
> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 3 * G_N_ELEMENTS(company_ids);
> + ? ? ? ? ? ? ? }
> +
> + ? ? ? ? ? ? ? pdu->params[0] = E_INVALID_PARAM;
> + ? ? ? ? ? ? ? goto err_metadata;
> + ? ? ? }
> +
> + ? ? ? /*
> + ? ? ? ?* If either pdu_id was invalid or message was malformed, respond with
> + ? ? ? ?* E_INVALID_COMMAND. For other errors, we already jumped into
> + ? ? ? ?* err_metadata.
> + ? ? ? ?*/
> + ? ? ? pdu->params[0] = E_INVALID_COMMAND;
> +
> +err_metadata:
> + ? ? ? avrcp->code = CTYPE_REJECTED;
> + ? ? ? pdu->params_len = htons(1);
> +
> + ? ? ? return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
> ?}
>
> ?static void avctp_disconnected(struct audio_device *dev)
> --
> 1.7.6
>
> --
> 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
>



--
Luiz Augusto von Dentz

2011-07-20 08:21:46

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: Re: [RFC 00/19] Implement AVRCP 1.3 profile - TG role

Hi Lucas,

On Tue, Jul 19, 2011 at 10:49 PM, Lucas De Marchi
<[email protected]> wrote:
> This series implement all the mandatory and some optional features in AVRCP 1.3
> profile, target role.
>
> It passed all the related PTS tests and it's slightly tested with a Sony
> MEX-BT2900 car kit.
>
> Current limitation:
> ?* It does not support message continuation in case the metadata
> ? doesn't fit one AV/C packet.
> ?* The D-Bus API doesn't use an agent. Thus it's possible that two applications
> ? screw up the data passed to the control interface. I made this on purpose,
> ? since it allows me to easily test the interface. Future versions shall
> ? remove this limitation.

I don't think it is a good idea to introduce a new API knowing its
limitation, also in some systems we can have different players e.g.
video player and music player and iirc some pdus can address those
players separately. So if you really want this for testing purpose I
would suggest using another interface which should be disabled by
default, this way applications know it is unstable and we can
experiment with it.

> I preferred the API written on doc/control-api.txt rather than the past tentatives
> of using mpris. I'm not sure it's worth supporting it, but I'd like to hear
> opinions. Considering both approaches need the client to be modified in
> order to support the agent, maybe using mpris would just be too much hassle.

I think we might need an agent API anyway, since MPRIS players might
be siting in the session bus so bluetoothd cannot really connect to
them directly, in the other hand there is nothing preventing someone
to write an agent that uses MPRIS to talk to players.

> regards,
> Lucas De Marchi
>
>
>
> Lucas De Marchi (19):
> ?avrcp: handle query for company ids
> ?avrcp: implement ChangeSetting() method for TG role
> ?avrcp: handle ListPlayerApplicationSettingAttributes pdu
> ?avrcp: handle ListPlayerApplicationSettingValues pdu
> ?avrcp: handle GetCurrentPlayerAplicationSettingValue pdu
> ?avrcp: handle SetPlayerApplicationSettingValue pdu
> ?avrcp: handle commands for future extension
> ?avrcp: handle InformDisplayableCharacterSet pdu
> ?avrcp: handle InformBatteryStatusOfCT pdu
> ?avrcp: implement ChangePlayback() method
> ?avrcp: handle GetPlayStatus pdu
> ?avrcp: implement ChangeTrack() method
> ?Add script to test control interface
> ?avrcp: handle RegisterNotification pdu
> ?avrcp: answer query for supported events
> ?avrcp: handle GetElementAttributes pdu
> ?avrcp: send response for registered events
> ?avrcp: change TG record to use version 1.3
> ?avrcp: update copyright
>
> ?audio/control.c ? ? | 1193 ++++++++++++++++++++++++++++++++++++++++++++++++++-
> ?doc/control-api.txt | ? 18 +-
> ?test/test-control ? | ? 67 +++
> ?3 files changed, 1268 insertions(+), 10 deletions(-)
> ?create mode 100755 test/test-control
>
> --
> 1.7.6
>
> --
> 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
>



--
Luiz Augusto von Dentz

2011-07-19 19:49:30

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 19/19] avrcp: update copyright

---
audio/control.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 1704c0a..0f03ec0 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -4,6 +4,7 @@
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ * Copyright (C) 2011 Texas Instruments, Inc.
*
*
* This program is free software; you can redistribute it and/or modify
--
1.7.6


2011-07-19 19:49:29

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 18/19] avrcp: change TG record to use version 1.3

---
audio/control.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 894a706..1704c0a 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -420,7 +420,7 @@ static sdp_record_t *avrcp_tg_record(void)
sdp_record_t *record;
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM;
- uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f;
+ uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;

record = sdp_record_alloc();
if (!record)
--
1.7.6


2011-07-19 19:49:28

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 17/19] avrcp: send response for registered events

When a certain event occur, check if CT registered to receive that
notification and send a response.
---
audio/control.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index d37848e..894a706 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -79,6 +79,7 @@
#define CTYPE_ACCEPTED 0x9
#define CTYPE_REJECTED 0xA
#define CTYPE_STABLE 0xC
+#define CTYPE_CHANGED 0xD
#define CTYPE_INTERIM 0xF

/* opcodes */
@@ -319,6 +320,7 @@ struct control {
uint8_t player_setting[PLAYER_SETTING_SCAN + 1];
struct media_info mi;
uint16_t registered_events;
+ uint8_t transaction_events[AVRCP_EVENT_VOLUME_CHANGED + 1];
};

static struct {
@@ -793,8 +795,74 @@ static dbus_bool_t emit_setting_changed(DBusConnection *conn,
return g_dbus_send_message(conn, signal);
}

+static int avctp_send_event(struct control *control, uint8_t id)
+{
+ uint8_t buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + 9];
+ struct avctp_header *avctp = (void *) buf;
+ struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+ struct avrcp_spec_avc_pdu *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
+ AVRCP_HEADER_LENGTH];
+ int sk = g_io_channel_unix_get_fd(control->io);
+ uint16_t size;
+
+ memset(buf, 0, sizeof(buf));
+
+ avctp->transaction = control->transaction_events[id];
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avrcp->code = CTYPE_CHANGED;
+ avrcp->subunit_type = SUBUNIT_PANEL;
+ avrcp->opcode = OP_VENDORDEP;
+
+ pdu->company_id[0] = IEEEID_BTSIG >> 16;
+ pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF;
+ pdu->company_id[2] = IEEEID_BTSIG & 0xFF;
+
+ pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
+ pdu->params[0] = id;
+
+ switch (id) {
+ case (AVRCP_EVENT_PLAYBACK_STATUS_CHANGED):
+ size = 2;
+ pdu->params[1] = control->mi.status;
+
+ break;
+ case (AVRCP_EVENT_TRACK_CHANGED): {
+ size = 9;
+
+ /*
+ * AVRCP 1.3 supports only one track identifier: PLAYING
+ * (0x0). When 1.4 version is added, this shall be changed to
+ * contain the identifier of the track.
+ */
+ memset(&pdu->params[1], 0, 8);
+
+ break;
+ }
+ default:
+ error("Unknown event %u", id);
+ return -EINVAL;
+ }
+
+ pdu->params_len = htons(size);
+ size += AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH;
+
+ if (write(sk, buf, size) < 0)
+ return -errno;
+
+ /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
+ control->registered_events ^= 1 << id;
+
+ return 0;
+}
+
/* handle vendordep pdu inside an avctp packet */
static int handle_vendordep_pdu(struct control *control,
+ struct avctp_header *avctp,
struct avrcp_header *avrcp,
int operand_count)
{
@@ -1158,6 +1226,8 @@ static int handle_vendordep_pdu(struct control *control,

/* Register event */
control->registered_events |= (1 << pdu->params[0]);
+ control->transaction_events[pdu->params[0]] =
+ avctp->transaction;

avrcp->code = CTYPE_INTERIM;
pdu->params_len = htons(size);
@@ -1359,7 +1429,8 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
int r_size;
operand_count -= 3;
avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avrcp, operand_count);
+ r_size = handle_vendordep_pdu(control, avctp, avrcp,
+ operand_count);
packet_size = AVCTP_HEADER_LENGTH + r_size;
} else {
avctp->cr = AVCTP_RESPONSE;
@@ -2080,7 +2151,16 @@ static DBusMessage *control_change_playback(DBusConnection *conn,
if (status < 0)
return btd_error_invalid_args(msg);

- control->mi.status = status;
+ if ((uint8_t) status != control->mi.status) {
+ control->mi.status = status;
+
+ if (control->registered_events &
+ (1 << AVRCP_EVENT_PLAYBACK_STATUS_CHANGED)) {
+ avctp_send_event(control,
+ AVRCP_EVENT_PLAYBACK_STATUS_CHANGED);
+ }
+ }
+
control->mi.current_position = elapsed;

DBG("Change playback: %s %u", statusstr, elapsed);
@@ -2203,6 +2283,9 @@ static DBusMessage *control_change_track(DBusConnection *conn,
mi.title, mi.artist, mi.album, mi.genre,
mi.ntracks, mi.track, mi.track_length);

+ if (control->registered_events & (1 << AVRCP_EVENT_TRACK_CHANGED))
+ avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED);
+
return dbus_message_new_method_return(msg);
}

--
1.7.6


2011-07-19 19:49:27

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 16/19] avrcp: handle GetElementAttributes pdu

---
audio/control.c | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 162 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 88efdb5..d37848e 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -123,6 +123,7 @@
#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
#define AVRCP_DISPLAYABLE_CHARSET 0x17
#define AVRCP_CT_BATTERY_STATUS 0x18
+#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20
#define AVRCP_GET_PLAY_STATUS 0x30
#define AVRCP_REGISTER_NOTIFICATION 0x31

@@ -195,6 +196,16 @@ enum play_status {
PLAY_STATUS_ERROR = 0xFF
};

+enum media_info_id {
+ MEDIA_INFO_TITLE = 1,
+ MEDIA_INFO_ARTIST = 2,
+ MEDIA_INFO_ALBUM = 3,
+ MEDIA_INFO_TRACK = 4,
+ MEDIA_INFO_N_TRACKS = 5,
+ MEDIA_INFO_GENRE = 6,
+ MEDIA_INFO_CURRENT_POSITION = 7,
+};
+
static DBusConnection *connection = NULL;

static GSList *servers = NULL;
@@ -655,6 +666,95 @@ static const char *battery_status_to_string(enum battery_status status)
return NULL;
}

+/*
+ * 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 returns the size written.
+ * If there's an error, nothing is written and -EINVAL is returned.
+ */
+static int media_info_get_elem(struct control *control, uint32_t id,
+ uint8_t *buf)
+{
+ struct media_info_elem {
+ uint32_t id;
+ uint16_t charset;
+ uint16_t len;
+ uint8_t val[];
+ };
+ struct media_info_elem *elem = (void *)buf;
+ struct media_info *mi = &control->mi;
+ uint16_t len;
+ char valstr[20];
+
+ switch (id) {
+ case (MEDIA_INFO_TITLE):
+ if (mi->title) {
+ len = strlen(mi->title);
+ memcpy(elem->val, mi->title, len);
+ } else {
+ len = 0;
+ }
+
+ break;
+ case (MEDIA_INFO_ARTIST):
+ if (mi->artist == NULL)
+ return -ENOENT;
+
+ len = strlen(mi->artist);
+ memcpy(elem->val, mi->artist, len);
+ break;
+ case (MEDIA_INFO_ALBUM):
+ if (mi->album == NULL)
+ return -ENOENT;
+
+ len = strlen(mi->album);
+ memcpy(elem->val, mi->album, len);
+ break;
+ case (MEDIA_INFO_GENRE):
+ if (mi->genre == NULL)
+ return -ENOENT;
+
+ len = strlen(mi->genre);
+ memcpy(elem->val, mi->genre, len);
+ break;
+
+ case (MEDIA_INFO_TRACK):
+ if (!mi->track)
+ return -ENOENT;
+
+ snprintf(valstr, 20, "%u", mi->track);
+ len = strlen(valstr);
+ memcpy(elem->val, valstr, len);
+ 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);
+ break;
+ case (MEDIA_INFO_CURRENT_POSITION):
+ if (mi->current_position == 0xFFFFFFFF)
+ return -ENOENT;
+
+ snprintf(valstr, 20, "%u", mi->current_position);
+ len = strlen(valstr);
+ memcpy(elem->val, valstr, len);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ elem->id = htonl(id);
+ elem->charset = htons(0x6A); /* Always use UTF-8 */
+ elem->len = htons(len);
+
+ return sizeof(struct media_info_elem) + len;
+}
+
+
static void append_variant(DBusMessageIter *iter, int type, void *val)
{
DBusMessageIter value;
@@ -800,6 +900,68 @@ static int handle_vendordep_pdu(struct control *control,

return AVRCP_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH + len + 1;
+ case AVRCP_GET_ELEMENT_ATTRIBUTES:
+ if (len > 8 && avrcp->code == CTYPE_STATUS) {
+ uint16_t pos = 1; /* keep track of position in resp */
+ uint8_t nattr = pdu->params[8];
+ uint64_t *identifier = (void *) &pdu->params[0];
+ int size;
+
+ if (*identifier != 0) {
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ len = 0;
+
+ if (!nattr) {
+ /*
+ * Return all available information, at least
+ * title must be returned
+ */
+ for (i = 1; i < MEDIA_INFO_CURRENT_POSITION;
+ i++) {
+ size = media_info_get_elem(control,
+ i, &pdu->params[pos]);
+
+ if (size > 0) {
+ len++;
+ pos += size;
+ }
+ }
+ } else {
+ uint32_t attr_ids[nattr];
+
+ memcpy(&attr_ids[0], &pdu->params[9],
+ nattr * 4);
+
+ for (i = 0; i < nattr; i++) {
+ uint32_t attr = ntohl(attr_ids[i]);
+
+ size = media_info_get_elem(control,
+ attr, &pdu->params[pos]);
+
+ if (size > 0) {
+ len++;
+ pos += size;
+ }
+ }
+
+ if (!len) {
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+ }
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params[0] = len;
+ pdu->params_len = htons(pos);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + pos;
+ }
+
+ break;
case AVRCP_GET_CURRENT_PLAYER_VALUE:
if (len > 1 && pdu->params[0] == len - 1 &&
avrcp->code == CTYPE_STATUS) {
--
1.7.6


2011-07-19 19:49:26

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 15/19] avrcp: answer query for supported events

---
audio/control.c | 10 ++++++++++
1 files changed, 10 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 212e95c..88efdb5 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -745,6 +745,16 @@ static int handle_vendordep_pdu(struct control *control,
return AVRCP_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH + 1 +
3 * G_N_ELEMENTS(company_ids);
+ case CAP_EVENTS_SUPPORTED:
+ avrcp->code = CTYPE_STABLE;
+ /* CpabilityID + CapabilityCount + n_events */
+ pdu->params_len = htons(4);
+ pdu->params[1] = 2;
+ pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED;
+ pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + 4;
}

pdu->params[0] = E_INVALID_PARAM;
--
1.7.6


2011-07-19 19:49:25

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 14/19] avrcp: handle RegisterNotification pdu

Handle mandatory events according to AVRCP 1.3 spec.
---
audio/control.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 670660f..212e95c 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -74,10 +74,12 @@
/* ctype entries */
#define CTYPE_CONTROL 0x0
#define CTYPE_STATUS 0x1
+#define CTYPE_NOTIFY 0x3
#define CTYPE_NOT_IMPLEMENTED 0x8
#define CTYPE_ACCEPTED 0x9
#define CTYPE_REJECTED 0xA
#define CTYPE_STABLE 0xC
+#define CTYPE_INTERIM 0xF

/* opcodes */
#define OP_VENDORDEP 0x00
@@ -122,6 +124,22 @@
#define AVRCP_DISPLAYABLE_CHARSET 0x17
#define AVRCP_CT_BATTERY_STATUS 0x18
#define AVRCP_GET_PLAY_STATUS 0x30
+#define AVRCP_REGISTER_NOTIFICATION 0x31
+
+/* Notification events */
+#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED 0x01
+#define AVRCP_EVENT_TRACK_CHANGED 0x02
+#define AVRCP_EVENT_TRACK_REACHED_END 0x03
+#define AVRCP_EVENT_TRACK_REACHED_START 0x04
+#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05
+#define AVRCP_EVENT_BATT_STATUS_CHANGED 0x06
+#define AVRCP_EVENT_SYSTEM_STATUS_CHANGED 0x07
+#define AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED 0x08
+#define AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED 0x09
+#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a
+#define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b
+#define AVRCP_EVENT_UIDS_CHANGED 0x0c
+#define AVRCP_EVENT_VOLUME_CHANGED 0x0d

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -289,6 +307,7 @@ struct control {

uint8_t player_setting[PLAYER_SETTING_SCAN + 1];
struct media_info mi;
+ uint16_t registered_events;
};

static struct {
@@ -944,6 +963,38 @@ static int handle_vendordep_pdu(struct control *control,
}

break;
+ case AVRCP_REGISTER_NOTIFICATION:
+ if (len == 5 && avrcp->code == CTYPE_NOTIFY) {
+ int size;
+
+ switch (pdu->params[0]) {
+ case (AVRCP_EVENT_PLAYBACK_STATUS_CHANGED):
+ size = 2;
+ pdu->params[1] = control->mi.status;
+
+ break;
+ case (AVRCP_EVENT_TRACK_CHANGED):
+ size = 9;
+ memset(&pdu->params[1], 0, 8);
+
+ break;
+ default:
+ /* All other events are not supported yet */
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ /* Register event */
+ control->registered_events |= (1 << pdu->params[0]);
+
+ avrcp->code = CTYPE_INTERIM;
+ pdu->params_len = htons(size);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + size;
+ }
+
+ break;
}

/*
--
1.7.6


2011-07-19 19:49:24

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 13/19] Add script to test control interface

---
test/test-control | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 67 insertions(+), 0 deletions(-)
create mode 100755 test/test-control

diff --git a/test/test-control b/test/test-control
new file mode 100755
index 0000000..4351d73
--- /dev/null
+++ b/test/test-control
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+from optparse import OptionParser, make_option
+
+USAGE = """Usage: %s <command> [args]
+
+ changetrack <bdaddr> <key> <value> [<key> <value> ...]
+ """ % sys.argv[0]
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+ make_option("-i", "--device", action="store",
+ type="string", dest="dev_id"),
+ ]
+parser = OptionParser(option_list=option_list)
+
+(options, args) = parser.parse_args()
+
+if options.dev_id:
+ adapter_path = manager.FindAdapter(options.dev_id)
+else:
+ adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+ "org.bluez.Adapter")
+
+if len(args) < 2:
+ print(USAGE)
+ sys.exit(1)
+
+device = adapter.FindDevice(args[1])
+control = dbus.Interface(bus.get_object("org.bluez", device),
+ "org.bluez.Control")
+
+def handle_change_track(control, args):
+ if len(args) % 2 != 0:
+ print("Don't know how to handle odd number of parameters")
+ print(USAGE)
+ sys.exit(1)
+
+ d = dict()
+ for i in range(2, len(args), 2):
+ key = args[i]
+ if key == "Title" or key == "Artist" or key == "Album" \
+ or key == "Genre":
+ d[key] = dbus.String(args[i + 1].decode(sys.stdin.encoding))
+ elif key == "NumberOfTracks" or key == "TrackNumber" \
+ or key == "TrackDuration":
+ d[key] = dbus.UInt32(int(args[i + 1]))
+ else:
+ print("Unknown metadata: %s" % key)
+ sys.exit(1)
+
+ d = dbus.Dictionary(d, signature='sv')
+ control.ChangeTrack(d)
+
+if args[0] == "changetrack":
+ handle_change_track(control, args)
+else:
+ print("Unknown command -- %s" % argv[1])
+ print(USAGE)
+ sys.exit(1)
--
1.7.6


2011-07-19 19:49:23

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 12/19] avrcp: implement ChangeTrack() method

ChangeTrack() is used by applications to inform BlueZ that current track
changed, passing also the metadata. It's expected to work only when the
device is in TG role.
---
audio/control.c | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++
doc/control-api.txt | 3 +
2 files changed, 170 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 03f1cb4..670660f 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -261,6 +261,13 @@ struct avctp_server {
struct media_info {
enum play_status status;
uint32_t current_position;
+
+ char *title;
+ char *artist;
+ char *album;
+ char *genre;
+ uint32_t ntracks;
+ uint32_t track;
uint32_t track_length;
};

@@ -1240,6 +1247,47 @@ static void media_info_init(struct media_info *mi)
mi->current_position = 0xFFFFFFFF;
}

+static void media_info_reset(struct media_info *mi)
+{
+ DBG("");
+
+ if (mi->title)
+ free(mi->title);
+
+ if (mi->artist)
+ free(mi->artist);
+
+ if (mi->album)
+ free(mi->album);
+
+ if (mi->genre)
+ free(mi->genre);
+
+ media_info_init(mi);
+}
+
+static void media_info_copy(struct media_info *dest, struct media_info *src)
+{
+ DBG("");
+
+ if (src->title)
+ dest->title = strdup(src->title);
+
+ if (src->artist)
+ dest->artist = strdup(src->artist);
+
+ if (src->album)
+ dest->album = strdup(src->album);
+
+ if (src->genre)
+ dest->genre = strdup(src->genre);
+
+ dest->ntracks = src->ntracks;
+ dest->track = src->track;
+ dest->track_length = src->track_length;
+}
+
+
static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
{
struct control *control = data;
@@ -1817,6 +1865,124 @@ static DBusMessage *control_change_playback(DBusConnection *conn,
return dbus_message_new_method_return(msg);
}

+static gboolean media_info_parse(DBusMessageIter *iter, struct media_info *mi)
+{
+ DBusMessageIter dict;
+ DBusMessageIter var;
+ int ctype;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ if (ctype != DBUS_TYPE_ARRAY)
+ return FALSE;
+
+ media_info_init(mi);
+ dbus_message_iter_recurse(iter, &dict);
+
+ while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+ DBUS_TYPE_INVALID) {
+ DBusMessageIter entry;
+ const char *key;
+
+ if (ctype != DBUS_TYPE_DICT_ENTRY)
+ return FALSE;
+
+ dbus_message_iter_recurse(&dict, &entry);
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&entry, &key);
+ dbus_message_iter_next(&entry);
+
+ if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
+ return FALSE;
+
+ dbus_message_iter_recurse(&entry, &var);
+
+ if (!strcmp(key, "Title")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->title);
+ } else if (!strcmp(key, "Artist")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->artist);
+ } else if (!strcmp(key, "Album")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->album);
+ } else if (!strcmp(key, "Genre")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_STRING)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->genre);
+ } else if (!strcmp(key, "NumberOfTracks")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_UINT32)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->ntracks);
+ } else if (!strcmp(key, "TrackNumber")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_UINT32)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->track);
+ } else if (!strcmp(key, "TrackDuration")) {
+ if (dbus_message_iter_get_arg_type(&var) !=
+ DBUS_TYPE_UINT32)
+ return FALSE;
+
+ dbus_message_iter_get_basic(&var, &mi->track_length);
+ } else {
+ return FALSE;
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (mi->title == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static DBusMessage *control_change_track(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ DBusMessageIter iter;
+ struct media_info mi;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (control->target) /* Only supported if this device is in TG role */
+ return btd_error_not_supported(msg);
+
+ dbus_message_iter_init(msg, &iter);
+ if (!media_info_parse(&iter, &mi))
+ return btd_error_invalid_args(msg);
+
+ media_info_reset(&control->mi);
+ media_info_copy(&control->mi, &mi);
+
+ DBG("Track change:\n\ttitle: %s\n\tartist: %s\n\talbum: %s\n"
+ "\tgenre: %s\n\tNumber of tracks: %u\n"
+ "\tTrack number: %u\n\tTrack duration: %u",
+ mi.title, mi.artist, mi.album, mi.genre,
+ mi.ntracks, mi.track, mi.track_length);
+
+ return dbus_message_new_method_return(msg);
+}
+
static GDBusMethodTable control_methods[] = {
{ "IsConnected", "", "b", control_is_connected,
G_DBUS_METHOD_FLAG_DEPRECATED },
@@ -1825,6 +1991,7 @@ static GDBusMethodTable control_methods[] = {
{ "VolumeDown", "", "", volume_down },
{ "ChangeSetting", "sv", "", control_change_setting },
{ "ChangePlayback", "su", "", control_change_playback },
+ { "ChangeTrack", "a{sv}","", control_change_track },
{ NULL, NULL, NULL, NULL }
};

diff --git a/doc/control-api.txt b/doc/control-api.txt
index ca97544..c2fa0e9 100644
--- a/doc/control-api.txt
+++ b/doc/control-api.txt
@@ -68,6 +68,9 @@ Methods void Connect()
TrackNumber uint32
TrackDuration uint32 (in milliseconds)

+ When track is changed, all metadata information not
+ supplied is treat as absent.
+
void ChangeSetting(string setting, variant value)

Called to transmit Application Settings, CT Status
--
1.7.6


2011-07-19 19:49:22

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 11/19] avrcp: handle GetPlayStatus pdu

---
audio/control.c | 34 ++++++++++++++++++++++++++++++++++
1 files changed, 34 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 77e0e6d..03f1cb4 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -121,6 +121,7 @@
#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
#define AVRCP_DISPLAYABLE_CHARSET 0x17
#define AVRCP_CT_BATTERY_STATUS 0x18
+#define AVRCP_GET_PLAY_STATUS 0x30

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -260,6 +261,7 @@ struct avctp_server {
struct media_info {
enum play_status status;
uint32_t current_position;
+ uint32_t track_length;
};

struct control {
@@ -918,6 +920,23 @@ static int handle_vendordep_pdu(struct control *control,
}

break;
+ case AVRCP_GET_PLAY_STATUS:
+ if (len == 0 && avrcp->code == CTYPE_STATUS) {
+ uint32_t track_length = htonl(control->mi.track_length);
+ uint32_t pos = htonl(control->mi.current_position);
+
+ memcpy(&pdu->params[0], &track_length, 4);
+ memcpy(&pdu->params[4], &pos, 4);
+ pdu->params[8] = control->mi.status;
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params_len = htons(9);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + 9;
+ }
+
+ break;
}

/*
@@ -1207,6 +1226,20 @@ static void init_uinput(struct control *control)
DBG("AVRCP: uinput initialized for %s", address);
}

+static void media_info_init(struct media_info *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+ mi->status = PLAY_STATUS_ERROR;
+
+ /*
+ * As per section 5.4.1 of AVRCP 1.3 spec, return 0xFFFFFFFF if TG
+ * does not support these attributes (i.e. they were never set via
+ * D-Bus)
+ */
+ mi->track_length = 0xFFFFFFFF;
+ mi->current_position = 0xFFFFFFFF;
+}
+
static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
{
struct control *control = data;
@@ -1237,6 +1270,7 @@ static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
control->io = g_io_channel_ref(chan);

init_uinput(control);
+ media_info_init(&control->mi);

avctp_set_state(control, AVCTP_STATE_CONNECTED);
control->mtu = imtu;
--
1.7.6


2011-07-19 19:49:21

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 10/19] avrcp: implement ChangePlayback() method

ChangePlayback() is used by applications to inform BlueZ of the current
status of playback. It's expected to work only when the device is in TG
role.
---
audio/control.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++
doc/control-api.txt | 3 +-
2 files changed, 68 insertions(+), 1 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 866a558..77e0e6d 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -167,6 +167,15 @@ enum battery_status {
BATTERY_STATUS_FULL_CHARGE = 4,
};

+enum play_status {
+ PLAY_STATUS_STOPPED = 0x00,
+ PLAY_STATUS_PLAYING = 0x01,
+ PLAY_STATUS_PAUSED = 0x02,
+ PLAY_STATUS_FWD_SEEK = 0x03,
+ PLAY_STATUS_REV_SEEK = 0x04,
+ PLAY_STATUS_ERROR = 0xFF
+};
+
static DBusConnection *connection = NULL;

static GSList *servers = NULL;
@@ -248,6 +257,11 @@ struct avctp_server {
uint32_t ct_record_id;
};

+struct media_info {
+ enum play_status status;
+ uint32_t current_position;
+};
+
struct control {
struct audio_device *dev;

@@ -265,6 +279,7 @@ struct control {
uint8_t key_quirks[256];

uint8_t player_setting[PLAYER_SETTING_SCAN + 1];
+ struct media_info mi;
};

static struct {
@@ -1653,6 +1668,24 @@ static int setting_string_to_val(uint8_t setting, const char *value) {
return -EINVAL;
}

+static int play_status_string_to_val(const char *status)
+{
+ if (!strcmp(status, "stopped"))
+ return PLAY_STATUS_STOPPED;
+ else if (!strcmp(status, "playing"))
+ return PLAY_STATUS_PLAYING;
+ else if (!strcmp(status, "paused"))
+ return PLAY_STATUS_PAUSED;
+ else if (!strcmp(status, "forward-seek"))
+ return PLAY_STATUS_FWD_SEEK;
+ else if (!strcmp(status, "reverse-seek"))
+ return PLAY_STATUS_REV_SEEK;
+ else if (!strcmp(status, "error"))
+ return PLAY_STATUS_ERROR;
+
+ return -EINVAL;
+}
+
static DBusMessage *setting_set(DBusMessage *msg, struct control *control,
const char *settingstr, const char *valstr)
{
@@ -1718,6 +1751,38 @@ static DBusMessage *control_change_setting(DBusConnection *conn,
return setting_set(msg, control, setting, value);
}

+static DBusMessage *control_change_playback(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ const char *statusstr;
+ int status;
+ uint32_t elapsed;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (control->target) /* Only supported if this device is in TG role */
+ return btd_error_not_supported(msg);
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &statusstr,
+ DBUS_TYPE_UINT32, &elapsed,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ status = play_status_string_to_val(statusstr);
+ if (status < 0)
+ return btd_error_invalid_args(msg);
+
+ control->mi.status = status;
+ control->mi.current_position = elapsed;
+
+ DBG("Change playback: %s %u", statusstr, elapsed);
+
+ return dbus_message_new_method_return(msg);
+}
+
static GDBusMethodTable control_methods[] = {
{ "IsConnected", "", "b", control_is_connected,
G_DBUS_METHOD_FLAG_DEPRECATED },
@@ -1725,6 +1790,7 @@ static GDBusMethodTable control_methods[] = {
{ "VolumeUp", "", "", volume_up },
{ "VolumeDown", "", "", volume_down },
{ "ChangeSetting", "sv", "", control_change_setting },
+ { "ChangePlayback", "su", "", control_change_playback },
{ NULL, NULL, NULL, NULL }
};

diff --git a/doc/control-api.txt b/doc/control-api.txt
index 931fa10..ca97544 100644
--- a/doc/control-api.txt
+++ b/doc/control-api.txt
@@ -49,7 +49,8 @@ Methods void Connect()

The status can be "playing", "stopped", "paused",
"forward-seek", "reverse-seek" or "error". Elapsed is
- the position within the track in milliseconds.
+ the position within the track in milliseconds. ONLY
+ valid if BlueZ device is in TG role.

void ChangeTrack(dict metadata)

--
1.7.6


2011-07-19 19:49:20

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 09/19] avrcp: handle InformBatteryStatusOfCT pdu

---
audio/control.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 2825e6c..866a558 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -120,6 +120,7 @@
#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15
#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
#define AVRCP_DISPLAYABLE_CHARSET 0x17
+#define AVRCP_CT_BATTERY_STATUS 0x18

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -158,6 +159,14 @@ enum scan_mode {
SCAN_MODE_GROUP = 3,
};

+enum battery_status {
+ BATTERY_STATUS_NORMAL = 0,
+ BATTERY_STATUS_WARNING = 1,
+ BATTERY_STATUS_CRITICAL = 2,
+ BATTERY_STATUS_EXTERNAL = 3,
+ BATTERY_STATUS_FULL_CHARGE = 4,
+};
+
static DBusConnection *connection = NULL;

static GSList *servers = NULL;
@@ -585,6 +594,24 @@ static const char *setting_to_string(enum player_setting setting)
return NULL;
}

+static const char *battery_status_to_string(enum battery_status status)
+{
+ switch (status) {
+ case (BATTERY_STATUS_NORMAL):
+ return "normal";
+ case (BATTERY_STATUS_WARNING):
+ return "warning";
+ case (BATTERY_STATUS_CRITICAL):
+ return "critical";
+ case (BATTERY_STATUS_EXTERNAL):
+ return "external";
+ case (BATTERY_STATUS_FULL_CHARGE):
+ return "fullcharge";
+ }
+
+ return NULL;
+}
+
static void append_variant(DBusMessageIter *iter, int type, void *val)
{
DBusMessageIter value;
@@ -853,6 +880,29 @@ static int handle_vendordep_pdu(struct control *control,

return AVRCP_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH;
+ case AVRCP_CT_BATTERY_STATUS:
+ if (len == 1 && avrcp->code == CTYPE_STATUS) {
+ const char *value;
+
+ value = battery_status_to_string(pdu->params[0]);
+ if (value == NULL) {
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ emit_setting_changed(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Battery",
+ DBUS_TYPE_STRING, &value);
+
+ pdu->params_len = 0;
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH;
+ }
+
+ break;
}

/*
@@ -1682,6 +1732,7 @@ static GDBusSignalTable control_signals[] = {
{ "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
{ "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
{ "PropertyChanged", "sv" },
+ { "SettingChanged", "sv" },
{ NULL, NULL }
};

--
1.7.6


2011-07-19 19:49:19

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 08/19] avrcp: handle InformDisplayableCharacterSet pdu

---
audio/control.c | 13 +++++++++++++
1 files changed, 13 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 36f727f..2825e6c 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -119,6 +119,7 @@
#define AVRCP_SET_PLAYER_VALUE 0x14
#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15
#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
+#define AVRCP_DISPLAYABLE_CHARSET 0x17

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -840,6 +841,18 @@ static int handle_vendordep_pdu(struct control *control,
*/
pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
+ case AVRCP_DISPLAYABLE_CHARSET:
+ if (len < 3 || avrcp->code != CTYPE_STATUS)
+ break;
+
+ /*
+ * We acknowledge the commands, but we always use UTF-8 for
+ * encoding since CT is obliged to support it
+ */
+ pdu->params_len = 0;
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH;
}

/*
--
1.7.6


2011-07-19 19:49:18

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 07/19] avrcp: handle commands for future extension

AVRCP_GET_PLAYER_ATTRIBUTE_TEXT and AVRCP_GET_PLAYER_VALUE_TEXT shall
only be used if TG has extended attributes.

For the ones defined in AVRCP spec these commands should not be called.
Since we do not have extended attributes yet we can ignore those
commands.
---
audio/control.c | 16 ++++++++++++++++
1 files changed, 16 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 2d6d9e6..36f727f 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -117,6 +117,8 @@
#define AVRCP_LIST_PLAYER_VALUES 0x12
#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13
#define AVRCP_SET_PLAYER_VALUE 0x14
+#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15
+#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -824,6 +826,20 @@ static int handle_vendordep_pdu(struct control *control,
}

break;
+ case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT:
+ case AVRCP_GET_PLAYER_VALUE_TEXT:
+ if (avrcp->code != CTYPE_STATUS)
+ break;
+
+ /*
+ * As per sec. 5.2.5 of AVRCP 1.3 spec, this command is
+ * expected to be used only for extended attributes, i.e.
+ * custom attributes defined by the application. As we
+ * currently don't have any such attribute, we respond with
+ * invalid param id.
+ */
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
}

/*
--
1.7.6


2011-07-19 19:49:17

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 06/19] avrcp: handle SetPlayerApplicationSettingValue pdu

---
audio/control.c | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 177 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 884e01c..2d6d9e6 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -116,6 +116,7 @@
#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11
#define AVRCP_LIST_PLAYER_VALUES 0x12
#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13
+#define AVRCP_SET_PLAYER_VALUE 0x14

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -492,6 +493,133 @@ static unsigned int setting_get_max_val(uint8_t setting)
return 0;
}

+static const char *setting_equalizer_to_string(enum equalizer_mode value)
+{
+ switch (value) {
+ case (EQUALIZER_MODE_ON):
+ return "on";
+ case (EQUALIZER_MODE_OFF):
+ return "off";
+ }
+
+ return NULL;
+}
+
+static const char *setting_repeat_to_string(enum repeat_mode value)
+{
+ switch (value) {
+ case (REPEAT_MODE_OFF):
+ return "off";
+ case (REPEAT_MODE_SINGLE):
+ return "singletrack";
+ case (REPEAT_MODE_ALL):
+ return "alltracks";
+ case (REPEAT_MODE_GROUP):
+ return "group";
+ }
+
+ return NULL;
+}
+
+static const char *setting_shuffle_to_string(enum shuffle_mode value)
+{
+ switch (value) {
+ case (SHUFFLE_MODE_OFF):
+ return "off";
+ case (SHUFFLE_MODE_ALL):
+ return "alltracks";
+ case (SHUFFLE_MODE_GROUP):
+ return "group";
+ }
+
+ return NULL;
+}
+
+static const char *setting_scan_to_string(enum scan_mode value)
+{
+ switch (value) {
+ case (SCAN_MODE_OFF):
+ return "off";
+ case (SCAN_MODE_ALL):
+ return "alltracks";
+ case (SCAN_MODE_GROUP):
+ return "group";
+ }
+
+ return NULL;
+}
+
+static const char *setting_value_to_string(enum player_setting setting,
+ uint8_t value)
+{
+ switch (setting) {
+ case (PLAYER_SETTING_EQUALIZER):
+ return setting_equalizer_to_string(value);
+ case(PLAYER_SETTING_REPEAT):
+ return setting_repeat_to_string(value);
+ case(PLAYER_SETTING_SHUFFLE):
+ return setting_shuffle_to_string(value);
+ case(PLAYER_SETTING_SCAN):
+ return setting_scan_to_string(value);
+ }
+
+ return NULL;
+}
+
+static const char *setting_to_string(enum player_setting setting)
+{
+ switch (setting) {
+ case (PLAYER_SETTING_EQUALIZER):
+ return "Equalizer";
+ case(PLAYER_SETTING_REPEAT):
+ return "Repeat";
+ case(PLAYER_SETTING_SHUFFLE):
+ return "Shuffle";
+ case(PLAYER_SETTING_SCAN):
+ return "Scan";
+ }
+
+ return NULL;
+}
+
+static void append_variant(DBusMessageIter *iter, int type, void *val)
+{
+ DBusMessageIter value;
+ char sig[2] = { type, '\0' };
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value);
+
+ dbus_message_iter_append_basic(&value, type, val);
+
+ dbus_message_iter_close_container(iter, &value);
+}
+
+static dbus_bool_t emit_setting_changed(DBusConnection *conn,
+ const char *path,
+ const char *interface,
+ const char *name,
+ int type, void *value)
+{
+ DBusMessage *signal;
+ DBusMessageIter iter;
+
+ signal = dbus_message_new_signal(path, interface, "SettingChanged");
+
+ if (!signal) {
+ error("Unable to allocate new %s.SettingChanged signal",
+ interface);
+ return FALSE;
+ }
+
+ dbus_message_iter_init_append(signal, &iter);
+
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name);
+
+ append_variant(&iter, type, value);
+
+ return g_dbus_send_message(conn, signal);
+}
+
/* handle vendordep pdu inside an avctp packet */
static int handle_vendordep_pdu(struct control *control,
struct avrcp_header *avrcp,
@@ -647,6 +775,55 @@ static int handle_vendordep_pdu(struct control *control,
}

break;
+ case AVRCP_SET_PLAYER_VALUE:
+ if (len > 2 && avrcp->code == CTYPE_CONTROL) {
+ len = 0;
+
+ /*
+ * From sec. 5.7 of AVRCP 1.3 spec, we should igore
+ * non-existent IDs and set the existent ones. Sec.
+ * 5.2.4 is not clear however how to indicate that a
+ * certain ID was not accepted. If at least one
+ * attribute we respond with no parameters. Otherwise
+ * an E_INVALID_PARAM is sent.
+ */
+ for (i = 0; i < pdu->params[0]; i++) {
+ uint8_t setting = pdu->params[i * 2 + 1];
+ uint8_t val = pdu->params[i * 2 + 2];
+ const char *settingstr;
+ const char *valstr;
+
+ settingstr = setting_to_string(setting);
+ if (!setting)
+ continue;
+
+ valstr = setting_value_to_string(setting, val);
+ if (!valstr)
+ continue;
+
+ len++;
+ control->player_setting[setting] = val;
+
+ emit_setting_changed(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ settingstr,
+ DBUS_TYPE_STRING, &valstr);
+ }
+
+ if (!len) {
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params_len = 0;
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH;
+ }
+
+ break;
}

/*
--
1.7.6


2011-07-19 19:49:16

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 05/19] avrcp: handle GetCurrentPlayerAplicationSettingValue pdu

---
audio/control.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 59 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index cd3beb7..884e01c 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -115,6 +115,7 @@
#define AVRCP_GET_CAPABILITIES 0x10
#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11
#define AVRCP_LIST_PLAYER_VALUES 0x12
+#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -588,6 +589,64 @@ static int handle_vendordep_pdu(struct control *control,

return AVRCP_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH + len + 1;
+ case AVRCP_GET_CURRENT_PLAYER_VALUE:
+ if (len > 1 && pdu->params[0] == len - 1 &&
+ avrcp->code == CTYPE_STATUS) {
+ uint8_t settings[pdu->params[0]];
+
+ memcpy(settings, &pdu->params[1], len);
+ len = 0;
+
+ /*
+ * From sec. 5.7 of AVRCP 1.3 spec, we should igore
+ * non-existent IDs and send a response with the
+ * existent ones. Only if all IDs are non-existent we
+ * should send an error.
+ */
+ for (i = 0; i < pdu->params[0]; i++) {
+ uint8_t val;
+
+ if (settings[i] < PLAYER_SETTING_EQUALIZER) {
+ DBG("Ignoring invalid setting %u",
+ settings[i]);
+ continue;
+ }
+
+ if (settings[i] > PLAYER_SETTING_SCAN) {
+ DBG("Ignoring invalid setting %u",
+ settings[i]);
+ continue;
+ }
+
+ val = control->player_setting[settings[i]];
+ if (!val) {
+ DBG("Ignoring setting %u since value"
+ "has never been set",
+ settings[i]);
+ continue;
+ }
+
+ pdu->params[len * 2 + 1] = settings[i];
+ pdu->params[len * 2 + 2] = val;
+ len++;
+ }
+
+ if (!len) {
+ error("GetCurrentPlayerValue: no valid "
+ "attribute found in request");
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params[0] = len;
+ pdu->params_len = htons(2 * len + 1);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + 2 * len + 1;
+ }
+
+ break;
}

/*
--
1.7.6


2011-07-19 19:49:15

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 04/19] avrcp: handle ListPlayerApplicationSettingValues pdu

---
audio/control.c | 37 +++++++++++++++++++++++++++++++++++++
1 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 56115df..cd3beb7 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -114,6 +114,7 @@
/* PDU types for metadata transfer */
#define AVRCP_GET_CAPABILITIES 0x10
#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11
+#define AVRCP_LIST_PLAYER_VALUES 0x12

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -474,6 +475,22 @@ static void handle_panel_passthrough(struct control *control,
operands[0] & 0x7F, status);
}

+static unsigned int setting_get_max_val(uint8_t setting)
+{
+ switch (setting) {
+ case (PLAYER_SETTING_EQUALIZER):
+ return EQUALIZER_MODE_ON;
+ case(PLAYER_SETTING_REPEAT):
+ return REPEAT_MODE_GROUP;
+ case(PLAYER_SETTING_SHUFFLE):
+ return SHUFFLE_MODE_GROUP;
+ case(PLAYER_SETTING_SCAN):
+ return SCAN_MODE_GROUP;
+ }
+
+ return 0;
+}
+
/* handle vendordep pdu inside an avctp packet */
static int handle_vendordep_pdu(struct control *control,
struct avrcp_header *avrcp,
@@ -551,6 +568,26 @@ static int handle_vendordep_pdu(struct control *control,

return AVRCP_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH + len + 1;
+ case AVRCP_LIST_PLAYER_VALUES:
+ if (len != 1 || avrcp->code != CTYPE_STATUS)
+ break;
+
+ len = setting_get_max_val(pdu->params[0]);
+ if (!len) {
+ error("Setting is invalid: %u", pdu->params[0]);
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ for (i = 1; i <= len; i++)
+ pdu->params[i] = i;
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params[0] = len;
+ pdu->params_len = htons(len + 1);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + len + 1;
}

/*
--
1.7.6


2011-07-19 19:49:14

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 03/19] avrcp: handle ListPlayerApplicationSettingAttributes pdu

---
audio/control.c | 22 ++++++++++++++++++++++
1 files changed, 22 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 905f57a..56115df 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -113,6 +113,7 @@

/* PDU types for metadata transfer */
#define AVRCP_GET_CAPABILITIES 0x10
+#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11

/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
@@ -529,6 +530,27 @@ static int handle_vendordep_pdu(struct control *control,

pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
+ case AVRCP_LIST_PLAYER_ATTRIBUTES:
+ if (len != 0 || avrcp->code != CTYPE_STATUS)
+ break;
+
+ for (i = 1; i <= PLAYER_SETTING_SCAN; i++) {
+ if (!control->player_setting[i]) {
+ DBG("Ignoring setting %u since value"
+ "has never never been set", i);
+ continue;
+ }
+
+ len++;
+ pdu->params[len] = i;
+ }
+
+ avrcp->code = CTYPE_STABLE;
+ pdu->params[0] = len;
+ pdu->params_len = htons(len + 1);
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + len + 1;
}

/*
--
1.7.6


2011-07-19 19:49:13

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 02/19] avrcp: implement ChangeSetting() method for TG role

ChangeSetting() is used by an application to communicate with the
bluetooth device in TG role in order to notify the change in a certain
setting.
---
audio/control.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++
doc/control-api.txt | 12 ++--
2 files changed, 159 insertions(+), 5 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 983c8cd..905f57a 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -120,6 +120,37 @@

#define QUIRK_NO_RELEASE 1 << 0

+enum player_setting {
+ PLAYER_SETTING_EQUALIZER = 1,
+ PLAYER_SETTING_REPEAT = 2,
+ PLAYER_SETTING_SHUFFLE = 3,
+ PLAYER_SETTING_SCAN = 4,
+};
+
+enum equalizer_mode {
+ EQUALIZER_MODE_OFF = 1,
+ EQUALIZER_MODE_ON = 2,
+};
+
+enum repeat_mode {
+ REPEAT_MODE_OFF = 1,
+ REPEAT_MODE_SINGLE = 2,
+ REPEAT_MODE_ALL = 3,
+ REPEAT_MODE_GROUP = 4,
+};
+
+enum shuffle_mode {
+ SHUFFLE_MODE_OFF = 1,
+ SHUFFLE_MODE_ALL = 2,
+ SHUFFLE_MODE_GROUP = 3,
+};
+
+enum scan_mode {
+ SCAN_MODE_OFF = 1,
+ SCAN_MODE_ALL = 2,
+ SCAN_MODE_GROUP = 3,
+};
+
static DBusConnection *connection = NULL;

static GSList *servers = NULL;
@@ -216,6 +247,8 @@ struct control {
gboolean target;

uint8_t key_quirks[256];
+
+ uint8_t player_setting[PLAYER_SETTING_SCAN + 1];
};

static struct {
@@ -1193,12 +1226,131 @@ static DBusMessage *control_get_properties(DBusConnection *conn,
return reply;
}

+static int setting_string_to_val(uint8_t setting, const char *value) {
+ int ret;
+
+ switch (setting) {
+ case (PLAYER_SETTING_EQUALIZER):
+ if (!strcmp(value, "off"))
+ ret = EQUALIZER_MODE_OFF;
+ else if (!strcmp(value, "on"))
+ ret = EQUALIZER_MODE_ON;
+ else
+ ret = -EINVAL;
+
+ return ret;
+ case(PLAYER_SETTING_REPEAT):
+ if (!strcmp(value, "off"))
+ ret = REPEAT_MODE_OFF;
+ else if (!strcmp(value, "singletrack"))
+ ret = REPEAT_MODE_SINGLE;
+ else if (!strcmp(value, "alltracks"))
+ ret = REPEAT_MODE_ALL;
+ else if (!strcmp(value, "group"))
+ ret = REPEAT_MODE_GROUP;
+ else
+ ret = -EINVAL;
+
+ return ret;
+ case(PLAYER_SETTING_SHUFFLE):
+ if (!strcmp(value, "off"))
+ ret = SHUFFLE_MODE_OFF;
+ else if (!strcmp(value, "alltracks"))
+ ret = SHUFFLE_MODE_ALL;
+ else if (!strcmp(value, "group"))
+ ret = SHUFFLE_MODE_GROUP;
+ else
+ ret = -EINVAL;
+
+ return ret;
+ case(PLAYER_SETTING_SCAN):
+ if (!strcmp(value, "off"))
+ ret = SCAN_MODE_OFF;
+ else if (!strcmp(value, "alltracks"))
+ ret = SCAN_MODE_ALL;
+ else if (!strcmp(value, "group"))
+ ret = SCAN_MODE_GROUP;
+ else
+ ret = -EINVAL;
+
+ return ret;
+ }
+
+ return -EINVAL;
+}
+
+static DBusMessage *setting_set(DBusMessage *msg, struct control *control,
+ const char *settingstr, const char *valstr)
+{
+ int val;
+ uint8_t setting;
+
+ if (!strcmp(settingstr, "Equalizer"))
+ setting = PLAYER_SETTING_EQUALIZER;
+ else if (!strcmp(settingstr, "Repeat"))
+ setting = PLAYER_SETTING_REPEAT;
+ else if (!strcmp(settingstr, "Shuffle"))
+ setting = PLAYER_SETTING_SHUFFLE;
+ else if (!strcmp(settingstr, "Scan"))
+ setting = PLAYER_SETTING_SCAN;
+ else
+ return btd_error_not_supported(msg);
+
+ val = setting_string_to_val(setting, valstr);
+ if (val == -1)
+ return btd_error_invalid_args(msg);
+
+ control->player_setting[setting] = val;
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *control_change_setting(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ DBusMessageIter iter;
+ DBusMessageIter var;
+ const char *setting;
+ const char *value;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return btd_error_not_connected(msg);
+
+ if (control->target) /* Only supported if this device is in TG role */
+ return btd_error_not_supported(msg);
+
+ if (!dbus_message_iter_init(msg, &iter))
+ return btd_error_invalid_args(msg);
+
+ if (!dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&iter, &setting);
+ dbus_message_iter_next(&iter);
+
+ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_recurse(&iter, &var);
+
+ /* Only string arguments are supported for now */
+ if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+
+ dbus_message_iter_get_basic(&var, &value);
+
+ return setting_set(msg, control, setting, value);
+}
+
static GDBusMethodTable control_methods[] = {
{ "IsConnected", "", "b", control_is_connected,
G_DBUS_METHOD_FLAG_DEPRECATED },
{ "GetProperties", "", "a{sv}",control_get_properties },
{ "VolumeUp", "", "", volume_up },
{ "VolumeDown", "", "", volume_down },
+ { "ChangeSetting", "sv", "", control_change_setting },
{ NULL, NULL, NULL, NULL }
};

diff --git a/doc/control-api.txt b/doc/control-api.txt
index 1a42846..931fa10 100644
--- a/doc/control-api.txt
+++ b/doc/control-api.txt
@@ -70,18 +70,20 @@ Methods void Connect()
void ChangeSetting(string setting, variant value)

Called to transmit Application Settings, CT Status
- and the like.
+ and the like. It's ONLY implemented for TG role. In
+ case they're never set, they will not be transmitted to
+ CT as a supported setting.

- Currenet defined settings are represented with the
+ Current defined settings are represented with the
following keys:

Equalizer off, on
Repeat off, singletrack, alltracks, group
Shuffle off, alltracks, group
Scan off, alltracks, group
- Battery normal, warning, critical, external, fullcharge
- System powered, unpowered, unplugged
- Volume uint8
+ Battery normal, warning, critical, external, fullcharge [ Not implemented]
+ System powered, unpowered, unplugged [ Not implemented ]
+ Volume uint8 [ Not implemented ]

Signals Connected()

--
1.7.6


2011-07-19 19:49:12

by Lucas De Marchi

[permalink] [raw]
Subject: [RFC 01/19] avrcp: handle query for company ids

---
audio/control.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index c3ef737..983c8cd 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -102,6 +102,22 @@
#define FORWARD_OP 0x4b
#define BACKWARD_OP 0x4c

+/* Company IDs for vendor dependent commands */
+#define IEEEID_BTSIG 0x001958
+
+/* Error codes for metadata transfer */
+#define E_INVALID_COMMAND 0x00
+#define E_INVALID_PARAM 0x01
+#define E_PARAM_NOT_FOUND 0x02
+#define E_INTERNAL 0x03
+
+/* PDU types for metadata transfer */
+#define AVRCP_GET_CAPABILITIES 0x10
+
+/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
+#define CAP_COMPANY_ID 0x02
+#define CAP_EVENTS_SUPPORTED 0x03
+
#define QUIRK_NO_RELEASE 1 << 0

static DBusConnection *connection = NULL;
@@ -217,6 +233,11 @@ static struct {
{ NULL }
};

+/* Company IDs supported by this device */
+static uint32_t company_ids[] = {
+ IEEEID_BTSIG,
+};
+
static GSList *avctp_callbacks = NULL;

static void auth_cb(DBusError *derr, void *user_data);
@@ -424,8 +445,71 @@ static int handle_vendordep_pdu(struct control *control,
struct avrcp_header *avrcp,
int operand_count)
{
- avrcp->code = CTYPE_NOT_IMPLEMENTED;
- return AVRCP_HEADER_LENGTH;
+ struct avrcp_spec_avc_pdu *pdu = (void *) avrcp + AVRCP_HEADER_LENGTH;
+ uint32_t company_id = (pdu->company_id[0] << 16) |
+ (pdu->company_id[1] << 8) |
+ (pdu->company_id[2]);
+ uint16_t len;
+ unsigned int i;
+
+ if (company_id != IEEEID_BTSIG ||
+ pdu->packet_type != AVCTP_PACKET_SINGLE) {
+ avrcp->code = CTYPE_NOT_IMPLEMENTED;
+ return AVRCP_HEADER_LENGTH;
+ }
+
+ pdu->packet_type = 0;
+ pdu->rsvd = 0;
+
+ if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH) {
+ pdu->params[0] = E_INVALID_COMMAND;
+ goto err_metadata;
+ }
+
+ len = ntohs(pdu->params_len);
+
+ switch (pdu->pdu_id) {
+ case AVRCP_GET_CAPABILITIES:
+ if (len != 1 || avrcp->code != CTYPE_STATUS)
+ break;
+
+ DBG("GET_CAPABILITIES id=%u", pdu->params[0]);
+
+ switch (pdu->params[0]) { /* capability id */
+ case CAP_COMPANY_ID:
+ avrcp->code = CTYPE_STABLE;
+ pdu->params_len = htons(1 +
+ 3 * G_N_ELEMENTS(company_ids));
+ pdu->params[1] = G_N_ELEMENTS(company_ids);
+
+ for (i = 0; i < G_N_ELEMENTS(company_ids); i++) {
+ pdu->params[2 + i * 3] = company_ids[i] >> 16;
+ pdu->params[3 + i * 3] = (company_ids[i] >> 8)
+ & 0xFF;
+ pdu->params[4 + i * 3] = company_ids[i] & 0xFF;
+ }
+
+ return AVRCP_HEADER_LENGTH +
+ AVRCP_SPECAVCPDU_HEADER_LENGTH + 1 +
+ 3 * G_N_ELEMENTS(company_ids);
+ }
+
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ /*
+ * If either pdu_id was invalid or message was malformed, respond with
+ * E_INVALID_COMMAND. For other errors, we already jumped into
+ * err_metadata.
+ */
+ pdu->params[0] = E_INVALID_COMMAND;
+
+err_metadata:
+ avrcp->code = CTYPE_REJECTED;
+ pdu->params_len = htons(1);
+
+ return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
}

static void avctp_disconnected(struct audio_device *dev)
--
1.7.6