2011-09-12 16:33:57

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH BlueZ 1/5 v2] AVRCP: use a vtable to simplify PDU parsing/handling

From: Luiz Augusto von Dentz <[email protected]>

This simplify a bit the handling by introducing common checks before
calling the handler callback, it is also much easier to add/remove
new PDUs in this way.
---
audio/control.c | 321 +++++++++++++++++++++----------------------------------
1 files changed, 124 insertions(+), 197 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 9990b06..f84c7f7 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -986,8 +986,9 @@ static void mp_set_media_attributes(struct control *control,
avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
}

-static int avrcp_handle_get_capabilities(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_get_capabilities(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
@@ -1008,31 +1009,35 @@ static int avrcp_handle_get_capabilities(struct control *control,
pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
pdu->params[1] = G_N_ELEMENTS(company_ids);

- return 2 + (3 * G_N_ELEMENTS(company_ids));
+ return CTYPE_STABLE;
case CAP_EVENTS_SUPPORTED:
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 4;
+ return CTYPE_STABLE;
}

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_list_player_attributes(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_list_player_attributes(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
struct media_player *mp = control->mp;
unsigned int i;

if (len != 0) {
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

if (!mp)
@@ -1052,11 +1057,12 @@ done:
pdu->params[0] = len;
pdu->params_len = htons(len + 1);

- return len + 1;
+ return CTYPE_STABLE;
}

-static int avrcp_handle_list_player_values(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_list_player_values(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
struct media_player *mp = control->mp;
@@ -1077,15 +1083,17 @@ static int avrcp_handle_list_player_values(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(len + 1);

- return len + 1;
+ return CTYPE_STABLE;

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_get_element_attributes(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_get_element_attributes(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
uint64_t *identifier = (void *) &pdu->params[0];
@@ -1145,14 +1153,16 @@ done:
pdu->params[0] = len;
pdu->params_len = htons(pos);

- return pos;
+ return CTYPE_STABLE;
err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_get_current_player_value(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_get_current_player_value(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
struct media_player *mp = control->mp;
@@ -1202,19 +1212,21 @@ static int avrcp_handle_get_current_player_value(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(2 * len + 1);

- return 2 * len + 1;
+ return CTYPE_STABLE;
}

error("No valid attributes in request");

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;

- return -EINVAL;
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_set_player_value(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_set_player_value(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
@@ -1256,16 +1268,38 @@ static int avrcp_handle_set_player_value(struct control *control,
if (len) {
pdu->params_len = 0;

- return 0;
+ return CTYPE_STABLE;
}

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_ct_battery_status(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_displayable_charset(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+
+ if (len < 3) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return CTYPE_REJECTED;
+ }
+
+ /*
+ * We acknowledge the commands, but we always use UTF-8 for
+ * encoding since CT is obliged to support it.
+ */
+ pdu->params_len = 0;
+ return CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_ct_battery_status(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
const char *valstr;
@@ -1282,15 +1316,17 @@ static int avrcp_handle_ct_battery_status(struct control *control,
DBUS_TYPE_STRING, &valstr);
pdu->params_len = 0;

- return 0;
+ return CTYPE_STABLE;

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

-static int avrcp_handle_get_play_status(struct control *control,
- struct avrcp_spec_avc_pdu *pdu)
+static uint8_t avrcp_handle_get_play_status(struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
uint32_t elapsed;
@@ -1298,8 +1334,9 @@ static int avrcp_handle_get_play_status(struct control *control,
uint8_t status;

if (len != 0) {
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

if (control->mp) {
@@ -1319,10 +1356,10 @@ static int avrcp_handle_get_play_status(struct control *control,

pdu->params_len = htons(9);

- return 9;
+ return CTYPE_STABLE;
}

-static int avrcp_handle_register_notification(struct control *control,
+static uint8_t avrcp_handle_register_notification(struct control *control,
struct avrcp_spec_avc_pdu *pdu,
uint8_t transaction)
{
@@ -1369,24 +1406,59 @@ static int avrcp_handle_register_notification(struct control *control,

pdu->params_len = htons(len);

- return len;
+ return CTYPE_INTERIM;

err:
+ pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return -EINVAL;
+ return CTYPE_REJECTED;
}

+static struct pdu_handler {
+ uint8_t pdu_id;
+ uint8_t code;
+ uint8_t (*func) (struct control *control,
+ struct avrcp_spec_avc_pdu *pdu,
+ uint8_t transaction);
+} handlers[] = {
+ { AVRCP_GET_CAPABILITIES, CTYPE_STATUS,
+ avrcp_handle_get_capabilities },
+ { AVRCP_LIST_PLAYER_ATTRIBUTES, CTYPE_STATUS,
+ avrcp_handle_list_player_attributes },
+ { AVRCP_LIST_PLAYER_VALUES, CTYPE_STATUS,
+ avrcp_handle_list_player_values },
+ { AVRCP_GET_ELEMENT_ATTRIBUTES, CTYPE_STATUS,
+ avrcp_handle_get_element_attributes },
+ { AVRCP_GET_CURRENT_PLAYER_VALUE, CTYPE_STATUS,
+ avrcp_handle_get_current_player_value },
+ { AVRCP_SET_PLAYER_VALUE, CTYPE_CONTROL,
+ avrcp_handle_set_player_value },
+ { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, CTYPE_STATUS,
+ NULL },
+ { AVRCP_GET_PLAYER_VALUE_TEXT, CTYPE_STATUS,
+ NULL },
+ { AVRCP_DISPLAYABLE_CHARSET, CTYPE_STATUS,
+ avrcp_handle_displayable_charset },
+ { AVRCP_CT_BATTERY_STATUS, CTYPE_STATUS,
+ avrcp_handle_ct_battery_status },
+ { AVRCP_GET_PLAY_STATUS, CTYPE_STATUS,
+ avrcp_handle_get_play_status },
+ { AVRCP_REGISTER_NOTIFICATION, CTYPE_NOTIFY,
+ avrcp_handle_register_notification },
+ { },
+};
+
/* 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)
{
+ struct pdu_handler *handler;
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]);
- int len;

if (company_id != IEEEID_BTSIG ||
pdu->packet_type != AVCTP_PACKET_SINGLE) {
@@ -1397,177 +1469,32 @@ static int handle_vendordep_pdu(struct control *control,
pdu->packet_type = 0;
pdu->rsvd = 0;

- if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH) {
- pdu->params[0] = E_INVALID_COMMAND;
+ if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH)
goto err_metadata;
- }
-
- switch (pdu->pdu_id) {
- case AVRCP_GET_CAPABILITIES:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_get_capabilities(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_LIST_PLAYER_ATTRIBUTES:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }

- len = avrcp_handle_list_player_attributes(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_LIST_PLAYER_VALUES:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_list_player_values(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_GET_ELEMENT_ATTRIBUTES:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_get_element_attributes(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_GET_CURRENT_PLAYER_VALUE:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_get_current_player_value(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_SET_PLAYER_VALUE:
- if (avrcp->code != CTYPE_CONTROL) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_set_player_value(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT:
- case AVRCP_GET_PLAYER_VALUE_TEXT:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
+ for (handler = handlers; handler; handler++) {
+ if (handler->pdu_id == pdu->pdu_id)
+ 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;
+ if (!handler || handler->code != avrcp->code) {
+ pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
- case AVRCP_DISPLAYABLE_CHARSET:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- if (pdu->params[0] < 3) {
- pdu->params[0] = E_INVALID_PARAM;
- goto err_metadata;
- }
-
- /*
- * We acknowledge the commands, but we always use UTF-8 for
- * encoding since CT is obliged to support it.
- */
- pdu->params_len = 0;
- avrcp->code = CTYPE_STABLE;
- len = 0;
-
- break;
- case AVRCP_CT_BATTERY_STATUS:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_ct_battery_status(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_GET_PLAY_STATUS:
- if (avrcp->code != CTYPE_STATUS) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_get_play_status(control, pdu);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_STABLE;
-
- break;
- case AVRCP_REGISTER_NOTIFICATION:
- if (avrcp->code != CTYPE_NOTIFY) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- len = avrcp_handle_register_notification(control, pdu,
- avctp->transaction);
- if (len < 0)
- goto err_metadata;
-
- avrcp->code = CTYPE_INTERIM;
+ }

- break;
- default:
- /* Invalid pdu_id */
- pdu->params[0] = E_INVALID_COMMAND;
+ if (!handler->func) {
+ pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
}

- return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + len;
+ avrcp->code = handler->func(control, pdu, avctp->transaction);
+
+ return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH +
+ ntohs(pdu->params_len);

err_metadata:
- avrcp->code = CTYPE_REJECTED;
pdu->params_len = htons(1);
+ avrcp->code = CTYPE_REJECTED;

return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
}
--
1.7.6.1



2011-09-27 08:30:23

by Johan Hedberg

[permalink] [raw]
Subject: Re: [PATCH BlueZ 1/5 v2] AVRCP: use a vtable to simplify PDU parsing/handling

Hi Luiz,

On Mon, Sep 12, 2011, Luiz Augusto von Dentz wrote:
> This simplify a bit the handling by introducing common checks before
> calling the handler callback, it is also much easier to add/remove
> new PDUs in this way.
> ---
> audio/control.c | 321 +++++++++++++++++++++----------------------------------
> 1 files changed, 124 insertions(+), 197 deletions(-)

All five patches applied. Thanks.

Johan

2011-09-12 18:01:58

by Lucas De Marchi

[permalink] [raw]
Subject: Re: [PATCH BlueZ 2/5 v2] AVRCP: rename avrcp_header to avc_header

Hi Luiz,

On Mon, Sep 12, 2011 at 1:33 PM, Luiz Augusto von Dentz
<[email protected]> wrote:
> From: Luiz Augusto von Dentz <[email protected]>
>
> AVCTP carries AV/C packets/PDUs not AVRCP as avrcp_header suggests.
> ---
> ?audio/control.c | ? 96 +++++++++++++++++++++++++++---------------------------
> ?1 files changed, 48 insertions(+), 48 deletions(-)

Ack

Lucas De Marchi

2011-09-12 17:57:17

by Lucas De Marchi

[permalink] [raw]
Subject: Re: [PATCH BlueZ 1/5 v2] AVRCP: use a vtable to simplify PDU parsing/handling

Hi Luiz

On Mon, Sep 12, 2011 at 1:33 PM, Luiz Augusto von Dentz
<[email protected]> wrote:
> From: Luiz Augusto von Dentz <[email protected]>
>
> This simplify a bit the handling by introducing common checks before
> calling the handler callback, it is also much easier to add/remove
> new PDUs in this way.
> ---
> ?audio/control.c | ?321 +++++++++++++++++++++----------------------------------
> ?1 files changed, 124 insertions(+), 197 deletions(-)
>

Ack.

Lucas De Marchi

2011-09-12 16:34:01

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH BlueZ 5/5 v2] AVRCP: move handling of vendor dependent PDU from control.c to avrcp.c

From: Luiz Augusto von Dentz <[email protected]>

This helps to isolate AVRCP PDU handling and MediaPlayer interface.
---
Makefile.am | 1 +
audio/avctp.c | 5 +
audio/avctp.h | 1 +
audio/avrcp.c | 1661 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
audio/avrcp.h | 34 ++
audio/control.c | 1636 +------------------------------------------------------
audio/control.h | 13 +-
audio/device.c | 4 +
audio/device.h | 2 +
audio/manager.c | 10 +-
10 files changed, 1720 insertions(+), 1647 deletions(-)
create mode 100644 audio/avrcp.c
create mode 100644 audio/avrcp.h

diff --git a/Makefile.am b/Makefile.am
index ef546d4..6988d03 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -144,6 +144,7 @@ builtin_sources += audio/main.c \
audio/headset.h audio/headset.c \
audio/control.h audio/control.c \
audio/avctp.h audio/avctp.c \
+ audio/avrcp.h audio/avrcp.c \
audio/device.h audio/device.c \
audio/source.h audio/source.c \
audio/sink.h audio/sink.c \
diff --git a/audio/avctp.c b/audio/avctp.c
index 75875bf..cf34107 100644
--- a/audio/avctp.c
+++ b/audio/avctp.c
@@ -1028,3 +1028,8 @@ void avctp_disconnect(struct avctp *session)

avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
}
+
+struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ return avctp_get_internal(src, dst);
+}
diff --git a/audio/avctp.h b/audio/avctp.h
index 157ecc6..2dab8fa 100644
--- a/audio/avctp.h
+++ b/audio/avctp.h
@@ -83,6 +83,7 @@ int avctp_register(const bdaddr_t *src, gboolean master);
void avctp_unregister(const bdaddr_t *src);

struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst);
+struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst);
void avctp_disconnect(struct avctp *session);

unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
diff --git a/audio/avrcp.c b/audio/avrcp.c
new file mode 100644
index 0000000..c9eae6e
--- /dev/null
+++ b/audio/avrcp.c
@@ -0,0 +1,1661 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * 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
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "log.h"
+#include "error.h"
+#include "device.h"
+#include "manager.h"
+#include "avctp.h"
+#include "avrcp.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "dbus-common.h"
+
+/* 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
+#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
+#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
+#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20
+#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
+
+/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
+#define CAP_COMPANY_ID 0x02
+#define CAP_EVENTS_SUPPORTED 0x03
+
+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,
+};
+
+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
+};
+
+enum battery_status {
+ BATTERY_STATUS_NORMAL = 0,
+ BATTERY_STATUS_WARNING = 1,
+ BATTERY_STATUS_CRITICAL = 2,
+ BATTERY_STATUS_EXTERNAL = 3,
+ BATTERY_STATUS_FULL_CHARGE = 4,
+};
+
+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,
+};
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avrcp_header {
+ uint8_t company_id[3];
+ uint8_t pdu_id;
+ uint8_t packet_type:2;
+ uint8_t rsvd:6;
+ uint16_t params_len;
+ uint8_t params[0];
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 7
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avrcp_header {
+ uint8_t company_id[3];
+ uint8_t pdu_id;
+ uint8_t rsvd:6;
+ uint8_t packet_type:2;
+ uint16_t params_len;
+ uint8_t params[0];
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 7
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avrcp_server {
+ bdaddr_t src;
+ uint32_t tg_record_id;
+ uint32_t ct_record_id;
+};
+
+struct media_info {
+ char *title;
+ char *artist;
+ char *album;
+ char *genre;
+ uint32_t ntracks;
+ uint32_t track;
+ uint32_t track_len;
+ uint32_t elapsed;
+};
+
+struct media_player {
+ struct avctp *session;
+ struct audio_device *dev;
+ uint8_t settings[PLAYER_SETTING_SCAN + 1];
+ enum play_status status;
+
+ struct media_info mi;
+ GTimer *timer;
+ unsigned int handler;
+ uint16_t registered_events;
+ uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
+};
+
+static GSList *servers = NULL;
+
+/* Company IDs supported by this device */
+static uint32_t company_ids[] = {
+ IEEEID_BTSIG,
+};
+
+static sdp_record_t *avrcp_ct_record(void)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrct;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ 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;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &avrct);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto[1] = sdp_list_append(0, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = avrcp_ver;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP CT", 0, 0);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static sdp_record_t *avrcp_tg_record(void)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrtg;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = AVCTP_PSM;
+ uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(0, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+ svclass_id = sdp_list_append(0, &avrtg);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(0, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(0, proto[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto[1] = sdp_list_append(0, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(0, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = avrcp_ver;
+ pfseq = sdp_list_append(0, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP TG", 0, 0);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], 0);
+ sdp_list_free(proto[1], 0);
+ sdp_list_free(apseq, 0);
+ sdp_list_free(aproto, 0);
+ sdp_list_free(pfseq, 0);
+ sdp_list_free(root, 0);
+ sdp_list_free(svclass_id, 0);
+
+ return record;
+}
+
+static unsigned int attr_get_max_val(uint8_t attr)
+{
+ switch (attr) {
+ 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;
+}
+
+static const char *attrval_to_str(uint8_t attr, uint8_t value)
+{
+ switch (attr) {
+ case PLAYER_SETTING_EQUALIZER:
+ switch (value) {
+ case EQUALIZER_MODE_ON:
+ return "on";
+ case EQUALIZER_MODE_OFF:
+ return "off";
+ }
+
+ break;
+ case PLAYER_SETTING_REPEAT:
+ 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";
+ }
+
+ break;
+ /* Shuffle and scan have the same values */
+ case PLAYER_SETTING_SHUFFLE:
+ case PLAYER_SETTING_SCAN:
+ switch (value) {
+ case SCAN_MODE_OFF:
+ return "off";
+ case SCAN_MODE_ALL:
+ return "alltracks";
+ case SCAN_MODE_GROUP:
+ return "group";
+ }
+
+ break;
+ }
+
+ return NULL;
+}
+
+static int attrval_to_val(uint8_t attr, const char *value)
+{
+ int ret;
+
+ switch (attr) {
+ 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 const char *attr_to_str(uint8_t attr)
+{
+ switch (attr) {
+ 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 int attr_to_val(const char *str)
+{
+ if (!strcmp(str, "Equalizer"))
+ return PLAYER_SETTING_EQUALIZER;
+ else if (!strcmp(str, "Repeat"))
+ return PLAYER_SETTING_REPEAT;
+ else if (!strcmp(str, "Shuffle"))
+ return PLAYER_SETTING_SHUFFLE;
+ else if (!strcmp(str, "Scan"))
+ return PLAYER_SETTING_SCAN;
+
+ return -EINVAL;
+}
+
+static int play_status_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 const char *battery_status_to_str(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 int avrcp_send_event(struct media_player *mp, uint8_t id, void *data)
+{
+ uint8_t buf[AVRCP_HEADER_LENGTH + 9];
+ struct avrcp_header *pdu = (void *) buf;
+ uint16_t size;
+ int err;
+
+ if (mp->session)
+ return -ENOTCONN;
+
+ if (!(mp->registered_events & (1 << id)))
+ return 0;
+
+ memset(buf, 0, sizeof(buf));
+
+ 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;
+
+ DBG("id=%u", id);
+
+ switch (id) {
+ case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
+ size = 2;
+ pdu->params[1] = *((uint8_t *)data);
+
+ 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);
+
+ err = avctp_send_vendordep(mp->session, mp->transaction_events[id],
+ AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+ buf, size);
+ if (err < 0)
+ return err;
+
+ /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
+ mp->registered_events ^= 1 << id;
+
+ return 0;
+}
+
+static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
+ uint32_t *elapsed, uint32_t *track_len)
+{
+ if (status)
+ *status = mp->status;
+ if (track_len)
+ *track_len = mp->mi.track_len;
+
+ if (!elapsed)
+ return;
+
+ *elapsed = mp->mi.elapsed;
+
+ if (mp->status == PLAY_STATUS_PLAYING) {
+ double timedelta = g_timer_elapsed(mp->timer, NULL);
+ uint32_t sec, msec;
+
+ sec = (uint32_t) timedelta;
+ msec = (uint32_t)((timedelta - sec) * 1000);
+
+ *elapsed += sec * 1000 + msec;
+ }
+}
+
+static void mp_set_playback_status(struct media_player *mp, uint8_t status,
+ uint32_t elapsed)
+{
+ DBG("Change playback: %u %u", status, elapsed);
+
+ mp->mi.elapsed = elapsed;
+ g_timer_start(mp->timer);
+
+ if (status == mp->status)
+ return;
+
+ mp->status = status;
+
+ avrcp_send_event(mp, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED, &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 @param id is not valid, -EINVAL is returned. If there's no such media
+ * attribute, -ENOENT is returned.
+ */
+static int mp_get_media_attribute(struct media_player *mp,
+ uint32_t id, uint8_t *buf)
+{
+ struct media_info_elem {
+ uint32_t id;
+ uint16_t charset;
+ uint16_t len;
+ uint8_t val[];
+ };
+ const struct media_info *mi = &mp->mi;
+ struct media_info_elem *elem = (void *)buf;
+ 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->elapsed != 0xFFFFFFFF) {
+ uint32_t elapsed;
+
+ mp_get_playback_status(mp, NULL, &elapsed, NULL);
+
+ snprintf(valstr, 20, "%u", elapsed);
+ len = strlen(valstr);
+ memcpy(elem->val, valstr, len);
+ } else {
+ return -ENOENT;
+ }
+
+ 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 mp_set_attribute(struct media_player *mp,
+ uint8_t attr, uint8_t val)
+{
+ DBG("Change attribute: %u %u", attr, val);
+
+ mp->settings[attr] = val;
+}
+
+static int mp_get_attribute(struct media_player *mp, uint8_t attr)
+{
+ DBG("Get attribute: %u", attr);
+
+ return mp->settings[attr];
+}
+
+static void mp_set_media_attributes(struct media_player *mp,
+ struct media_info *mi)
+{
+ g_free(mp->mi.title);
+ mp->mi.title = g_strdup(mi->title);
+
+ g_free(mp->mi.artist);
+ mp->mi.artist = g_strdup(mi->artist);
+
+ g_free(mp->mi.album);
+ mp->mi.album = g_strdup(mi->album);
+
+ g_free(mp->mi.genre);
+ mp->mi.genre = g_strdup(mi->genre);
+
+ mp->mi.ntracks = mi->ntracks;
+ mp->mi.track = mi->track;
+ mp->mi.track_len = mi->track_len;
+
+ /*
+ * elapsed is special. Whenever the track changes, we reset it to 0,
+ * so client doesn't have to make another call to change_playback
+ */
+ mp->mi.elapsed = 0;
+ g_timer_start(mp->timer);
+
+ DBG("Track changed:\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_len);
+
+ avrcp_send_event(mp, AVRCP_EVENT_TRACK_CHANGED, NULL);
+}
+
+static uint8_t avrcp_handle_get_capabilities(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ unsigned int i;
+
+ if (len != 1)
+ goto err;
+
+ DBG("id=%u", pdu->params[0]);
+
+ switch (pdu->params[0]) {
+ case CAP_COMPANY_ID:
+ 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;
+ }
+
+ pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
+ pdu->params[1] = G_N_ELEMENTS(company_ids);
+
+ return AVC_CTYPE_STABLE;
+ case CAP_EVENTS_SUPPORTED:
+ 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 AVC_CTYPE_STABLE;
+ }
+
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+
+ return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_list_player_attributes(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ unsigned int i;
+
+ if (len != 0) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+ }
+
+ if (!mp)
+ goto done;
+
+ for (i = 1; i <= PLAYER_SETTING_SCAN; i++) {
+ if (!mp_get_attribute(mp, i)) {
+ DBG("Ignoring setting %u: not supported by player", i);
+ continue;
+ }
+
+ len++;
+ pdu->params[len] = i;
+ }
+
+done:
+ pdu->params[0] = len;
+ pdu->params_len = htons(len + 1);
+
+ return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_list_player_values(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ unsigned int i;
+
+ if (len != 1 || !mp)
+ goto err;
+
+ len = attr_get_max_val(pdu->params[0]);
+ if (!len) {
+ error("Attribute is invalid: %u", pdu->params[0]);
+ goto err;
+ }
+
+ for (i = 1; i <= len; i++)
+ pdu->params[i] = i;
+
+ pdu->params[0] = len;
+ pdu->params_len = htons(len + 1);
+
+ return AVC_CTYPE_STABLE;
+
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ uint64_t *identifier = (void *) &pdu->params[0];
+ 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_CURRENT_POSITION; i++) {
+ size = mp_get_media_attribute(mp, i,
+ &pdu->params[pos]);
+
+ if (size > 0) {
+ len++;
+ pos += size;
+ }
+ }
+ } else {
+ uint32_t *attr_ids;
+
+ attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
+
+ for (i = 0; i < nattr; i++) {
+ uint32_t attr = ntohl(attr_ids[i]);
+
+ size = mp_get_media_attribute(mp, attr,
+ &pdu->params[pos]);
+
+ if (size > 0) {
+ len++;
+ pos += size;
+ }
+ }
+
+ g_free(attr_ids);
+
+ if (!len)
+ goto err;
+ }
+
+ pdu->params[0] = len;
+ pdu->params_len = htons(pos);
+
+ return AVC_CTYPE_STABLE;
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_get_current_player_value(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ uint8_t *settings;
+ unsigned int i;
+
+ if (mp == NULL || len <= 1 || pdu->params[0] != len - 1)
+ goto err;
+
+ /*
+ * Save a copy of requested settings because we can override them
+ * while responding
+ */
+ settings = g_malloc(pdu->params[0]);
+ memcpy(settings, &pdu->params[1], pdu->params[0]);
+ 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 ||
+ settings[i] > PLAYER_SETTING_SCAN) {
+ DBG("Ignoring %u", settings[i]);
+ continue;
+ }
+
+ val = mp_get_attribute(mp, settings[i]);
+ if (!val) {
+ DBG("Ignoring %u: not supported by player",
+ settings[i]);
+ continue;
+ }
+
+ pdu->params[len] = settings[i];
+ pdu->params[len + 1] = val;
+ len += 2;
+ }
+
+ g_free(settings);
+
+ if (len) {
+ pdu->params[0] = len;
+ pdu->params_len = htons(2 * len + 1);
+
+ return AVC_CTYPE_STABLE;
+ }
+
+ error("No valid attributes in request");
+
+err:
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+
+ return AVC_CTYPE_REJECTED;
+}
+
+static uint8_t avrcp_handle_set_player_value(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ unsigned int i;
+
+ if (len < 3)
+ goto err;
+
+ 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 is valid, we respond with no parameters. Otherwise an
+ * E_INVALID_PARAM is sent.
+ */
+ for (i = 1; i < pdu->params[0]; i += 2) {
+ uint8_t attr = pdu->params[i];
+ uint8_t val = pdu->params[i + 1];
+ const char *attrstr;
+ const char *valstr;
+
+ attrstr = attr_to_str(attr);
+ if (!attrstr)
+ continue;
+
+ valstr = attrval_to_str(attr, val);
+ if (!valstr)
+ continue;
+
+ len++;
+
+ mp_set_attribute(mp, attr, val);
+ emit_property_changed(mp->dev->conn, mp->dev->path,
+ MEDIA_PLAYER_INTERFACE, attrstr,
+ DBUS_TYPE_STRING, &valstr);
+ }
+
+ if (len) {
+ 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 uint8_t avrcp_handle_displayable_charset(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+
+ if (len < 3) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+ }
+
+ /*
+ * We acknowledge the commands, but we always use UTF-8 for
+ * encoding since CT is obliged to support it.
+ */
+ pdu->params_len = 0;
+ return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_ct_battery_status(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ const char *valstr;
+
+ if (len != 1)
+ goto err;
+
+ valstr = battery_status_to_str(pdu->params[0]);
+ if (valstr == NULL)
+ goto err;
+
+ emit_property_changed(mp->dev->conn, mp->dev->path,
+ MEDIA_PLAYER_INTERFACE, "Battery",
+ DBUS_TYPE_STRING, &valstr);
+ 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 uint8_t avrcp_handle_get_play_status(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ uint32_t elapsed;
+ uint32_t track_len;
+ uint8_t status;
+
+ if (len != 0) {
+ pdu->params_len = htons(1);
+ pdu->params[0] = E_INVALID_PARAM;
+ return AVC_CTYPE_REJECTED;
+ }
+
+ mp_get_playback_status(mp, &status, &elapsed, &track_len);
+ track_len = htonl(track_len);
+ elapsed = htonl(elapsed);
+
+ memcpy(&pdu->params[0], &track_len, 4);
+ memcpy(&pdu->params[4], &elapsed, 4);
+ pdu->params[8] = status;
+
+ pdu->params_len = htons(9);
+
+ return AVC_CTYPE_STABLE;
+}
+
+static uint8_t avrcp_handle_register_notification(struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction)
+{
+ uint16_t len = ntohs(pdu->params_len);
+ uint8_t status;
+
+ /*
+ * 1 byte for EventID, 4 bytes for Playback interval but the latest
+ * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP
+ * 1.3 spec, section 5.4.2.
+ */
+ if (len != 5)
+ goto err;
+
+ switch (pdu->params[0]) {
+ case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
+ len = 2;
+ mp_get_playback_status(mp, &status, NULL, NULL);
+ pdu->params[1] = status;
+
+ break;
+ case AVRCP_EVENT_TRACK_CHANGED:
+ len = 9;
+
+ memset(&pdu->params[1], 0, 8);
+
+ break;
+ default:
+ /* All other events are not supported yet */
+ goto err;
+ }
+
+ /* Register event and save the transaction used */
+ mp->registered_events |= (1 << pdu->params[0]);
+ mp->transaction_events[pdu->params[0]] = transaction;
+
+ pdu->params_len = htons(len);
+
+ return AVC_CTYPE_INTERIM;
+
+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;
+ uint8_t (*func) (struct media_player *mp,
+ struct avrcp_header *pdu,
+ uint8_t transaction);
+} handlers[] = {
+ { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
+ avrcp_handle_get_capabilities },
+ { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
+ avrcp_handle_list_player_attributes },
+ { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
+ avrcp_handle_list_player_values },
+ { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
+ avrcp_handle_get_element_attributes },
+ { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
+ avrcp_handle_get_current_player_value },
+ { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
+ avrcp_handle_set_player_value },
+ { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
+ NULL },
+ { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
+ NULL },
+ { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
+ avrcp_handle_displayable_charset },
+ { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
+ avrcp_handle_ct_battery_status },
+ { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
+ avrcp_handle_get_play_status },
+ { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
+ avrcp_handle_register_notification },
+ { },
+};
+
+/* handle vendordep pdu inside an avctp packet */
+static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
+ uint8_t *code, uint8_t *subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data)
+{
+ struct media_player *mp = user_data;
+ struct pdu_handler *handler;
+ struct avrcp_header *pdu = (void *) operands;
+ uint32_t company_id = (pdu->company_id[0] << 16) |
+ (pdu->company_id[1] << 8) |
+ (pdu->company_id[2]);
+
+ if (company_id != IEEEID_BTSIG) {
+ *code = AVC_CTYPE_NOT_IMPLEMENTED;
+ return 0;
+ }
+
+ DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
+ pdu->pdu_id, company_id, pdu->params_len);
+
+ pdu->packet_type = 0;
+ pdu->rsvd = 0;
+
+ if (operand_count + 3 < AVRCP_HEADER_LENGTH)
+ goto err_metadata;
+
+ for (handler = handlers; handler; handler++) {
+ if (handler->pdu_id == pdu->pdu_id)
+ break;
+ }
+
+ if (!handler || handler->code != *code) {
+ pdu->params[0] = E_INVALID_COMMAND;
+ goto err_metadata;
+ }
+
+ if (!handler->func) {
+ pdu->params[0] = E_INVALID_PARAM;
+ goto err_metadata;
+ }
+
+ *code = handler->func(mp, pdu, transaction);
+
+ return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
+
+err_metadata:
+ pdu->params_len = htons(1);
+ *code = AVC_CTYPE_REJECTED;
+
+ return AVRCP_HEADER_LENGTH + 1;
+}
+
+static void state_changed(struct audio_device *dev, avctp_state_t old_state,
+ avctp_state_t new_state, void *user_data)
+{
+ struct media_player *mp = dev->media_player;
+
+
+ if (!mp)
+ return;
+
+ switch (new_state) {
+ case AVCTP_STATE_DISCONNECTED:
+ mp->session = NULL;
+
+ if (mp->handler) {
+ avctp_unregister_pdu_handler(mp->handler);
+ mp->handler = 0;
+ }
+
+ break;
+ case AVCTP_STATE_CONNECTING:
+ mp->session = avctp_connect(&dev->src, &dev->dst);
+
+ if (!mp->handler)
+ mp->handler = avctp_register_pdu_handler(
+ AVC_OP_VENDORDEP,
+ handle_vendordep_pdu,
+ mp);
+ break;
+ default:
+ return;
+ }
+}
+
+static void media_info_init(struct media_info *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+
+ /*
+ * 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_len = 0xFFFFFFFF;
+ mi->elapsed = 0xFFFFFFFF;
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+ struct avctp *session;
+
+ session = avctp_connect(&dev->src, &dev->dst);
+ if (session)
+ return FALSE;
+
+ return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+ struct avctp *session;
+
+ session = avctp_get(&dev->src, &dev->dst);
+ if (!session)
+ return;
+
+ avctp_disconnect(session);
+}
+
+static unsigned int avctp_id = 0;
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+ sdp_record_t *record;
+ gboolean tmp, master = TRUE;
+ GError *err = NULL;
+ struct avrcp_server *server;
+
+ if (config) {
+ tmp = g_key_file_get_boolean(config, "General",
+ "Master", &err);
+ if (err) {
+ DBG("audio.conf: %s", err->message);
+ g_error_free(err);
+ } else
+ master = tmp;
+ }
+
+ server = g_new0(struct avrcp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ record = avrcp_tg_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ g_free(server);
+ return -1;
+ }
+
+ if (add_record_to_server(src, record) < 0) {
+ error("Unable to register AVRCP target service record");
+ g_free(server);
+ sdp_record_free(record);
+ return -1;
+ }
+ server->tg_record_id = record->handle;
+
+ record = avrcp_ct_record();
+ if (!record) {
+ error("Unable to allocate new service record");
+ g_free(server);
+ return -1;
+ }
+
+ if (add_record_to_server(src, record) < 0) {
+ error("Unable to register AVRCP mpler service record");
+ sdp_record_free(record);
+ g_free(server);
+ return -1;
+ }
+ server->ct_record_id = record->handle;
+
+ if (avctp_register(src, master) < 0) {
+ remove_record_from_server(server->ct_record_id);
+ remove_record_from_server(server->tg_record_id);
+ g_free(server);
+ return -1;
+ }
+
+ bacpy(&server->src, src);
+
+ servers = g_slist_append(servers, server);
+
+ return 0;
+}
+
+static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ for (; list; list = list->next) {
+ struct avrcp_server *server = list->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+void avrcp_unregister(const bdaddr_t *src)
+{
+ struct avrcp_server *server;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ servers = g_slist_remove(servers, server);
+
+ remove_record_from_server(server->ct_record_id);
+ remove_record_from_server(server->tg_record_id);
+
+ avctp_unregister(&server->src);
+ g_free(server);
+
+ if (servers)
+ return;
+
+ if (avctp_id)
+ avctp_remove_state_cb(avctp_id);
+}
+
+static DBusMessage *mp_set_property(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct media_player *mp = device->media_player;
+ DBusMessageIter iter;
+ DBusMessageIter var;
+ const char *attrstr, *valstr;
+ int attr, val;
+
+ 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, &attrstr);
+
+ attr = attr_to_val(attrstr);
+ if (attr < 0)
+ return btd_error_not_supported(msg);
+
+ 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, &valstr);
+
+ val = attrval_to_val(attr, valstr);
+ if (val < 0)
+ return btd_error_not_supported(msg);
+
+ mp_set_attribute(mp, attr, val);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *mp_change_playback(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct media_player *mp = device->media_player;
+ const char *statusstr;
+ int status;
+ uint32_t elapsed;
+
+ 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_to_val(statusstr);
+ if (status < 0)
+ return btd_error_invalid_args(msg);
+
+ mp_set_playback_status(mp, status, elapsed);
+
+ 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_len);
+ } else {
+ return FALSE;
+ }
+
+ dbus_message_iter_next(&dict);
+ }
+
+ if (mi->title == NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+static DBusMessage *mp_change_track(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct audio_device *device = data;
+ struct media_player *mp = device->media_player;
+ DBusMessageIter iter;
+ struct media_info mi;
+
+
+ dbus_message_iter_init(msg, &iter);
+ if (!media_info_parse(&iter, &mi))
+ return btd_error_invalid_args(msg);
+
+ mp_set_media_attributes(mp, &mi);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static GDBusMethodTable mp_methods[] = {
+ { "SetProperty", "sv", "", mp_set_property },
+ { "ChangePlayback", "su", "", mp_change_playback },
+ { "ChangeTrack", "a{sv}", "", mp_change_track },
+ { }
+};
+
+static GDBusSignalTable mp_signals[] = {
+ { "PropertyChanged", "sv" },
+ { }
+};
+
+static void mp_path_unregister(void *data)
+{
+ struct audio_device *dev = data;
+ struct media_player *mp = dev->media_player;
+
+ DBG("Unregistered interface %s on path %s",
+ MEDIA_PLAYER_INTERFACE, dev->path);
+
+ if (mp->handler)
+ avctp_unregister_pdu_handler(mp->handler);
+
+ g_timer_destroy(mp->timer);
+ g_free(mp);
+}
+
+void media_player_unregister(struct audio_device *dev)
+{
+ g_dbus_unregister_interface(dev->conn, dev->path,
+ MEDIA_PLAYER_INTERFACE);
+}
+
+struct media_player *media_player_init(struct audio_device *dev)
+{
+ struct media_player *mp;
+
+ if (!g_dbus_register_interface(dev->conn, dev->path,
+ MEDIA_PLAYER_INTERFACE,
+ mp_methods, mp_signals, NULL,
+ dev, mp_path_unregister)) {
+ error("D-Bus failed do register %s on path %s",
+ MEDIA_PLAYER_INTERFACE, dev->path);
+ return NULL;
+ }
+
+ DBG("Registered interface %s on path %s",
+ MEDIA_PLAYER_INTERFACE, dev->path);
+
+ mp = g_new0(struct media_player, 1);
+ mp->timer = g_timer_new();
+ mp->dev = dev;
+ media_info_init(&mp->mi);
+
+ if (!avctp_id)
+ avctp_id = avctp_add_state_cb(state_changed, NULL);
+
+ return mp;
+}
diff --git a/audio/avrcp.h b/audio/avrcp.h
new file mode 100644
index 0000000..1fd912d
--- /dev/null
+++ b/audio/avrcp.h
@@ -0,0 +1,34 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void avrcp_unregister(const bdaddr_t *src);
+
+gboolean avrcp_connect(struct audio_device *dev);
+void avrcp_disconnect(struct audio_device *dev);
+
+struct media_player *media_player_init(struct audio_device *dev);
+void media_player_unregister(struct audio_device *dev);
diff --git a/audio/control.c b/audio/control.c
index dceb004..a75e992 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -55,1245 +55,15 @@
#include "glib-helper.h"
#include "dbus-common.h"

-/* 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
-#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
-#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
-#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20
-#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
-
-/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
-#define CAP_COMPANY_ID 0x02
-#define CAP_EVENTS_SUPPORTED 0x03
-
-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,
-};
-
-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
-};
-
-enum battery_status {
- BATTERY_STATUS_NORMAL = 0,
- BATTERY_STATUS_WARNING = 1,
- BATTERY_STATUS_CRITICAL = 2,
- BATTERY_STATUS_EXTERNAL = 3,
- BATTERY_STATUS_FULL_CHARGE = 4,
-};
-
-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;
static unsigned int avctp_id = 0;

-#if __BYTE_ORDER == __LITTLE_ENDIAN
-
-struct avrcp_header {
- uint8_t company_id[3];
- uint8_t pdu_id;
- uint8_t packet_type:2;
- uint8_t rsvd:6;
- uint16_t params_len;
- uint8_t params[0];
-} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 7
-
-#elif __BYTE_ORDER == __BIG_ENDIAN
-
-struct avrcp_header {
- uint8_t company_id[3];
- uint8_t pdu_id;
- uint8_t rsvd:6;
- uint8_t packet_type:2;
- uint16_t params_len;
- uint8_t params[0];
-} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 7
-
-#else
-#error "Unknown byte order"
-#endif
-
-struct avrcp_server {
- bdaddr_t src;
- uint32_t tg_record_id;
- uint32_t ct_record_id;
-};
-
-struct media_info {
- char *title;
- char *artist;
- char *album;
- char *genre;
- uint32_t ntracks;
- uint32_t track;
- uint32_t track_len;
- uint32_t elapsed;
-};
-
-struct media_player {
- uint8_t settings[PLAYER_SETTING_SCAN + 1];
- enum play_status status;
-
- struct media_info mi;
- GTimer *timer;
- unsigned int handler;
-};
-
struct control {
struct audio_device *dev;
- struct media_player *mp;
struct avctp *session;

gboolean target;
-
- uint16_t registered_events;
- uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
};

-/* Company IDs supported by this device */
-static uint32_t company_ids[] = {
- IEEEID_BTSIG,
-};
-
-static sdp_record_t *avrcp_ct_record(void)
-{
- sdp_list_t *svclass_id, *pfseq, *apseq, *root;
- uuid_t root_uuid, l2cap, avctp, avrct;
- sdp_profile_desc_t profile[1];
- sdp_list_t *aproto, *proto[2];
- 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;
-
- record = sdp_record_alloc();
- if (!record)
- return NULL;
-
- sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
- root = sdp_list_append(0, &root_uuid);
- sdp_set_browse_groups(record, root);
-
- /* Service Class ID List */
- sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
- svclass_id = sdp_list_append(0, &avrct);
- sdp_set_service_classes(record, svclass_id);
-
- /* Protocol Descriptor List */
- sdp_uuid16_create(&l2cap, L2CAP_UUID);
- proto[0] = sdp_list_append(0, &l2cap);
- psm = sdp_data_alloc(SDP_UINT16, &lp);
- proto[0] = sdp_list_append(proto[0], psm);
- apseq = sdp_list_append(0, proto[0]);
-
- sdp_uuid16_create(&avctp, AVCTP_UUID);
- proto[1] = sdp_list_append(0, &avctp);
- version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
- proto[1] = sdp_list_append(proto[1], version);
- apseq = sdp_list_append(apseq, proto[1]);
-
- aproto = sdp_list_append(0, apseq);
- sdp_set_access_protos(record, aproto);
-
- /* Bluetooth Profile Descriptor List */
- sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
- profile[0].version = avrcp_ver;
- pfseq = sdp_list_append(0, &profile[0]);
- sdp_set_profile_descs(record, pfseq);
-
- features = sdp_data_alloc(SDP_UINT16, &feat);
- sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
-
- sdp_set_info_attr(record, "AVRCP CT", 0, 0);
-
- free(psm);
- free(version);
- sdp_list_free(proto[0], 0);
- sdp_list_free(proto[1], 0);
- sdp_list_free(apseq, 0);
- sdp_list_free(pfseq, 0);
- sdp_list_free(aproto, 0);
- sdp_list_free(root, 0);
- sdp_list_free(svclass_id, 0);
-
- return record;
-}
-
-static sdp_record_t *avrcp_tg_record(void)
-{
- sdp_list_t *svclass_id, *pfseq, *apseq, *root;
- uuid_t root_uuid, l2cap, avctp, avrtg;
- sdp_profile_desc_t profile[1];
- sdp_list_t *aproto, *proto[2];
- sdp_record_t *record;
- sdp_data_t *psm, *version, *features;
- uint16_t lp = AVCTP_PSM;
- uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
-
- record = sdp_record_alloc();
- if (!record)
- return NULL;
-
- sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
- root = sdp_list_append(0, &root_uuid);
- sdp_set_browse_groups(record, root);
-
- /* Service Class ID List */
- sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
- svclass_id = sdp_list_append(0, &avrtg);
- sdp_set_service_classes(record, svclass_id);
-
- /* Protocol Descriptor List */
- sdp_uuid16_create(&l2cap, L2CAP_UUID);
- proto[0] = sdp_list_append(0, &l2cap);
- psm = sdp_data_alloc(SDP_UINT16, &lp);
- proto[0] = sdp_list_append(proto[0], psm);
- apseq = sdp_list_append(0, proto[0]);
-
- sdp_uuid16_create(&avctp, AVCTP_UUID);
- proto[1] = sdp_list_append(0, &avctp);
- version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
- proto[1] = sdp_list_append(proto[1], version);
- apseq = sdp_list_append(apseq, proto[1]);
-
- aproto = sdp_list_append(0, apseq);
- sdp_set_access_protos(record, aproto);
-
- /* Bluetooth Profile Descriptor List */
- sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
- profile[0].version = avrcp_ver;
- pfseq = sdp_list_append(0, &profile[0]);
- sdp_set_profile_descs(record, pfseq);
-
- features = sdp_data_alloc(SDP_UINT16, &feat);
- sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
-
- sdp_set_info_attr(record, "AVRCP TG", 0, 0);
-
- free(psm);
- free(version);
- sdp_list_free(proto[0], 0);
- sdp_list_free(proto[1], 0);
- sdp_list_free(apseq, 0);
- sdp_list_free(aproto, 0);
- sdp_list_free(pfseq, 0);
- sdp_list_free(root, 0);
- sdp_list_free(svclass_id, 0);
-
- return record;
-}
-
-static unsigned int attr_get_max_val(uint8_t attr)
-{
- switch (attr) {
- 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;
-}
-
-static const char *attrval_to_str(uint8_t attr, uint8_t value)
-{
- switch (attr) {
- case PLAYER_SETTING_EQUALIZER:
- switch (value) {
- case EQUALIZER_MODE_ON:
- return "on";
- case EQUALIZER_MODE_OFF:
- return "off";
- }
-
- break;
- case PLAYER_SETTING_REPEAT:
- 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";
- }
-
- break;
- /* Shuffle and scan have the same values */
- case PLAYER_SETTING_SHUFFLE:
- case PLAYER_SETTING_SCAN:
- switch (value) {
- case SCAN_MODE_OFF:
- return "off";
- case SCAN_MODE_ALL:
- return "alltracks";
- case SCAN_MODE_GROUP:
- return "group";
- }
-
- break;
- }
-
- return NULL;
-}
-
-static int attrval_to_val(uint8_t attr, const char *value)
-{
- int ret;
-
- switch (attr) {
- 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 const char *attr_to_str(uint8_t attr)
-{
- switch (attr) {
- 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 int attr_to_val(const char *str)
-{
- if (!strcmp(str, "Equalizer"))
- return PLAYER_SETTING_EQUALIZER;
- else if (!strcmp(str, "Repeat"))
- return PLAYER_SETTING_REPEAT;
- else if (!strcmp(str, "Shuffle"))
- return PLAYER_SETTING_SHUFFLE;
- else if (!strcmp(str, "Scan"))
- return PLAYER_SETTING_SCAN;
-
- return -EINVAL;
-}
-
-static int play_status_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 const char *battery_status_to_str(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 int avrcp_send_event(struct control *control, uint8_t id, void *data)
-{
- uint8_t buf[AVRCP_HEADER_LENGTH + 9];
- struct avrcp_header *pdu = (void *) buf;
- uint16_t size;
- int err;
-
- if (control->session)
- return -ENOTCONN;
-
- if (!(control->registered_events & (1 << id)))
- return 0;
-
- memset(buf, 0, sizeof(buf));
-
- 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;
-
- DBG("id=%u", id);
-
- switch (id) {
- case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
- size = 2;
- pdu->params[1] = *((uint8_t *)data);
-
- 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);
-
- err = avctp_send_vendordep(control->session, control->transaction_events[id],
- AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
- buf, size);
- if (err < 0)
- return err;
-
- /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
- control->registered_events ^= 1 << id;
-
- return 0;
-}
-
-static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
- uint32_t *elapsed, uint32_t *track_len)
-{
- if (status)
- *status = mp->status;
- if (track_len)
- *track_len = mp->mi.track_len;
-
- if (!elapsed)
- return;
-
- *elapsed = mp->mi.elapsed;
-
- if (mp->status == PLAY_STATUS_PLAYING) {
- double timedelta = g_timer_elapsed(mp->timer, NULL);
- uint32_t sec, msec;
-
- sec = (uint32_t) timedelta;
- msec = (uint32_t)((timedelta - sec) * 1000);
-
- *elapsed += sec * 1000 + msec;
- }
-}
-
-static void mp_set_playback_status(struct control *control, uint8_t status,
- uint32_t elapsed)
-{
- struct media_player *mp = control->mp;
-
- DBG("Change playback: %u %u", status, elapsed);
-
- mp->mi.elapsed = elapsed;
- g_timer_start(mp->timer);
-
- if (status == mp->status)
- return;
-
- mp->status = status;
-
- avrcp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
- &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 @param id is not valid, -EINVAL is returned. If there's no such media
- * attribute, -ENOENT is returned.
- */
-static int mp_get_media_attribute(struct media_player *mp,
- uint32_t id, uint8_t *buf)
-{
- struct media_info_elem {
- uint32_t id;
- uint16_t charset;
- uint16_t len;
- uint8_t val[];
- };
- const struct media_info *mi = &mp->mi;
- struct media_info_elem *elem = (void *)buf;
- 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->elapsed != 0xFFFFFFFF) {
- uint32_t elapsed;
-
- mp_get_playback_status(mp, NULL, &elapsed, NULL);
-
- snprintf(valstr, 20, "%u", elapsed);
- len = strlen(valstr);
- memcpy(elem->val, valstr, len);
- } else {
- return -ENOENT;
- }
-
- 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 mp_set_attribute(struct media_player *mp,
- uint8_t attr, uint8_t val)
-{
- DBG("Change attribute: %u %u", attr, val);
-
- mp->settings[attr] = val;
-}
-
-static int mp_get_attribute(struct media_player *mp, uint8_t attr)
-{
- DBG("Get attribute: %u", attr);
-
- return mp->settings[attr];
-}
-
-static void mp_set_media_attributes(struct control *control,
- struct media_info *mi)
-{
- struct media_player *mp = control->mp;
-
- g_free(mp->mi.title);
- mp->mi.title = g_strdup(mi->title);
-
- g_free(mp->mi.artist);
- mp->mi.artist = g_strdup(mi->artist);
-
- g_free(mp->mi.album);
- mp->mi.album = g_strdup(mi->album);
-
- g_free(mp->mi.genre);
- mp->mi.genre = g_strdup(mi->genre);
-
- mp->mi.ntracks = mi->ntracks;
- mp->mi.track = mi->track;
- mp->mi.track_len = mi->track_len;
-
- /*
- * elapsed is special. Whenever the track changes, we reset it to 0,
- * so client doesn't have to make another call to change_playback
- */
- mp->mi.elapsed = 0;
- g_timer_start(mp->timer);
-
- DBG("Track changed:\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_len);
-
- avrcp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
-}
-
-static uint8_t avrcp_handle_get_capabilities(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- unsigned int i;
-
- if (len != 1)
- goto err;
-
- DBG("id=%u", pdu->params[0]);
-
- switch (pdu->params[0]) {
- case CAP_COMPANY_ID:
- 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;
- }
-
- pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
- pdu->params[1] = G_N_ELEMENTS(company_ids);
-
- return AVC_CTYPE_STABLE;
- case CAP_EVENTS_SUPPORTED:
- 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 AVC_CTYPE_STABLE;
- }
-
-err:
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
-
- return AVC_CTYPE_REJECTED;
-}
-
-static uint8_t avrcp_handle_list_player_attributes(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- struct media_player *mp = control->mp;
- unsigned int i;
-
- if (len != 0) {
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
- return AVC_CTYPE_REJECTED;
- }
-
- if (!mp)
- goto done;
-
- for (i = 1; i <= PLAYER_SETTING_SCAN; i++) {
- if (!mp_get_attribute(mp, i)) {
- DBG("Ignoring setting %u: not supported by player", i);
- continue;
- }
-
- len++;
- pdu->params[len] = i;
- }
-
-done:
- pdu->params[0] = len;
- pdu->params_len = htons(len + 1);
-
- return AVC_CTYPE_STABLE;
-}
-
-static uint8_t avrcp_handle_list_player_values(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- struct media_player *mp = control->mp;
- unsigned int i;
-
- if (len != 1 || !mp)
- goto err;
-
- len = attr_get_max_val(pdu->params[0]);
- if (!len) {
- error("Attribute is invalid: %u", pdu->params[0]);
- goto err;
- }
-
- for (i = 1; i <= len; i++)
- pdu->params[i] = i;
-
- pdu->params[0] = len;
- pdu->params_len = htons(len + 1);
-
- return AVC_CTYPE_STABLE;
-
-err:
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
- return AVC_CTYPE_REJECTED;
-}
-
-static uint8_t avrcp_handle_get_element_attributes(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- uint64_t *identifier = (void *) &pdu->params[0];
- uint16_t pos;
- uint8_t nattr;
- int size;
- unsigned int i;
-
- if (len < 8 || *identifier != 0 || !control->mp)
- goto err;
-
- len = 0;
- pos = 1; /* Keep track of current position in reponse */
- nattr = pdu->params[8];
-
- if (!control->mp)
- goto done;
-
- if (!nattr) {
- /*
- * Return all available information, at least
- * title must be returned.
- */
- for (i = 1; i <= MEDIA_INFO_CURRENT_POSITION; i++) {
- size = mp_get_media_attribute(control->mp, i,
- &pdu->params[pos]);
-
- if (size > 0) {
- len++;
- pos += size;
- }
- }
- } else {
- uint32_t *attr_ids;
-
- attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr);
-
- for (i = 0; i < nattr; i++) {
- uint32_t attr = ntohl(attr_ids[i]);
-
- size = mp_get_media_attribute(control->mp, attr,
- &pdu->params[pos]);
-
- if (size > 0) {
- len++;
- pos += size;
- }
- }
-
- g_free(attr_ids);
-
- if (!len)
- goto err;
- }
-
-done:
- pdu->params[0] = len;
- pdu->params_len = htons(pos);
-
- return AVC_CTYPE_STABLE;
-err:
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
- return AVC_CTYPE_REJECTED;
-}
-
-static uint8_t avrcp_handle_get_current_player_value(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- struct media_player *mp = control->mp;
- uint8_t *settings;
- unsigned int i;
-
- if (mp == NULL || len <= 1 || pdu->params[0] != len - 1)
- goto err;
-
- /*
- * Save a copy of requested settings because we can override them
- * while responding
- */
- settings = g_malloc(pdu->params[0]);
- memcpy(settings, &pdu->params[1], pdu->params[0]);
- 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 ||
- settings[i] > PLAYER_SETTING_SCAN) {
- DBG("Ignoring %u", settings[i]);
- continue;
- }
-
- val = mp_get_attribute(mp, settings[i]);
- if (!val) {
- DBG("Ignoring %u: not supported by player",
- settings[i]);
- continue;
- }
-
- pdu->params[len] = settings[i];
- pdu->params[len + 1] = val;
- len += 2;
- }
-
- g_free(settings);
-
- if (len) {
- pdu->params[0] = len;
- pdu->params_len = htons(2 * len + 1);
-
- return AVC_CTYPE_STABLE;
- }
-
- error("No valid attributes in request");
-
-err:
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
-
- return AVC_CTYPE_REJECTED;
-}
-
-static uint8_t avrcp_handle_set_player_value(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- unsigned int i;
-
- if (len < 3 || !control->mp)
- goto err;
-
- 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 is valid, we respond with no parameters. Otherwise an
- * E_INVALID_PARAM is sent.
- */
- for (i = 1; i < pdu->params[0]; i += 2) {
- uint8_t attr = pdu->params[i];
- uint8_t val = pdu->params[i + 1];
- const char *attrstr;
- const char *valstr;
-
- attrstr = attr_to_str(attr);
- if (!attrstr)
- continue;
-
- valstr = attrval_to_str(attr, val);
- if (!valstr)
- continue;
-
- len++;
-
- mp_set_attribute(control->mp, attr, val);
- emit_property_changed(control->dev->conn, control->dev->path,
- MEDIA_PLAYER_INTERFACE, attrstr,
- DBUS_TYPE_STRING, &valstr);
- }
-
- if (len) {
- 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 uint8_t avrcp_handle_displayable_charset(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
-
- if (len < 3) {
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
- return AVC_CTYPE_REJECTED;
- }
-
- /*
- * We acknowledge the commands, but we always use UTF-8 for
- * encoding since CT is obliged to support it.
- */
- pdu->params_len = 0;
- return AVC_CTYPE_STABLE;
-}
-
-static uint8_t avrcp_handle_ct_battery_status(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- const char *valstr;
-
- if (len != 1)
- goto err;
-
- valstr = battery_status_to_str(pdu->params[0]);
- if (valstr == NULL)
- goto err;
-
- emit_property_changed(control->dev->conn, control->dev->path,
- MEDIA_PLAYER_INTERFACE, "Battery",
- DBUS_TYPE_STRING, &valstr);
- 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 uint8_t avrcp_handle_get_play_status(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- uint32_t elapsed;
- uint32_t track_len;
- uint8_t status;
-
- if (len != 0) {
- pdu->params_len = htons(1);
- pdu->params[0] = E_INVALID_PARAM;
- return AVC_CTYPE_REJECTED;
- }
-
- if (control->mp) {
- mp_get_playback_status(control->mp, &status,
- &elapsed, &track_len);
- track_len = htonl(track_len);
- elapsed = htonl(elapsed);
- } else {
- track_len = 0xFFFFFFFF;
- elapsed = 0xFFFFFFFF;
- status = PLAY_STATUS_ERROR;
- }
-
- memcpy(&pdu->params[0], &track_len, 4);
- memcpy(&pdu->params[4], &elapsed, 4);
- pdu->params[8] = status;
-
- pdu->params_len = htons(9);
-
- return AVC_CTYPE_STABLE;
-}
-
-static uint8_t avrcp_handle_register_notification(struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction)
-{
- uint16_t len = ntohs(pdu->params_len);
- uint8_t status;
-
- /*
- * 1 byte for EventID, 4 bytes for Playback interval but the latest
- * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP
- * 1.3 spec, section 5.4.2.
- */
- if (len != 5)
- goto err;
-
- switch (pdu->params[0]) {
- case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
- len = 2;
- if (control->mp) {
- mp_get_playback_status(control->mp, &status,
- NULL, NULL);
- pdu->params[1] = status;
- } else {
- pdu->params[1] = PLAY_STATUS_ERROR;
- }
-
- break;
- case AVRCP_EVENT_TRACK_CHANGED:
- len = 9;
-
- if (!control->mp)
- memset(&pdu->params[1], 0xFF, 8);
- else
- memset(&pdu->params[1], 0, 8);
-
- break;
- default:
- /* All other events are not supported yet */
- goto err;
- }
-
- /* Register event and save the transaction used */
- control->registered_events |= (1 << pdu->params[0]);
- control->transaction_events[pdu->params[0]] = transaction;
-
- pdu->params_len = htons(len);
-
- return AVC_CTYPE_INTERIM;
-
-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;
- uint8_t (*func) (struct control *control,
- struct avrcp_header *pdu,
- uint8_t transaction);
-} handlers[] = {
- { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
- avrcp_handle_get_capabilities },
- { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
- avrcp_handle_list_player_attributes },
- { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
- avrcp_handle_list_player_values },
- { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
- avrcp_handle_get_element_attributes },
- { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
- avrcp_handle_get_current_player_value },
- { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
- avrcp_handle_set_player_value },
- { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
- NULL },
- { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
- NULL },
- { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
- avrcp_handle_displayable_charset },
- { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
- avrcp_handle_ct_battery_status },
- { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
- avrcp_handle_get_play_status },
- { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
- avrcp_handle_register_notification },
- { },
-};
-
-/* handle vendordep pdu inside an avctp packet */
-static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
- uint8_t *code, uint8_t *subunit,
- uint8_t *operands, size_t operand_count,
- void *user_data)
-{
- struct control *control = user_data;
- struct pdu_handler *handler;
- struct avrcp_header *pdu = (void *) operands;
- uint32_t company_id = (pdu->company_id[0] << 16) |
- (pdu->company_id[1] << 8) |
- (pdu->company_id[2]);
-
- if (company_id != IEEEID_BTSIG) {
- *code = AVC_CTYPE_NOT_IMPLEMENTED;
- return 0;
- }
-
- DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
- pdu->pdu_id, company_id, pdu->params_len);
-
- pdu->packet_type = 0;
- pdu->rsvd = 0;
-
- if (operand_count + 3 < AVRCP_HEADER_LENGTH)
- goto err_metadata;
-
- for (handler = handlers; handler; handler++) {
- if (handler->pdu_id == pdu->pdu_id)
- break;
- }
-
- if (!handler || handler->code != *code) {
- pdu->params[0] = E_INVALID_COMMAND;
- goto err_metadata;
- }
-
- if (!handler->func) {
- pdu->params[0] = E_INVALID_PARAM;
- goto err_metadata;
- }
-
- *code = handler->func(control, pdu, transaction);
-
- return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
-
-err_metadata:
- pdu->params_len = htons(1);
- *code = AVC_CTYPE_REJECTED;
-
- return AVRCP_HEADER_LENGTH + 1;
-}
-
static void state_changed(struct audio_device *dev, avctp_state_t old_state,
avctp_state_t new_state, void *user_data)
{
@@ -1304,11 +74,6 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state,
case AVCTP_STATE_DISCONNECTED:
control->session = NULL;

- if (control->mp && control->mp->handler) {
- avctp_unregister_pdu_handler(control->mp->handler);
- control->mp->handler = 0;
- }
-
if (old_state != AVCTP_STATE_CONNECTED)
break;

@@ -1325,14 +90,8 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state,
if (control->session)
break;

- control->session = avctp_connect(&dev->src, &dev->dst);
- if (!control->mp)
- break;
+ control->session = avctp_get(&dev->src, &dev->dst);

- control->mp->handler = avctp_register_pdu_handler(
- AVC_OP_VENDORDEP,
- handle_vendordep_pdu,
- control);
break;
case AVCTP_STATE_CONNECTED:
value = TRUE;
@@ -1348,149 +107,6 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state,
}
}

-static void media_info_init(struct media_info *mi)
-{
- memset(mi, 0, sizeof(*mi));
-
- /*
- * 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_len = 0xFFFFFFFF;
- mi->elapsed = 0xFFFFFFFF;
-}
-
-gboolean avrcp_connect(struct audio_device *dev)
-{
- struct control *control = dev->control;
-
- if (control->session)
- return TRUE;
-
- control->session = avctp_connect(&dev->src, &dev->dst);
- if (!control->session)
- return FALSE;
-
- return TRUE;
-}
-
-void avrcp_disconnect(struct audio_device *dev)
-{
- struct control *control = dev->control;
-
- if (!(control && control->session))
- return;
-
- avctp_disconnect(control->session);
-}
-
-int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
-{
- sdp_record_t *record;
- gboolean tmp, master = TRUE;
- GError *err = NULL;
- struct avrcp_server *server;
-
- if (config) {
- tmp = g_key_file_get_boolean(config, "General",
- "Master", &err);
- if (err) {
- DBG("audio.conf: %s", err->message);
- g_error_free(err);
- } else
- master = tmp;
- }
-
- server = g_new0(struct avrcp_server, 1);
- if (!server)
- return -ENOMEM;
-
- if (!connection)
- connection = dbus_connection_ref(conn);
-
- record = avrcp_tg_record();
- if (!record) {
- error("Unable to allocate new service record");
- g_free(server);
- return -1;
- }
-
- if (add_record_to_server(src, record) < 0) {
- error("Unable to register AVRCP target service record");
- g_free(server);
- sdp_record_free(record);
- return -1;
- }
- server->tg_record_id = record->handle;
-
- record = avrcp_ct_record();
- if (!record) {
- error("Unable to allocate new service record");
- g_free(server);
- return -1;
- }
-
- if (add_record_to_server(src, record) < 0) {
- error("Unable to register AVRCP controller service record");
- sdp_record_free(record);
- g_free(server);
- return -1;
- }
- server->ct_record_id = record->handle;
-
- if (avctp_register(src, master) < 0) {
- remove_record_from_server(server->ct_record_id);
- remove_record_from_server(server->tg_record_id);
- g_free(server);
- return -1;
- }
-
- bacpy(&server->src, src);
-
- servers = g_slist_append(servers, server);
-
- return 0;
-}
-
-static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
-{
- for (; list; list = list->next) {
- struct avrcp_server *server = list->data;
-
- if (bacmp(&server->src, src) == 0)
- return server;
- }
-
- return NULL;
-}
-
-void avrcp_unregister(const bdaddr_t *src)
-{
- struct avrcp_server *server;
-
- server = find_server(servers, src);
- if (!server)
- return;
-
- servers = g_slist_remove(servers, server);
-
- remove_record_from_server(server->ct_record_id);
- remove_record_from_server(server->tg_record_id);
-
- avctp_unregister(&server->src);
- g_free(server);
-
- if (servers)
- return;
-
- if (avctp_id)
- avctp_remove_state_cb(avctp_id);
-
- dbus_connection_unref(connection);
- connection = NULL;
-}
-
static DBusMessage *control_is_connected(DBusConnection *conn,
DBusMessage *msg,
void *data)
@@ -1597,191 +213,6 @@ static GDBusSignalTable control_signals[] = {
{ NULL, NULL }
};

-static DBusMessage *mp_set_property(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct control *control = device->control;
- DBusMessageIter iter;
- DBusMessageIter var;
- const char *attrstr, *valstr;
- int attr, val;
-
- 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, &attrstr);
-
- attr = attr_to_val(attrstr);
- if (attr < 0)
- return btd_error_not_supported(msg);
-
- 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, &valstr);
-
- val = attrval_to_val(attr, valstr);
- if (val < 0)
- return btd_error_not_supported(msg);
-
- mp_set_attribute(control->mp, attr, val);
-
- return dbus_message_new_method_return(msg);
-}
-
-static DBusMessage *mp_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 (!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_to_val(statusstr);
- if (status < 0)
- return btd_error_invalid_args(msg);
-
- mp_set_playback_status(control, status, elapsed);
-
- 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_len);
- } else {
- return FALSE;
- }
-
- dbus_message_iter_next(&dict);
- }
-
- if (mi->title == NULL)
- return FALSE;
-
- return TRUE;
-}
-
-static DBusMessage *mp_change_track(DBusConnection *conn,
- DBusMessage *msg, void *data)
-{
- struct audio_device *device = data;
- struct control *control = device->control;
- DBusMessageIter iter;
- struct media_info mi;
-
-
- dbus_message_iter_init(msg, &iter);
- if (!media_info_parse(&iter, &mi))
- return btd_error_invalid_args(msg);
-
- mp_set_media_attributes(control, &mi);
-
- return dbus_message_new_method_return(msg);
-}
-
-static GDBusMethodTable mp_methods[] = {
- { "SetProperty", "sv", "", mp_set_property },
- { "ChangePlayback", "su", "", mp_change_playback },
- { "ChangeTrack", "a{sv}", "", mp_change_track },
- { }
-};
-
-static GDBusSignalTable mp_signals[] = {
- { "PropertyChanged", "sv" },
- { }
-};
-
static void path_unregister(void *data)
{
struct audio_device *dev = data;
@@ -1797,78 +228,19 @@ static void path_unregister(void *data)
dev->control = NULL;
}

-static void mp_path_unregister(void *data)
-{
- struct audio_device *dev = data;
- struct control *control = dev->control;
- struct media_player *mp = control->mp;
-
- DBG("Unregistered interface %s on path %s",
- MEDIA_PLAYER_INTERFACE, dev->path);
-
- if (mp->handler)
- avctp_unregister_pdu_handler(mp->handler);
-
- g_timer_destroy(mp->timer);
- g_free(mp);
- control->mp = NULL;
-}
-
-static void mp_unregister(struct control *control)
-{
- struct audio_device *dev = control->dev;
-
- g_dbus_unregister_interface(dev->conn, dev->path,
- MEDIA_PLAYER_INTERFACE);
-}
-
void control_unregister(struct audio_device *dev)
{
- struct control *control = dev->control;
-
- if (control->mp)
- mp_unregister(control);
-
g_dbus_unregister_interface(dev->conn, dev->path,
AUDIO_CONTROL_INTERFACE);
}

-static void mp_register(struct control *control)
-{
- struct audio_device *dev = control->dev;
- struct media_player *mp;
-
- mp = g_new0(struct media_player, 1);
-
- if (!g_dbus_register_interface(dev->conn, dev->path,
- MEDIA_PLAYER_INTERFACE,
- mp_methods, mp_signals, NULL,
- dev, mp_path_unregister)) {
- error("D-Bus failed do register %s on path %s",
- MEDIA_PLAYER_INTERFACE, dev->path);
- g_free(mp);
- return;
- }
-
- DBG("Registered interface %s on path %s",
- MEDIA_PLAYER_INTERFACE, dev->path);
-
- mp->timer = g_timer_new();
- media_info_init(&mp->mi);
- control->mp = mp;
-}
-
-void control_update(struct control *control, uint16_t uuid16,
- gboolean media_player)
+void control_update(struct control *control, uint16_t uuid16)
{
if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
control->target = TRUE;
- else if (media_player && !control->mp)
- mp_register(control);
}

-struct control *control_init(struct audio_device *dev, uint16_t uuid16,
- gboolean media_player)
+struct control *control_init(struct audio_device *dev, uint16_t uuid16)
{
struct control *control;

@@ -1884,7 +256,7 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16,
control = g_new0(struct control, 1);
control->dev = dev;

- control_update(control, uuid16, media_player);
+ control_update(control, uuid16);

if (!avctp_id)
avctp_id = avctp_add_state_cb(state_changed, NULL);
diff --git a/audio/control.h b/audio/control.h
index f5cfef2..2219e5f 100644
--- a/audio/control.h
+++ b/audio/control.h
@@ -23,17 +23,8 @@
*/

#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
-#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"

-int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
-void avrcp_unregister(const bdaddr_t *src);
-
-gboolean avrcp_connect(struct audio_device *dev);
-void avrcp_disconnect(struct audio_device *dev);
-
-struct control *control_init(struct audio_device *dev, uint16_t uuid16,
- gboolean media_player);
-void control_update(struct control *control, uint16_t uuid16,
- gboolean media_player);
+struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+void control_update(struct control *control, uint16_t uuid16);
void control_unregister(struct audio_device *dev);
gboolean control_is_active(struct audio_device *dev);
diff --git a/audio/device.c b/audio/device.c
index 16f0701..9ec6fc4 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -53,6 +53,7 @@
#include "avdtp.h"
#include "control.h"
#include "avctp.h"
+#include "avrcp.h"
#include "headset.h"
#include "gateway.h"
#include "sink.h"
@@ -752,6 +753,9 @@ void audio_device_unregister(struct audio_device *device)
if (device->control)
control_unregister(device);

+ if (device->media_player)
+ media_player_unregister(device);
+
g_dbus_unregister_interface(device->conn, device->path,
AUDIO_INTERFACE);

diff --git a/audio/device.h b/audio/device.h
index 5671f77..3975108 100644
--- a/audio/device.h
+++ b/audio/device.h
@@ -44,6 +44,7 @@ struct target;
struct sink;
struct headset;
struct gateway;
+struct media_player;
struct dev_priv;

struct audio_device {
@@ -63,6 +64,7 @@ struct audio_device {
struct source *source;
struct control *control;
struct target *target;
+ struct media_player *media_player;

guint hs_preauth_id;

diff --git a/audio/manager.c b/audio/manager.c
index 880872f..dd4fa6b 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -63,6 +63,7 @@
#include "gateway.h"
#include "sink.h"
#include "source.h"
+#include "avrcp.h"
#include "control.h"
#include "manager.h"
#include "sdpd.h"
@@ -220,11 +221,12 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device)
DBG("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ?
"Remote" : "Target");
if (device->control)
- control_update(device->control, uuid16,
- enabled.media_player);
+ control_update(device->control, uuid16);
else
- device->control = control_init(device, uuid16,
- enabled.media_player);
+ device->control = control_init(device, uuid16);
+
+ if (enabled.media_player && !device->media_player)
+ device->media_player = media_player_init(device);
if (device->sink && sink_is_active(device))
avrcp_connect(device);
break;
--
1.7.6.1


2011-09-12 16:34:00

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH BlueZ 4/5 v2] AVRCP: split AVCTP specific code from control.c

From: Luiz Augusto von Dentz <[email protected]>

The code is moved to avctp.c to simplify control.c
---
Makefile.am | 1 +
audio/avctp.c | 1030 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
audio/avctp.h | 95 +++++
audio/control.c | 875 +++++++----------------------------------------
audio/control.h | 14 -
audio/device.c | 1 +
6 files changed, 1244 insertions(+), 772 deletions(-)
create mode 100644 audio/avctp.c
create mode 100644 audio/avctp.h

diff --git a/Makefile.am b/Makefile.am
index f4113b1..ef546d4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -143,6 +143,7 @@ builtin_sources += audio/main.c \
audio/gateway.h audio/gateway.c \
audio/headset.h audio/headset.c \
audio/control.h audio/control.c \
+ audio/avctp.h audio/avctp.c \
audio/device.h audio/device.c \
audio/source.h audio/source.c \
audio/sink.h audio/sink.c \
diff --git a/audio/avctp.c b/audio/avctp.c
new file mode 100644
index 0000000..75875bf
--- /dev/null
+++ b/audio/avctp.c
@@ -0,0 +1,1030 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * 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
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+
+#include "adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "error.h"
+#include "uinput.h"
+#include "btio.h"
+#include "manager.h"
+#include "device.h"
+#include "avctp.h"
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+/* Message types */
+#define AVCTP_COMMAND 0
+#define AVCTP_RESPONSE 1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE 0
+#define AVCTP_PACKET_START 1
+#define AVCTP_PACKET_CONTINUE 2
+#define AVCTP_PACKET_END 3
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+ uint8_t ipid:1;
+ uint8_t cr:1;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+ uint8_t code:4;
+ uint8_t _hdr0:4;
+ uint8_t subunit_id:3;
+ uint8_t subunit_type:5;
+ uint8_t opcode;
+} __attribute__ ((packed));
+#define AVC_HEADER_LENGTH 3
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t cr:1;
+ uint8_t ipid:1;
+ uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+ uint8_t _hdr0:4;
+ uint8_t code:4;
+ uint8_t subunit_type:5;
+ uint8_t subunit_id:3;
+ uint8_t opcode;
+} __attribute__ ((packed));
+#define AVC_HEADER_LENGTH 3
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+ avctp_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avctp_server {
+ bdaddr_t src;
+ GIOChannel *io;
+ GSList *sessions;
+};
+
+struct avctp {
+ struct avctp_server *server;
+ bdaddr_t dst;
+
+ avctp_state_t state;
+
+ int uinput;
+
+ GIOChannel *io;
+ guint io_id;
+
+ uint16_t mtu;
+
+ uint8_t key_quirks[256];
+};
+
+struct avctp_pdu_handler {
+ uint8_t opcode;
+ avctp_pdu_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+static struct {
+ const char *name;
+ uint8_t avc;
+ uint16_t uinput;
+} key_map[] = {
+ { "PLAY", PLAY_OP, KEY_PLAYCD },
+ { "STOP", STAVC_OP_OP, KEY_STOPCD },
+ { "PAUSE", PAUSE_OP, KEY_PAUSECD },
+ { "FORWARD", FORWARD_OP, KEY_NEXTSONG },
+ { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
+ { "REWIND", REWIND_OP, KEY_REWIND },
+ { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
+ { NULL }
+};
+
+static GSList *callbacks = NULL;
+static GSList *servers = NULL;
+static GSList *handlers = NULL;
+
+static void auth_cb(DBusError *derr, void *user_data);
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+ struct uinput_event event;
+
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+
+ return write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+ if (fd < 0)
+ return;
+
+ send_event(fd, EV_KEY, key, pressed);
+ send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static size_t handle_panel_passthrough(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ const char *status;
+ int pressed, i;
+
+ if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ if (operand_count == 0)
+ goto done;
+
+ if (operands[0] & 0x80) {
+ status = "released";
+ pressed = 0;
+ } else {
+ status = "pressed";
+ pressed = 1;
+ }
+
+ for (i = 0; key_map[i].name != NULL; i++) {
+ uint8_t key_quirks;
+
+ if ((operands[0] & 0x7F) != key_map[i].avc)
+ continue;
+
+ DBG("AVRCP: %s %s", key_map[i].name, status);
+
+ key_quirks = session->key_quirks[key_map[i].avc];
+
+ if (key_quirks & QUIRK_NO_RELEASE) {
+ if (!pressed) {
+ DBG("AVRCP: Ignoring release");
+ break;
+ }
+
+ DBG("AVRCP: treating key press as press + release");
+ send_key(session->uinput, key_map[i].uinput, 1);
+ send_key(session->uinput, key_map[i].uinput, 0);
+ break;
+ }
+
+ send_key(session->uinput, key_map[i].uinput, pressed);
+ break;
+ }
+
+ if (key_map[i].name == NULL)
+ DBG("AVRCP: unknown button 0x%02X %s",
+ operands[0] & 0x7F, status);
+
+done:
+ *code = AVC_CTYPE_ACCEPTED;
+ return operand_count;
+}
+
+static size_t handle_unit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_CTYPE_STABLE;
+
+ /* The first operand should be 0x07 for the UNITINFO response.
+ * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+ * Interface Command Set (section 9.2.1, page 45) specs
+ * explain this value but both use it */
+ if (operand_count >= 1)
+ operands[0] = 0x07;
+ if (operand_count >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_UNITINFO");
+
+ return 0;
+}
+
+static size_t handle_subunit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_CTYPE_STABLE;
+
+ /* The first operand should be 0x07 for the UNITINFO response.
+ * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+ * Interface Command Set (section 9.2.1, page 45) specs
+ * explain this value but both use it */
+ if (operand_count >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_SUBUNITINFO");
+
+ return 0;
+}
+
+static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode)
+{
+ for (; list; list = list->next) {
+ struct avctp_pdu_handler *handler = list->data;
+
+ if (handler->opcode == opcode)
+ return handler;
+ }
+
+ return NULL;
+}
+
+static void avctp_disconnected(struct avctp *session)
+{
+ struct avctp_server *server = session->server;
+
+ if (!session)
+ return;
+
+ if (session->io) {
+ g_io_channel_shutdown(session->io, TRUE, NULL);
+ g_io_channel_unref(session->io);
+ session->io = NULL;
+ }
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+
+ if (session->state == AVCTP_STATE_CONNECTING) {
+ struct audio_device *dev;
+
+ dev = manager_get_device(&session->server->src,
+ &session->dst, FALSE);
+ audio_device_cancel_authorization(dev, auth_cb,
+ session);
+ }
+ }
+
+ if (session->uinput >= 0) {
+ char address[18];
+
+ ba2str(&session->dst, address);
+ DBG("AVCTP: closing uinput for %s", address);
+
+ ioctl(session->uinput, UI_DEV_DESTROY);
+ close(session->uinput);
+ session->uinput = -1;
+ }
+
+ server->sessions = g_slist_remove(server->sessions, session);
+ g_free(session);
+}
+
+static void avctp_set_state(struct avctp *session, avctp_state_t new_state)
+{
+ GSList *l;
+ struct audio_device *dev;
+ avctp_state_t old_state = session->state;
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+ if (dev == NULL) {
+ error("avdtp_set_state(): no matching audio device");
+ return;
+ }
+
+ session->state = new_state;
+
+ for (l = callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ cb->cb(dev, old_state, new_state, cb->user_data);
+ }
+
+ switch (new_state) {
+ case AVCTP_STATE_DISCONNECTED:
+ DBG("AVCTP Disconnected");
+
+ avctp_disconnected(session);
+
+ if (old_state != AVCTP_STATE_CONNECTED)
+ break;
+
+ if (!audio_device_is_active(dev, NULL))
+ audio_device_set_authorized(dev, FALSE);
+
+ break;
+ case AVCTP_STATE_CONNECTING:
+ DBG("AVCTP Connecting");
+ break;
+ case AVCTP_STATE_CONNECTED:
+ DBG("AVCTP Connected");
+ break;
+ default:
+ error("Invalid AVCTP state %d", new_state);
+ return;
+ }
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ uint8_t buf[1024], *operands, code, subunit;
+ struct avctp_header *avctp;
+ struct avc_header *avc;
+ int ret, packet_size, operand_count, sock;
+ struct avctp_pdu_handler *handler;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto failed;
+
+ sock = g_io_channel_unix_get_fd(session->io);
+
+ ret = read(sock, buf, sizeof(buf));
+ if (ret <= 0)
+ goto failed;
+
+ DBG("Got %d bytes of data for AVCTP session %p", ret, session);
+
+ if ((unsigned int) ret < sizeof(struct avctp_header)) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ avctp = (struct avctp_header *) buf;
+
+ DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+ "PID 0x%04X",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+ ret -= sizeof(struct avctp_header);
+ if ((unsigned int) ret < sizeof(struct avc_header)) {
+ error("Too small AVRCP packet");
+ goto failed;
+ }
+
+ avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
+
+ ret -= sizeof(struct avc_header);
+
+ operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
+ operand_count = ret;
+
+ DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+ "opcode 0x%02X, %d operands",
+ avctp->cr ? "response" : "command",
+ avc->code, avc->subunit_type, avc->subunit_id,
+ avc->opcode, operand_count);
+
+ if (avctp->cr == AVCTP_RESPONSE)
+ return TRUE;
+
+ packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
+ avctp->cr = AVCTP_RESPONSE;
+
+ if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+ avc->code = AVC_CTYPE_NOT_IMPLEMENTED;
+ goto done;
+ }
+
+ if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ avc->code = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ handler = find_handler(handlers, avc->opcode);
+ if (!handler) {
+ avc->code = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ code = avc->code;
+ subunit = avc->subunit_type;
+
+ packet_size += handler->cb(session, avctp->transaction, &code,
+ &subunit, operands, operand_count,
+ handler->user_data);
+
+ avc->code = code;
+ avc->subunit_type = subunit;
+
+done:
+ ret = write(sock, buf, packet_size);
+ if (ret != packet_size)
+ goto failed;
+
+ return TRUE;
+
+failed:
+ DBG("AVCTP session %p got disconnected", session);
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+ struct uinput_dev dev;
+ int fd, err, i;
+
+ fd = open("/dev/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/input/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/misc/uinput", O_RDWR);
+ if (fd < 0) {
+ err = errno;
+ error("Can't open input device: %s (%d)",
+ strerror(err), err);
+ return -err;
+ }
+ }
+ }
+
+ memset(&dev, 0, sizeof(dev));
+ if (name)
+ strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
+
+ dev.id.bustype = BUS_BLUETOOTH;
+ dev.id.vendor = 0x0000;
+ dev.id.product = 0x0000;
+ dev.id.version = 0x0000;
+
+ if (write(fd, &dev, sizeof(dev)) < 0) {
+ err = errno;
+ error("Can't write device information: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ ioctl(fd, UI_SET_EVBIT, EV_REL);
+ ioctl(fd, UI_SET_EVBIT, EV_REP);
+ ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+ for (i = 0; key_map[i].name != NULL; i++)
+ ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+ if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+ err = errno;
+ error("Can't create uinput device: %s (%d)",
+ strerror(err), err);
+ close(fd);
+ errno = err;
+ return -err;
+ }
+
+ return fd;
+}
+
+static void init_uinput(struct avctp *session)
+{
+ struct audio_device *dev;
+ char address[18], name[248 + 1];
+
+ dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+ device_get_name(dev->btd_dev, name, sizeof(name));
+ if (g_str_equal(name, "Nokia CK-20W")) {
+ session->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
+ session->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
+ }
+
+ ba2str(&session->dst, address);
+
+ session->uinput = uinput_create(address);
+ if (session->uinput < 0)
+ error("AVRCP: failed to init uinput for %s", address);
+ else
+ DBG("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+ struct avctp *session = data;
+ char address[18];
+ uint16_t imtu;
+ GError *gerr = NULL;
+
+ if (err) {
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ error("%s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, BT_IO_L2CAP, &gerr,
+ BT_IO_OPT_DEST, &address,
+ BT_IO_OPT_IMTU, &imtu,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ return;
+ }
+
+ DBG("AVCTP: connected to %s", address);
+
+ if (!session->io)
+ session->io = g_io_channel_ref(chan);
+
+ init_uinput(session);
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTED);
+ session->mtu = imtu;
+ session->io_id = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+ struct avctp *session = user_data;
+ GError *err = NULL;
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+ }
+
+ if (derr && dbus_error_is_set(derr)) {
+ error("Access denied: %s", derr->message);
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ return;
+ }
+
+ if (!bt_io_accept(session->io, avctp_connect_cb, session,
+ NULL, &err)) {
+ error("bt_io_accept: %s", err->message);
+ g_error_free(err);
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ }
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+ for (; list; list = list->next) {
+ struct avctp_server *server = list->data;
+
+ if (bacmp(&server->src, src) == 0)
+ return server;
+ }
+
+ return NULL;
+}
+
+static struct avctp *find_session(GSList *list, const bdaddr_t *dst)
+{
+ for (; list != NULL; list = g_slist_next(list)) {
+ struct avctp *s = list->data;
+
+ if (bacmp(dst, &s->dst))
+ continue;
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct avctp *avctp_get_internal(const bdaddr_t *src,
+ const bdaddr_t *dst)
+{
+ struct avctp_server *server;
+ struct avctp *session;
+
+ assert(src != NULL);
+ assert(dst != NULL);
+
+ server = find_server(servers, src);
+ if (server == NULL)
+ return NULL;
+
+ session = find_session(server->sessions, dst);
+ if (session)
+ return session;
+
+ session = g_new0(struct avctp, 1);
+
+ session->server = server;
+ bacpy(&session->dst, dst);
+ session->state = AVCTP_STATE_DISCONNECTED;
+
+ server->sessions = g_slist_append(server->sessions, session);
+
+ return session;
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+ struct avctp *session;
+ struct audio_device *dev;
+ char address[18];
+ bdaddr_t src, dst;
+ GError *err = NULL;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &src,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ DBG("AVCTP: incoming connect from %s", address);
+
+ session = avctp_get_internal(&src, &dst);
+ if (!session)
+ goto drop;
+
+ dev = manager_get_device(&src, &dst, FALSE);
+ if (!dev) {
+ dev = manager_get_device(&src, &dst, TRUE);
+ if (!dev) {
+ error("Unable to get audio device object for %s",
+ address);
+ goto drop;
+ }
+ btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+ }
+
+ if (session->io) {
+ error("Refusing unexpected connect from %s", address);
+ goto drop;
+ }
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTING);
+ session->io = g_io_channel_ref(chan);
+
+ if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+ auth_cb, session) < 0)
+ goto drop;
+
+ session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ session_cb, session);
+ return;
+
+drop:
+ if (!session || !session->io)
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ if (session)
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
+
+static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
+{
+ GError *err = NULL;
+ GIOChannel *io;
+
+ io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
+ NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, src,
+ BT_IO_OPT_PSM, AVCTP_PSM,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_MASTER, master,
+ BT_IO_OPT_INVALID);
+ if (!io) {
+ error("%s", err->message);
+ g_error_free(err);
+ }
+
+ return io;
+}
+
+static unsigned int passthrough_id = 0;
+static unsigned int unit_id = 0;
+static unsigned int subunit_id = 0;
+
+int avctp_register(const bdaddr_t *src, gboolean master)
+{
+ struct avctp_server *server;
+
+ server = g_new0(struct avctp_server, 1);
+ if (!server)
+ return -ENOMEM;
+
+ server->io = avctp_server_socket(src, master);
+ if (!server->io) {
+ g_free(server);
+ return -1;
+ }
+
+ bacpy(&server->src, src);
+
+ servers = g_slist_append(servers, server);
+
+ if (!passthrough_id)
+ passthrough_id = avctp_register_pdu_handler(AVC_OP_PASSTHROUGH,
+ handle_panel_passthrough, NULL);
+
+ if (!unit_id)
+ unit_id = avctp_register_pdu_handler(AVC_OP_UNITINFO, handle_unit_info,
+ NULL);
+
+ if (!subunit_id)
+ subunit_id = avctp_register_pdu_handler(AVC_OP_SUBUNITINFO,
+ handle_subunit_info, NULL);
+
+ return 0;
+}
+
+void avctp_unregister(const bdaddr_t *src)
+{
+ struct avctp_server *server;
+
+ server = find_server(servers, src);
+ if (!server)
+ return;
+
+ while (server->sessions)
+ avctp_disconnected(server->sessions->data);
+
+ servers = g_slist_remove(servers, server);
+
+ g_io_channel_shutdown(server->io, TRUE, NULL);
+ g_io_channel_unref(server->io);
+ g_free(server);
+
+ if (servers)
+ return;
+
+ if (passthrough_id)
+ avctp_unregister_pdu_handler(passthrough_id);
+
+ if (unit_id)
+ avctp_unregister_pdu_handler(unit_id);
+
+ if (subunit_id)
+ avctp_unregister_pdu_handler(unit_id);
+}
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op)
+{
+ unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
+ struct avctp_header *avctp = (void *) buf;
+ struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+ uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+ int sk;
+ static uint8_t transaction = 0;
+
+ if (session->state != AVCTP_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ memset(buf, 0, sizeof(buf));
+
+ avctp->transaction = transaction++;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_COMMAND;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avc->code = AVC_CTYPE_CONTROL;
+ avc->subunit_type = AVC_SUBUNIT_PANEL;
+ avc->opcode = AVC_OP_PASSTHROUGH;
+
+ operands[0] = op & 0x7f;
+ operands[1] = 0;
+
+ sk = g_io_channel_unix_get_fd(session->io);
+
+ if (write(sk, buf, sizeof(buf)) < 0)
+ return -errno;
+
+ /* Button release */
+ avctp->transaction = transaction++;
+ operands[0] |= 0x80;
+
+ if (write(sk, buf, sizeof(buf)) < 0)
+ return -errno;
+
+ return 0;
+}
+
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count)
+{
+ uint8_t *buf;
+ struct avctp_header *avctp;
+ struct avc_header *avc;
+ uint8_t *pdu;
+ int sk, err;
+ uint16_t size;
+
+ if (session->state != AVCTP_STATE_CONNECTED)
+ return -ENOTCONN;
+
+ sk = g_io_channel_unix_get_fd(session->io);
+ size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + operand_count;
+ buf = g_malloc0(size);
+
+ avctp = (void *) buf;
+ avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+ pdu = (void *) &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+
+ avctp->transaction = transaction;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avc->code = code;
+ avc->subunit_type = subunit;
+ avc->opcode = AVC_OP_VENDORDEP;
+
+ memcpy(pdu, operands, operand_count);
+
+ err = write(sk, buf, size);
+ if (err < 0) {
+ g_free(buf);
+ return -errno;
+ }
+
+ g_free(buf);
+ return 0;
+}
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
+{
+ struct avctp_state_callback *state_cb;
+ static unsigned int id = 0;
+
+ state_cb = g_new(struct avctp_state_callback, 1);
+ state_cb->cb = cb;
+ state_cb->user_data = user_data;
+ state_cb->id = ++id;
+
+ callbacks = g_slist_append(callbacks, state_cb);
+
+ return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+ GSList *l;
+
+ for (l = callbacks; l != NULL; l = l->next) {
+ struct avctp_state_callback *cb = l->data;
+ if (cb && cb->id == id) {
+ callbacks = g_slist_remove(callbacks, cb);
+ g_free(cb);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+ void *user_data)
+{
+ struct avctp_pdu_handler *handler;
+ static unsigned int id = 0;
+
+ handler = find_handler(handlers, opcode);
+ if (handler)
+ return 0;
+
+ handler = g_new(struct avctp_pdu_handler, 1);
+ handler->opcode = opcode;
+ handler->cb = cb;
+ handler->user_data = user_data;
+ handler->id = ++id;
+
+ handlers = g_slist_append(handlers, handler);
+
+ return handler->id;
+}
+
+gboolean avctp_unregister_pdu_handler(unsigned int id)
+{
+ GSList *l;
+
+ for (l = handlers; l != NULL; l = l->next) {
+ struct avctp_pdu_handler *handler = l->data;
+
+ if (handler->id == id) {
+ handlers = g_slist_remove(handlers, handler);
+ g_free(handler);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst)
+{
+ struct avctp *session;
+ GError *err = NULL;
+ GIOChannel *io;
+
+ session = avctp_get_internal(src, dst);
+ if (!session)
+ return NULL;
+
+ if (session->state > AVCTP_STATE_DISCONNECTED)
+ return session;
+
+ avctp_set_state(session, AVCTP_STATE_CONNECTING);
+
+ io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, session, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+ BT_IO_OPT_DEST_BDADDR, &session->dst,
+ BT_IO_OPT_PSM, AVCTP_PSM,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+ error("%s", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+
+ session->io = io;
+
+ return session;
+}
+
+void avctp_disconnect(struct avctp *session)
+{
+ if (session->io)
+ return;
+
+ avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
diff --git a/audio/avctp.h b/audio/avctp.h
new file mode 100644
index 0000000..157ecc6
--- /dev/null
+++ b/audio/avctp.h
@@ -0,0 +1,95 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <[email protected]>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#define AVCTP_PSM 23
+
+/* ctype entries */
+#define AVC_CTYPE_CONTROL 0x0
+#define AVC_CTYPE_STATUS 0x1
+#define AVC_CTYPE_NOTIFY 0x3
+#define AVC_CTYPE_NOT_IMPLEMENTED 0x8
+#define AVC_CTYPE_ACCEPTED 0x9
+#define AVC_CTYPE_REJECTED 0xA
+#define AVC_CTYPE_STABLE 0xC
+#define AVC_CTYPE_CHANGED 0xD
+#define AVC_CTYPE_INTERIM 0xF
+
+/* opcodes */
+#define AVC_OP_VENDORDEP 0x00
+#define AVC_OP_UNITINFO 0x30
+#define AVC_OP_SUBUNITINFO 0x31
+#define AVC_OP_PASSTHROUGH 0x7c
+
+/* subunits of interest */
+#define AVC_SUBUNIT_PANEL 0x09
+
+/* operands in passthrough commands */
+#define VOL_UP_OP 0x41
+#define VOL_DOWN_OP 0x42
+#define MUTE_OP 0x43
+#define PLAY_OP 0x44
+#define STAVC_OP_OP 0x45
+#define PAUSE_OP 0x46
+#define RECORD_OP 0x47
+#define REWIND_OP 0x48
+#define FAST_FORWARD_OP 0x49
+#define EJECT_OP 0x4a
+#define FORWARD_OP 0x4b
+#define BACKWARD_OP 0x4c
+
+struct avctp;
+
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+ avctp_state_t old_state,
+ avctp_state_t new_state,
+ void *user_data);
+
+typedef size_t (*avctp_pdu_cb) (struct avctp *session, uint8_t transaction,
+ uint8_t *code, uint8_t *subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avctp_register(const bdaddr_t *src, gboolean master);
+void avctp_unregister(const bdaddr_t *src);
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst);
+void avctp_disconnect(struct avctp *session);
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+ void *user_data);
+gboolean avctp_unregister_pdu_handler(unsigned int id);
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op);
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count);
diff --git a/audio/control.c b/audio/control.c
index cafb951..dceb004 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -36,7 +36,6 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
-#include <netinet/in.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
@@ -48,64 +47,14 @@

#include "log.h"
#include "error.h"
-#include "uinput.h"
-#include "adapter.h"
-#include "../src/device.h"
#include "device.h"
#include "manager.h"
-#include "avdtp.h"
+#include "avctp.h"
#include "control.h"
#include "sdpd.h"
#include "glib-helper.h"
-#include "btio.h"
#include "dbus-common.h"

-#define AVCTP_PSM 23
-
-/* Message types */
-#define AVCTP_COMMAND 0
-#define AVCTP_RESPONSE 1
-
-/* Packet types */
-#define AVCTP_PACKET_SINGLE 0
-#define AVCTP_PACKET_START 1
-#define AVCTP_PACKET_CONTINUE 2
-#define AVCTP_PACKET_END 3
-
-/* 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_CHANGED 0xD
-#define CTYPE_INTERIM 0xF
-
-/* opcodes */
-#define OP_VENDORDEP 0x00
-#define OP_UNITINFO 0x30
-#define OP_SUBUNITINFO 0x31
-#define OP_PASSTHROUGH 0x7c
-
-/* subunits of interest */
-#define SUBUNIT_PANEL 0x09
-
-/* operands in passthrough commands */
-#define VOL_UP_OP 0x41
-#define VOL_DOWN_OP 0x42
-#define MUTE_OP 0x43
-#define PLAY_OP 0x44
-#define STOP_OP 0x45
-#define PAUSE_OP 0x46
-#define RECORD_OP 0x47
-#define REWIND_OP 0x48
-#define FAST_FORWARD_OP 0x49
-#define EJECT_OP 0x4a
-#define FORWARD_OP 0x4b
-#define BACKWARD_OP 0x4c
-
/* Company IDs for vendor dependent commands */
#define IEEEID_BTSIG 0x001958

@@ -137,8 +86,6 @@
#define CAP_COMPANY_ID 0x02
#define CAP_EVENTS_SUPPORTED 0x03

-#define QUIRK_NO_RELEASE 1 << 0
-
enum player_setting {
PLAYER_SETTING_EQUALIZER = 1,
PLAYER_SETTING_REPEAT = 2,
@@ -200,27 +147,10 @@ enum media_info_id {
static DBusConnection *connection = NULL;

static GSList *servers = NULL;
+static unsigned int avctp_id = 0;

#if __BYTE_ORDER == __LITTLE_ENDIAN

-struct avctp_header {
- uint8_t ipid:1;
- uint8_t cr:1;
- uint8_t packet_type:2;
- uint8_t transaction:4;
- uint16_t pid;
-} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
-
-struct avc_header {
- uint8_t code:4;
- uint8_t _hdr0:4;
- uint8_t subunit_id:3;
- uint8_t subunit_type:5;
- uint8_t opcode;
-} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
-
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
@@ -233,24 +163,6 @@ struct avrcp_header {

#elif __BYTE_ORDER == __BIG_ENDIAN

-struct avctp_header {
- uint8_t transaction:4;
- uint8_t packet_type:2;
- uint8_t cr:1;
- uint8_t ipid:1;
- uint16_t pid;
-} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
-
-struct avc_header {
- uint8_t _hdr0:4;
- uint8_t code:4;
- uint8_t subunit_type:5;
- uint8_t subunit_id:3;
- uint8_t opcode;
-} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
-
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
@@ -265,15 +177,8 @@ struct avrcp_header {
#error "Unknown byte order"
#endif

-struct avctp_state_callback {
- avctp_state_cb cb;
- void *user_data;
- unsigned int id;
-};
-
-struct avctp_server {
+struct avrcp_server {
bdaddr_t src;
- GIOChannel *io;
uint32_t tg_record_id;
uint32_t ct_record_id;
};
@@ -295,53 +200,25 @@ struct media_player {

struct media_info mi;
GTimer *timer;
+ unsigned int handler;
};

struct control {
struct audio_device *dev;
struct media_player *mp;
-
- avctp_state_t state;
-
- int uinput;
-
- GIOChannel *io;
- guint io_id;
-
- uint16_t mtu;
+ struct avctp *session;

gboolean target;

- uint8_t key_quirks[256];
-
uint16_t registered_events;
uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
};

-static struct {
- const char *name;
- uint8_t avrcp;
- uint16_t uinput;
-} key_map[] = {
- { "PLAY", PLAY_OP, KEY_PLAYCD },
- { "STOP", STOP_OP, KEY_STOPCD },
- { "PAUSE", PAUSE_OP, KEY_PAUSECD },
- { "FORWARD", FORWARD_OP, KEY_NEXTSONG },
- { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
- { "REWIND", REWIND_OP, KEY_REWIND },
- { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
- { 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);
-
static sdp_record_t *avrcp_ct_record(void)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -470,76 +347,6 @@ static sdp_record_t *avrcp_tg_record(void)
return record;
}

-static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
-{
- struct uinput_event event;
-
- memset(&event, 0, sizeof(event));
- event.type = type;
- event.code = code;
- event.value = value;
-
- return write(fd, &event, sizeof(event));
-}
-
-static void send_key(int fd, uint16_t key, int pressed)
-{
- if (fd < 0)
- return;
-
- send_event(fd, EV_KEY, key, pressed);
- send_event(fd, EV_SYN, SYN_REPORT, 0);
-}
-
-static void handle_panel_passthrough(struct control *control,
- const unsigned char *operands,
- int operand_count)
-{
- const char *status;
- int pressed, i;
-
- if (operand_count == 0)
- return;
-
- if (operands[0] & 0x80) {
- status = "released";
- pressed = 0;
- } else {
- status = "pressed";
- pressed = 1;
- }
-
- for (i = 0; key_map[i].name != NULL; i++) {
- uint8_t key_quirks;
-
- if ((operands[0] & 0x7F) != key_map[i].avrcp)
- continue;
-
- DBG("AVRCP: %s %s", key_map[i].name, status);
-
- key_quirks = control->key_quirks[key_map[i].avrcp];
-
- if (key_quirks & QUIRK_NO_RELEASE) {
- if (!pressed) {
- DBG("AVRCP: Ignoring release");
- break;
- }
-
- DBG("AVRCP: treating key press as press + release");
- send_key(control->uinput, key_map[i].uinput, 1);
- send_key(control->uinput, key_map[i].uinput, 0);
- break;
- }
-
- send_key(control->uinput, key_map[i].uinput, pressed);
- break;
- }
-
- if (key_map[i].name == NULL)
- DBG("AVRCP: unknown button 0x%02X %s",
- operands[0] & 0x7F, status);
-}
-
static unsigned int attr_get_max_val(uint8_t attr)
{
switch (attr) {
@@ -719,18 +526,14 @@ static const char *battery_status_to_str(enum battery_status status)
return NULL;
}

-static int avctp_send_event(struct control *control, uint8_t id, void *data)
+static int avrcp_send_event(struct control *control, uint8_t id, void *data)
{
- uint8_t buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_HEADER_LENGTH + 9];
- struct avctp_header *avctp = (void *) buf;
- struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
- struct avrcp_header *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
- AVC_HEADER_LENGTH];
- int sk;
+ uint8_t buf[AVRCP_HEADER_LENGTH + 9];
+ struct avrcp_header *pdu = (void *) buf;
uint16_t size;
+ int err;

- if (control->state != AVCTP_STATE_CONNECTED)
+ if (control->session)
return -ENOTCONN;

if (!(control->registered_events & (1 << id)))
@@ -738,15 +541,6 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)

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);
-
- avc->code = CTYPE_CHANGED;
- avc->subunit_type = SUBUNIT_PANEL;
- avc->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;
@@ -780,13 +574,12 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)
}

pdu->params_len = htons(size);
- size += AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_HEADER_LENGTH;
-
- sk = g_io_channel_unix_get_fd(control->io);

- if (write(sk, buf, size) < 0)
- return -errno;
+ err = avctp_send_vendordep(control->session, control->transaction_events[id],
+ AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+ buf, size);
+ if (err < 0)
+ return err;

/* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
control->registered_events ^= 1 << id;
@@ -833,7 +626,7 @@ static void mp_set_playback_status(struct control *control, uint8_t status,

mp->status = status;

- avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
+ avrcp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
&status);
}

@@ -983,7 +776,7 @@ static void mp_set_media_attributes(struct control *control,
mi->title, mi->artist, mi->album, mi->genre,
mi->ntracks, mi->track, mi->track_len);

- avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
+ avrcp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
}

static uint8_t avrcp_handle_get_capabilities(struct control *control,
@@ -1009,21 +802,21 @@ static uint8_t avrcp_handle_get_capabilities(struct control *control,
pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
pdu->params[1] = G_N_ELEMENTS(company_ids);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
case CAP_EVENTS_SUPPORTED:
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 CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;

- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_list_player_attributes(struct control *control,
@@ -1037,7 +830,7 @@ static uint8_t avrcp_handle_list_player_attributes(struct control *control,
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

if (!mp)
@@ -1057,7 +850,7 @@ done:
pdu->params[0] = len;
pdu->params_len = htons(len + 1);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

static uint8_t avrcp_handle_list_player_values(struct control *control,
@@ -1083,12 +876,12 @@ static uint8_t avrcp_handle_list_player_values(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(len + 1);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;

err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_get_element_attributes(struct control *control,
@@ -1153,11 +946,11 @@ done:
pdu->params[0] = len;
pdu->params_len = htons(pos);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_get_current_player_value(struct control *control,
@@ -1212,7 +1005,7 @@ static uint8_t avrcp_handle_get_current_player_value(struct control *control,
pdu->params[0] = len;
pdu->params_len = htons(2 * len + 1);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

error("No valid attributes in request");
@@ -1221,7 +1014,7 @@ err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;

- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_set_player_value(struct control *control,
@@ -1268,13 +1061,13 @@ static uint8_t avrcp_handle_set_player_value(struct control *control,
if (len) {
pdu->params_len = 0;

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_displayable_charset(struct control *control,
@@ -1286,7 +1079,7 @@ static uint8_t avrcp_handle_displayable_charset(struct control *control,
if (len < 3) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

/*
@@ -1294,7 +1087,7 @@ static uint8_t avrcp_handle_displayable_charset(struct control *control,
* encoding since CT is obliged to support it.
*/
pdu->params_len = 0;
- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

static uint8_t avrcp_handle_ct_battery_status(struct control *control,
@@ -1316,12 +1109,12 @@ static uint8_t avrcp_handle_ct_battery_status(struct control *control,
DBUS_TYPE_STRING, &valstr);
pdu->params_len = 0;

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;

err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static uint8_t avrcp_handle_get_play_status(struct control *control,
@@ -1336,7 +1129,7 @@ static uint8_t avrcp_handle_get_play_status(struct control *control,
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

if (control->mp) {
@@ -1356,7 +1149,7 @@ static uint8_t avrcp_handle_get_play_status(struct control *control,

pdu->params_len = htons(9);

- return CTYPE_STABLE;
+ return AVC_CTYPE_STABLE;
}

static uint8_t avrcp_handle_register_notification(struct control *control,
@@ -1406,12 +1199,12 @@ static uint8_t avrcp_handle_register_notification(struct control *control,

pdu->params_len = htons(len);

- return CTYPE_INTERIM;
+ return AVC_CTYPE_INTERIM;

err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
- return CTYPE_REJECTED;
+ return AVC_CTYPE_REJECTED;
}

static struct pdu_handler {
@@ -1421,51 +1214,54 @@ static struct pdu_handler {
struct avrcp_header *pdu,
uint8_t transaction);
} handlers[] = {
- { AVRCP_GET_CAPABILITIES, CTYPE_STATUS,
+ { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
avrcp_handle_get_capabilities },
- { AVRCP_LIST_PLAYER_ATTRIBUTES, CTYPE_STATUS,
+ { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_attributes },
- { AVRCP_LIST_PLAYER_VALUES, CTYPE_STATUS,
+ { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_values },
- { AVRCP_GET_ELEMENT_ATTRIBUTES, CTYPE_STATUS,
+ { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_get_element_attributes },
- { AVRCP_GET_CURRENT_PLAYER_VALUE, CTYPE_STATUS,
+ { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
avrcp_handle_get_current_player_value },
- { AVRCP_SET_PLAYER_VALUE, CTYPE_CONTROL,
+ { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
avrcp_handle_set_player_value },
- { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, CTYPE_STATUS,
+ { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
NULL },
- { AVRCP_GET_PLAYER_VALUE_TEXT, CTYPE_STATUS,
+ { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
NULL },
- { AVRCP_DISPLAYABLE_CHARSET, CTYPE_STATUS,
+ { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
avrcp_handle_displayable_charset },
- { AVRCP_CT_BATTERY_STATUS, CTYPE_STATUS,
+ { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_ct_battery_status },
- { AVRCP_GET_PLAY_STATUS, CTYPE_STATUS,
+ { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_get_play_status },
- { AVRCP_REGISTER_NOTIFICATION, CTYPE_NOTIFY,
+ { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
avrcp_handle_register_notification },
{ },
};

/* handle vendordep pdu inside an avctp packet */
-static int handle_vendordep_pdu(struct control *control,
- struct avctp_header *avctp,
- struct avc_header *avc,
- int operand_count)
+static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
+ uint8_t *code, uint8_t *subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data)
{
+ struct control *control = user_data;
struct pdu_handler *handler;
- struct avrcp_header *pdu = (void *) avc + AVC_HEADER_LENGTH;
+ struct avrcp_header *pdu = (void *) operands;
uint32_t company_id = (pdu->company_id[0] << 16) |
(pdu->company_id[1] << 8) |
(pdu->company_id[2]);

- if (company_id != IEEEID_BTSIG ||
- pdu->packet_type != AVCTP_PACKET_SINGLE) {
- avc->code = CTYPE_NOT_IMPLEMENTED;
- return AVC_HEADER_LENGTH;
+ if (company_id != IEEEID_BTSIG) {
+ *code = AVC_CTYPE_NOT_IMPLEMENTED;
+ return 0;
}

+ DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
+ pdu->pdu_id, company_id, pdu->params_len);
+
pdu->packet_type = 0;
pdu->rsvd = 0;

@@ -1477,7 +1273,7 @@ static int handle_vendordep_pdu(struct control *control,
break;
}

- if (!handler || handler->code != avc->code) {
+ if (!handler || handler->code != *code) {
pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
}
@@ -1487,64 +1283,31 @@ static int handle_vendordep_pdu(struct control *control,
goto err_metadata;
}

- avc->code = handler->func(control, pdu, avctp->transaction);
+ *code = handler->func(control, pdu, transaction);

- return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
- ntohs(pdu->params_len);
+ return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);

err_metadata:
pdu->params_len = htons(1);
- avc->code = CTYPE_REJECTED;
+ *code = AVC_CTYPE_REJECTED;

- return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 1;
+ return AVRCP_HEADER_LENGTH + 1;
}

-static void avctp_disconnected(struct audio_device *dev)
+static void state_changed(struct audio_device *dev, avctp_state_t old_state,
+ avctp_state_t new_state, void *user_data)
{
struct control *control = dev->control;
-
- if (!control)
- return;
-
- if (control->io) {
- g_io_channel_shutdown(control->io, TRUE, NULL);
- g_io_channel_unref(control->io);
- control->io = NULL;
- }
-
- if (control->io_id) {
- g_source_remove(control->io_id);
- control->io_id = 0;
-
- if (control->state == AVCTP_STATE_CONNECTING)
- audio_device_cancel_authorization(dev, auth_cb,
- control);
- }
-
- if (control->uinput >= 0) {
- char address[18];
-
- ba2str(&dev->dst, address);
- DBG("AVRCP: closing uinput for %s", address);
-
- ioctl(control->uinput, UI_DEV_DESTROY);
- close(control->uinput);
- control->uinput = -1;
- }
-}
-
-static void avctp_set_state(struct control *control, avctp_state_t new_state)
-{
- GSList *l;
- struct audio_device *dev = control->dev;
- avctp_state_t old_state = control->state;
gboolean value;

switch (new_state) {
case AVCTP_STATE_DISCONNECTED:
- DBG("AVCTP Disconnected");
+ control->session = NULL;

- avctp_disconnected(control->dev);
+ if (control->mp && control->mp->handler) {
+ avctp_unregister_pdu_handler(control->mp->handler);
+ control->mp->handler = 0;
+ }

if (old_state != AVCTP_STATE_CONNECTED)
break;
@@ -1557,221 +1320,32 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state)
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_BOOLEAN, &value);

- if (!audio_device_is_active(dev, NULL))
- audio_device_set_authorized(dev, FALSE);
-
break;
case AVCTP_STATE_CONNECTING:
- DBG("AVCTP Connecting");
+ if (control->session)
+ break;
+
+ control->session = avctp_connect(&dev->src, &dev->dst);
+ if (!control->mp)
+ break;
+
+ control->mp->handler = avctp_register_pdu_handler(
+ AVC_OP_VENDORDEP,
+ handle_vendordep_pdu,
+ control);
break;
case AVCTP_STATE_CONNECTED:
- DBG("AVCTP Connected");
value = TRUE;
- g_dbus_emit_signal(control->dev->conn, control->dev->path,
+ g_dbus_emit_signal(dev->conn, dev->path,
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_INVALID);
- emit_property_changed(control->dev->conn, control->dev->path,
+ emit_property_changed(dev->conn, dev->path,
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_BOOLEAN, &value);
break;
default:
- error("Invalid AVCTP state %d", new_state);
return;
}
-
- control->state = new_state;
-
- for (l = avctp_callbacks; l != NULL; l = l->next) {
- struct avctp_state_callback *cb = l->data;
- cb->cb(control->dev, old_state, new_state, cb->user_data);
- }
-}
-
-static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
- gpointer data)
-{
- struct control *control = data;
- unsigned char buf[1024], *operands;
- struct avctp_header *avctp;
- struct avc_header *avc;
- int ret, packet_size, operand_count, sock;
-
- if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
- goto failed;
-
- sock = g_io_channel_unix_get_fd(control->io);
-
- ret = read(sock, buf, sizeof(buf));
- if (ret <= 0)
- goto failed;
-
- DBG("Got %d bytes of data for AVCTP session %p", ret, control);
-
- if ((unsigned int) ret < sizeof(struct avctp_header)) {
- error("Too small AVCTP packet");
- goto failed;
- }
-
- packet_size = ret;
-
- avctp = (struct avctp_header *) buf;
-
- DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
- "PID 0x%04X",
- avctp->transaction, avctp->packet_type,
- avctp->cr, avctp->ipid, ntohs(avctp->pid));
-
- ret -= sizeof(struct avctp_header);
- if ((unsigned int) ret < sizeof(struct avc_header)) {
- error("Too small AVRCP packet");
- goto failed;
- }
-
- avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
-
- ret -= sizeof(struct avc_header);
-
- operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
- operand_count = ret;
-
- DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
- "opcode 0x%02X, %d operands",
- avctp->cr ? "response" : "command",
- avc->code, avc->subunit_type, avc->subunit_id,
- avc->opcode, operand_count);
-
- if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_NOT_IMPLEMENTED;
- } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
- avctp->ipid = 1;
- avctp->cr = AVCTP_RESPONSE;
- packet_size = sizeof(*avctp);
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->code == CTYPE_CONTROL &&
- avc->subunit_type == SUBUNIT_PANEL &&
- avc->opcode == OP_PASSTHROUGH) {
- handle_panel_passthrough(control, operands, operand_count);
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_ACCEPTED;
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->code == CTYPE_STATUS &&
- (avc->opcode == OP_UNITINFO
- || avc->opcode == OP_SUBUNITINFO)) {
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_STABLE;
- /* The first operand should be 0x07 for the UNITINFO response.
- * Neither AVRCP (section 22.1, page 117) nor AVC Digital
- * Interface Command Set (section 9.2.1, page 45) specs
- * explain this value but both use it */
- if (operand_count >= 1 && avc->opcode == OP_UNITINFO)
- operands[0] = 0x07;
- if (operand_count >= 2)
- operands[1] = SUBUNIT_PANEL << 3;
- DBG("reply to %s", avc->opcode == OP_UNITINFO ?
- "OP_UNITINFO" : "OP_SUBUNITINFO");
- } else if (avctp->cr == AVCTP_COMMAND &&
- avc->opcode == OP_VENDORDEP) {
- int r_size;
- operand_count -= 3;
- avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avctp, avc,
- operand_count);
- packet_size = AVCTP_HEADER_LENGTH + r_size;
- } else {
- avctp->cr = AVCTP_RESPONSE;
- avc->code = CTYPE_REJECTED;
- }
-
- ret = write(sock, buf, packet_size);
- if (ret != packet_size)
- goto failed;
-
- return TRUE;
-
-failed:
- DBG("AVCTP session %p got disconnected", control);
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- return FALSE;
-}
-
-static int uinput_create(char *name)
-{
- struct uinput_dev dev;
- int fd, err, i;
-
- fd = open("/dev/uinput", O_RDWR);
- if (fd < 0) {
- fd = open("/dev/input/uinput", O_RDWR);
- if (fd < 0) {
- fd = open("/dev/misc/uinput", O_RDWR);
- if (fd < 0) {
- err = errno;
- error("Can't open input device: %s (%d)",
- strerror(err), err);
- return -err;
- }
- }
- }
-
- memset(&dev, 0, sizeof(dev));
- if (name)
- strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
-
- dev.id.bustype = BUS_BLUETOOTH;
- dev.id.vendor = 0x0000;
- dev.id.product = 0x0000;
- dev.id.version = 0x0000;
-
- if (write(fd, &dev, sizeof(dev)) < 0) {
- err = errno;
- error("Can't write device information: %s (%d)",
- strerror(err), err);
- close(fd);
- errno = err;
- return -err;
- }
-
- ioctl(fd, UI_SET_EVBIT, EV_KEY);
- ioctl(fd, UI_SET_EVBIT, EV_REL);
- ioctl(fd, UI_SET_EVBIT, EV_REP);
- ioctl(fd, UI_SET_EVBIT, EV_SYN);
-
- for (i = 0; key_map[i].name != NULL; i++)
- ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
-
- if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
- err = errno;
- error("Can't create uinput device: %s (%d)",
- strerror(err), err);
- close(fd);
- errno = err;
- return -err;
- }
-
- return fd;
-}
-
-static void init_uinput(struct control *control)
-{
- struct audio_device *dev = control->dev;
- char address[18], name[248 + 1];
-
- device_get_name(dev->btd_dev, name, sizeof(name));
- if (g_str_equal(name, "Nokia CK-20W")) {
- control->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
- control->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
- control->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
- control->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
- }
-
- ba2str(&dev->dst, address);
-
- control->uinput = uinput_create(address);
- if (control->uinput < 0)
- error("AVRCP: failed to init uinput for %s", address);
- else
- DBG("AVRCP: uinput initialized for %s", address);
}

static void media_info_init(struct media_info *mi)
@@ -1787,169 +1361,16 @@ static void media_info_init(struct media_info *mi)
mi->elapsed = 0xFFFFFFFF;
}

-static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
-{
- struct control *control = data;
- char address[18];
- uint16_t imtu;
- GError *gerr = NULL;
-
- if (err) {
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- error("%s", err->message);
- return;
- }
-
- bt_io_get(chan, BT_IO_L2CAP, &gerr,
- BT_IO_OPT_DEST, &address,
- BT_IO_OPT_IMTU, &imtu,
- BT_IO_OPT_INVALID);
- if (gerr) {
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- error("%s", gerr->message);
- g_error_free(gerr);
- return;
- }
-
- DBG("AVCTP: connected to %s", address);
-
- if (!control->io)
- control->io = g_io_channel_ref(chan);
-
- init_uinput(control);
-
- avctp_set_state(control, AVCTP_STATE_CONNECTED);
- control->mtu = imtu;
- control->io_id = g_io_add_watch(chan,
- G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) control_cb, control);
-}
-
-static void auth_cb(DBusError *derr, void *user_data)
-{
- struct control *control = user_data;
- GError *err = NULL;
-
- if (control->io_id) {
- g_source_remove(control->io_id);
- control->io_id = 0;
- }
-
- if (derr && dbus_error_is_set(derr)) {
- error("Access denied: %s", derr->message);
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- return;
- }
-
- if (!bt_io_accept(control->io, avctp_connect_cb, control,
- NULL, &err)) {
- error("bt_io_accept: %s", err->message);
- g_error_free(err);
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- }
-}
-
-static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
-{
- struct control *control = NULL;
- struct audio_device *dev;
- char address[18];
- bdaddr_t src, dst;
- GError *err = NULL;
-
- bt_io_get(chan, BT_IO_L2CAP, &err,
- BT_IO_OPT_SOURCE_BDADDR, &src,
- BT_IO_OPT_DEST_BDADDR, &dst,
- BT_IO_OPT_DEST, address,
- BT_IO_OPT_INVALID);
- if (err) {
- error("%s", err->message);
- g_error_free(err);
- g_io_channel_shutdown(chan, TRUE, NULL);
- return;
- }
-
- dev = manager_get_device(&src, &dst, TRUE);
- if (!dev) {
- error("Unable to get audio device object for %s", address);
- goto drop;
- }
-
- if (!dev->control) {
- btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
- if (!dev->control)
- goto drop;
- }
-
- control = dev->control;
-
- if (control->io) {
- error("Refusing unexpected connect from %s", address);
- goto drop;
- }
-
- avctp_set_state(control, AVCTP_STATE_CONNECTING);
- control->io = g_io_channel_ref(chan);
-
- if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
- auth_cb, dev->control) < 0)
- goto drop;
-
- control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- control_cb, control);
- return;
-
-drop:
- if (!control || !control->io)
- g_io_channel_shutdown(chan, TRUE, NULL);
- if (control)
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-}
-
-static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
-{
- GError *err = NULL;
- GIOChannel *io;
-
- io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
- NULL, &err,
- BT_IO_OPT_SOURCE_BDADDR, src,
- BT_IO_OPT_PSM, AVCTP_PSM,
- BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
- BT_IO_OPT_MASTER, master,
- BT_IO_OPT_INVALID);
- if (!io) {
- error("%s", err->message);
- g_error_free(err);
- }
-
- return io;
-}
-
gboolean avrcp_connect(struct audio_device *dev)
{
struct control *control = dev->control;
- GError *err = NULL;
- GIOChannel *io;

- if (control->state > AVCTP_STATE_DISCONNECTED)
+ if (control->session)
return TRUE;

- avctp_set_state(control, AVCTP_STATE_CONNECTING);
-
- io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err,
- BT_IO_OPT_SOURCE_BDADDR, &dev->src,
- BT_IO_OPT_DEST_BDADDR, &dev->dst,
- BT_IO_OPT_PSM, AVCTP_PSM,
- BT_IO_OPT_INVALID);
- if (err) {
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- error("%s", err->message);
- g_error_free(err);
+ control->session = avctp_connect(&dev->src, &dev->dst);
+ if (!control->session)
return FALSE;
- }
-
- control->io = io;

return TRUE;
}
@@ -1958,10 +1379,10 @@ void avrcp_disconnect(struct audio_device *dev)
{
struct control *control = dev->control;

- if (!(control && control->io))
+ if (!(control && control->session))
return;

- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ avctp_disconnect(control->session);
}

int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
@@ -1969,7 +1390,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
sdp_record_t *record;
gboolean tmp, master = TRUE;
GError *err = NULL;
- struct avctp_server *server;
+ struct avrcp_server *server;

if (config) {
tmp = g_key_file_get_boolean(config, "General",
@@ -1981,7 +1402,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
master = tmp;
}

- server = g_new0(struct avctp_server, 1);
+ server = g_new0(struct avrcp_server, 1);
if (!server)
return -ENOMEM;

@@ -2018,8 +1439,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
}
server->ct_record_id = record->handle;

- server->io = avctp_server_socket(src, master);
- if (!server->io) {
+ if (avctp_register(src, master) < 0) {
remove_record_from_server(server->ct_record_id);
remove_record_from_server(server->tg_record_id);
g_free(server);
@@ -2033,10 +1453,10 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
return 0;
}

-static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
{
for (; list; list = list->next) {
- struct avctp_server *server = list->data;
+ struct avrcp_server *server = list->data;

if (bacmp(&server->src, src) == 0)
return server;
@@ -2047,7 +1467,7 @@ static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)

void avrcp_unregister(const bdaddr_t *src)
{
- struct avctp_server *server;
+ struct avrcp_server *server;

server = find_server(servers, src);
if (!server)
@@ -2058,13 +1478,15 @@ void avrcp_unregister(const bdaddr_t *src)
remove_record_from_server(server->ct_record_id);
remove_record_from_server(server->tg_record_id);

- g_io_channel_shutdown(server->io, TRUE, NULL);
- g_io_channel_unref(server->io);
+ avctp_unregister(&server->src);
g_free(server);

if (servers)
return;

+ if (avctp_id)
+ avctp_remove_state_cb(avctp_id);
+
dbus_connection_unref(connection);
connection = NULL;
}
@@ -2082,7 +1504,7 @@ static DBusMessage *control_is_connected(DBusConnection *conn,
if (!reply)
return NULL;

- connected = (control->state == AVCTP_STATE_CONNECTED);
+ connected = (control->session != NULL);

dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
DBUS_TYPE_INVALID);
@@ -2090,42 +1512,6 @@ static DBusMessage *control_is_connected(DBusConnection *conn,
return reply;
}

-static int avctp_send_passthrough(struct control *control, uint8_t op)
-{
- unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
- struct avctp_header *avctp = (void *) buf;
- struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
- uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
- int sk = g_io_channel_unix_get_fd(control->io);
- static uint8_t transaction = 0;
-
- memset(buf, 0, sizeof(buf));
-
- avctp->transaction = transaction++;
- avctp->packet_type = AVCTP_PACKET_SINGLE;
- avctp->cr = AVCTP_COMMAND;
- avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
-
- avc->code = CTYPE_CONTROL;
- avc->subunit_type = SUBUNIT_PANEL;
- avc->opcode = OP_PASSTHROUGH;
-
- operands[0] = op & 0x7f;
- operands[1] = 0;
-
- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
-
- /* Button release */
- avctp->transaction = transaction++;
- operands[0] |= 0x80;
-
- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
-
- return 0;
-}
-
static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
void *data)
{
@@ -2133,13 +1519,13 @@ static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
struct control *control = device->control;
int err;

- if (control->state != AVCTP_STATE_CONNECTED)
+ if (!control->session)
return btd_error_not_connected(msg);

if (!control->target)
return btd_error_not_supported(msg);

- err = avctp_send_passthrough(control, VOL_UP_OP);
+ err = avctp_send_passthrough(control->session, VOL_UP_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));

@@ -2153,13 +1539,13 @@ static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
struct control *control = device->control;
int err;

- if (control->state != AVCTP_STATE_CONNECTED)
+ if (!control->session)
return btd_error_not_connected(msg);

if (!control->target)
return btd_error_not_supported(msg);

- err = avctp_send_passthrough(control, VOL_DOWN_OP);
+ err = avctp_send_passthrough(control->session, VOL_DOWN_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));

@@ -2187,7 +1573,7 @@ static DBusMessage *control_get_properties(DBusConnection *conn,
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

/* Connected */
- value = (device->control->state == AVCTP_STATE_CONNECTED);
+ value = (device->control->session != NULL);
dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);

dbus_message_iter_close_container(&iter, &dict);
@@ -2404,8 +1790,8 @@ static void path_unregister(void *data)
DBG("Unregistered interface %s on path %s",
AUDIO_CONTROL_INTERFACE, dev->path);

- if (control->state != AVCTP_STATE_DISCONNECTED)
- avctp_disconnected(dev);
+ if (control->session)
+ avctp_disconnect(control->session);

g_free(control);
dev->control = NULL;
@@ -2420,6 +1806,9 @@ static void mp_path_unregister(void *data)
DBG("Unregistered interface %s on path %s",
MEDIA_PLAYER_INTERFACE, dev->path);

+ if (mp->handler)
+ avctp_unregister_pdu_handler(mp->handler);
+
g_timer_destroy(mp->timer);
g_free(mp);
control->mp = NULL;
@@ -2494,11 +1883,12 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16,

control = g_new0(struct control, 1);
control->dev = dev;
- control->state = AVCTP_STATE_DISCONNECTED;
- control->uinput = -1;

control_update(control, uuid16, media_player);

+ if (!avctp_id)
+ avctp_id = avctp_add_state_cb(state_changed, NULL);
+
return control;
}

@@ -2506,39 +1896,8 @@ gboolean control_is_active(struct audio_device *dev)
{
struct control *control = dev->control;

- if (control && control->state != AVCTP_STATE_DISCONNECTED)
+ if (control && control->session)
return TRUE;

return FALSE;
}
-
-unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
-{
- struct avctp_state_callback *state_cb;
- static unsigned int id = 0;
-
- state_cb = g_new(struct avctp_state_callback, 1);
- state_cb->cb = cb;
- state_cb->user_data = user_data;
- state_cb->id = ++id;
-
- avctp_callbacks = g_slist_append(avctp_callbacks, state_cb);
-
- return state_cb->id;
-}
-
-gboolean avctp_remove_state_cb(unsigned int id)
-{
- GSList *l;
-
- for (l = avctp_callbacks; l != NULL; l = l->next) {
- struct avctp_state_callback *cb = l->data;
- if (cb && cb->id == id) {
- avctp_callbacks = g_slist_remove(avctp_callbacks, cb);
- g_free(cb);
- return TRUE;
- }
- }
-
- return FALSE;
-}
diff --git a/audio/control.h b/audio/control.h
index 77e7595..f5cfef2 100644
--- a/audio/control.h
+++ b/audio/control.h
@@ -25,20 +25,6 @@
#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"

-typedef enum {
- AVCTP_STATE_DISCONNECTED = 0,
- AVCTP_STATE_CONNECTING,
- AVCTP_STATE_CONNECTED
-} avctp_state_t;
-
-typedef void (*avctp_state_cb) (struct audio_device *dev,
- avctp_state_t old_state,
- avctp_state_t new_state,
- void *user_data);
-
-unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
-gboolean avctp_remove_state_cb(unsigned int id);
-
int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
void avrcp_unregister(const bdaddr_t *src);

diff --git a/audio/device.c b/audio/device.c
index ea268bc..16f0701 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -52,6 +52,7 @@
#include "unix.h"
#include "avdtp.h"
#include "control.h"
+#include "avctp.h"
#include "headset.h"
#include "gateway.h"
#include "sink.h"
--
1.7.6.1


2011-09-12 16:33:59

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH BlueZ 3/5 v2] AVRCP: rename avrcp_spec_avc_pdu to avrcp_header

From: Luiz Augusto von Dentz <[email protected]>

Since old avrcp_header got renamed to avc_header now we can use
avrcp_header for AVRCP PDUs.
---
audio/control.c | 44 ++++++++++++++++++++++----------------------
1 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 91ab8d4..cafb951 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -221,7 +221,7 @@ struct avc_header {
} __attribute__ ((packed));
#define AVC_HEADER_LENGTH 3

-struct avrcp_spec_avc_pdu {
+struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
uint8_t packet_type:2;
@@ -229,7 +229,7 @@ struct avrcp_spec_avc_pdu {
uint16_t params_len;
uint8_t params[0];
} __attribute__ ((packed));
-#define AVRCP_SPECAVCPDU_HEADER_LENGTH 7
+#define AVRCP_HEADER_LENGTH 7

#elif __BYTE_ORDER == __BIG_ENDIAN

@@ -251,7 +251,7 @@ struct avc_header {
} __attribute__ ((packed));
#define AVC_HEADER_LENGTH 3

-struct avrcp_spec_avc_pdu {
+struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
uint8_t rsvd:6;
@@ -259,7 +259,7 @@ struct avrcp_spec_avc_pdu {
uint16_t params_len;
uint8_t params[0];
} __attribute__ ((packed));
-#define AVRCP_SPECAVCPDU_HEADER_LENGTH 7
+#define AVRCP_HEADER_LENGTH 7

#else
#error "Unknown byte order"
@@ -722,10 +722,10 @@ static const char *battery_status_to_str(enum battery_status status)
static int avctp_send_event(struct control *control, uint8_t id, void *data)
{
uint8_t buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_SPECAVCPDU_HEADER_LENGTH + 9];
+ AVRCP_HEADER_LENGTH + 9];
struct avctp_header *avctp = (void *) buf;
struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
- struct avrcp_spec_avc_pdu *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
+ struct avrcp_header *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
AVC_HEADER_LENGTH];
int sk;
uint16_t size;
@@ -781,7 +781,7 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)

pdu->params_len = htons(size);
size += AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
- AVRCP_SPECAVCPDU_HEADER_LENGTH;
+ AVRCP_HEADER_LENGTH;

sk = g_io_channel_unix_get_fd(control->io);

@@ -987,7 +987,7 @@ static void mp_set_media_attributes(struct control *control,
}

static uint8_t avrcp_handle_get_capabilities(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1027,7 +1027,7 @@ err:
}

static uint8_t avrcp_handle_list_player_attributes(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1061,7 +1061,7 @@ done:
}

static uint8_t avrcp_handle_list_player_values(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1092,7 +1092,7 @@ err:
}

static uint8_t avrcp_handle_get_element_attributes(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1161,7 +1161,7 @@ err:
}

static uint8_t avrcp_handle_get_current_player_value(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1225,7 +1225,7 @@ err:
}

static uint8_t avrcp_handle_set_player_value(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1278,7 +1278,7 @@ err:
}

static uint8_t avrcp_handle_displayable_charset(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1298,7 +1298,7 @@ static uint8_t avrcp_handle_displayable_charset(struct control *control,
}

static uint8_t avrcp_handle_ct_battery_status(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1325,7 +1325,7 @@ err:
}

static uint8_t avrcp_handle_get_play_status(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1360,7 +1360,7 @@ static uint8_t avrcp_handle_get_play_status(struct control *control,
}

static uint8_t avrcp_handle_register_notification(struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
@@ -1418,7 +1418,7 @@ static struct pdu_handler {
uint8_t pdu_id;
uint8_t code;
uint8_t (*func) (struct control *control,
- struct avrcp_spec_avc_pdu *pdu,
+ struct avrcp_header *pdu,
uint8_t transaction);
} handlers[] = {
{ AVRCP_GET_CAPABILITIES, CTYPE_STATUS,
@@ -1455,7 +1455,7 @@ static int handle_vendordep_pdu(struct control *control,
int operand_count)
{
struct pdu_handler *handler;
- struct avrcp_spec_avc_pdu *pdu = (void *) avc + AVC_HEADER_LENGTH;
+ struct avrcp_header *pdu = (void *) avc + AVC_HEADER_LENGTH;
uint32_t company_id = (pdu->company_id[0] << 16) |
(pdu->company_id[1] << 8) |
(pdu->company_id[2]);
@@ -1469,7 +1469,7 @@ static int handle_vendordep_pdu(struct control *control,
pdu->packet_type = 0;
pdu->rsvd = 0;

- if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH)
+ if (operand_count + 3 < AVRCP_HEADER_LENGTH)
goto err_metadata;

for (handler = handlers; handler; handler++) {
@@ -1489,14 +1489,14 @@ static int handle_vendordep_pdu(struct control *control,

avc->code = handler->func(control, pdu, avctp->transaction);

- return AVC_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH +
+ return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
ntohs(pdu->params_len);

err_metadata:
pdu->params_len = htons(1);
avc->code = CTYPE_REJECTED;

- return AVC_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
+ return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 1;
}

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


2011-09-12 16:33:58

by Luiz Augusto von Dentz

[permalink] [raw]
Subject: [PATCH BlueZ 2/5 v2] AVRCP: rename avrcp_header to avc_header

From: Luiz Augusto von Dentz <[email protected]>

AVCTP carries AV/C packets/PDUs not AVRCP as avrcp_header suggests.
---
audio/control.c | 96 +++++++++++++++++++++++++++---------------------------
1 files changed, 48 insertions(+), 48 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index f84c7f7..91ab8d4 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -212,14 +212,14 @@ struct avctp_header {
} __attribute__ ((packed));
#define AVCTP_HEADER_LENGTH 3

-struct avrcp_header {
+struct avc_header {
uint8_t code:4;
uint8_t _hdr0:4;
uint8_t subunit_id:3;
uint8_t subunit_type:5;
uint8_t opcode;
} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 3
+#define AVC_HEADER_LENGTH 3

struct avrcp_spec_avc_pdu {
uint8_t company_id[3];
@@ -242,14 +242,14 @@ struct avctp_header {
} __attribute__ ((packed));
#define AVCTP_HEADER_LENGTH 3

-struct avrcp_header {
+struct avc_header {
uint8_t _hdr0:4;
uint8_t code:4;
uint8_t subunit_type:5;
uint8_t subunit_id:3;
uint8_t opcode;
} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 3
+#define AVC_HEADER_LENGTH 3

struct avrcp_spec_avc_pdu {
uint8_t company_id[3];
@@ -721,12 +721,12 @@ static const char *battery_status_to_str(enum battery_status status)

static int avctp_send_event(struct control *control, uint8_t id, void *data)
{
- uint8_t buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+ uint8_t buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH + 9];
struct avctp_header *avctp = (void *) buf;
- struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+ struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
struct avrcp_spec_avc_pdu *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
- AVRCP_HEADER_LENGTH];
+ AVC_HEADER_LENGTH];
int sk;
uint16_t size;

@@ -743,9 +743,9 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)
avctp->cr = AVCTP_RESPONSE;
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);

- avrcp->code = CTYPE_CHANGED;
- avrcp->subunit_type = SUBUNIT_PANEL;
- avrcp->opcode = OP_VENDORDEP;
+ avc->code = CTYPE_CHANGED;
+ avc->subunit_type = SUBUNIT_PANEL;
+ avc->opcode = OP_VENDORDEP;

pdu->company_id[0] = IEEEID_BTSIG >> 16;
pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF;
@@ -780,7 +780,7 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)
}

pdu->params_len = htons(size);
- size += AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+ size += AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
AVRCP_SPECAVCPDU_HEADER_LENGTH;

sk = g_io_channel_unix_get_fd(control->io);
@@ -1451,19 +1451,19 @@ static struct pdu_handler {
/* handle vendordep pdu inside an avctp packet */
static int handle_vendordep_pdu(struct control *control,
struct avctp_header *avctp,
- struct avrcp_header *avrcp,
+ struct avc_header *avc,
int operand_count)
{
struct pdu_handler *handler;
- struct avrcp_spec_avc_pdu *pdu = (void *) avrcp + AVRCP_HEADER_LENGTH;
+ struct avrcp_spec_avc_pdu *pdu = (void *) avc + AVC_HEADER_LENGTH;
uint32_t company_id = (pdu->company_id[0] << 16) |
(pdu->company_id[1] << 8) |
(pdu->company_id[2]);

if (company_id != IEEEID_BTSIG ||
pdu->packet_type != AVCTP_PACKET_SINGLE) {
- avrcp->code = CTYPE_NOT_IMPLEMENTED;
- return AVRCP_HEADER_LENGTH;
+ avc->code = CTYPE_NOT_IMPLEMENTED;
+ return AVC_HEADER_LENGTH;
}

pdu->packet_type = 0;
@@ -1477,7 +1477,7 @@ static int handle_vendordep_pdu(struct control *control,
break;
}

- if (!handler || handler->code != avrcp->code) {
+ if (!handler || handler->code != avc->code) {
pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
}
@@ -1487,16 +1487,16 @@ static int handle_vendordep_pdu(struct control *control,
goto err_metadata;
}

- avrcp->code = handler->func(control, pdu, avctp->transaction);
+ avc->code = handler->func(control, pdu, avctp->transaction);

- return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH +
+ return AVC_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH +
ntohs(pdu->params_len);

err_metadata:
pdu->params_len = htons(1);
- avrcp->code = CTYPE_REJECTED;
+ avc->code = CTYPE_REJECTED;

- return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
+ return AVC_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
}

static void avctp_disconnected(struct audio_device *dev)
@@ -1593,7 +1593,7 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
struct control *control = data;
unsigned char buf[1024], *operands;
struct avctp_header *avctp;
- struct avrcp_header *avrcp;
+ struct avc_header *avc;
int ret, packet_size, operand_count, sock;

if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
@@ -1622,65 +1622,65 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
avctp->cr, avctp->ipid, ntohs(avctp->pid));

ret -= sizeof(struct avctp_header);
- if ((unsigned int) ret < sizeof(struct avrcp_header)) {
+ if ((unsigned int) ret < sizeof(struct avc_header)) {
error("Too small AVRCP packet");
goto failed;
}

- avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+ avc = (struct avc_header *) (buf + sizeof(struct avctp_header));

- ret -= sizeof(struct avrcp_header);
+ ret -= sizeof(struct avc_header);

- operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header);
+ operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
operand_count = ret;

- DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+ DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
"opcode 0x%02X, %d operands",
avctp->cr ? "response" : "command",
- avrcp->code, avrcp->subunit_type, avrcp->subunit_id,
- avrcp->opcode, operand_count);
+ avc->code, avc->subunit_type, avc->subunit_id,
+ avc->opcode, operand_count);

if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
avctp->cr = AVCTP_RESPONSE;
- avrcp->code = CTYPE_NOT_IMPLEMENTED;
+ avc->code = CTYPE_NOT_IMPLEMENTED;
} else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
avctp->ipid = 1;
avctp->cr = AVCTP_RESPONSE;
packet_size = sizeof(*avctp);
} else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->code == CTYPE_CONTROL &&
- avrcp->subunit_type == SUBUNIT_PANEL &&
- avrcp->opcode == OP_PASSTHROUGH) {
+ avc->code == CTYPE_CONTROL &&
+ avc->subunit_type == SUBUNIT_PANEL &&
+ avc->opcode == OP_PASSTHROUGH) {
handle_panel_passthrough(control, operands, operand_count);
avctp->cr = AVCTP_RESPONSE;
- avrcp->code = CTYPE_ACCEPTED;
+ avc->code = CTYPE_ACCEPTED;
} else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->code == CTYPE_STATUS &&
- (avrcp->opcode == OP_UNITINFO
- || avrcp->opcode == OP_SUBUNITINFO)) {
+ avc->code == CTYPE_STATUS &&
+ (avc->opcode == OP_UNITINFO
+ || avc->opcode == OP_SUBUNITINFO)) {
avctp->cr = AVCTP_RESPONSE;
- avrcp->code = CTYPE_STABLE;
+ avc->code = CTYPE_STABLE;
/* The first operand should be 0x07 for the UNITINFO response.
* Neither AVRCP (section 22.1, page 117) nor AVC Digital
* Interface Command Set (section 9.2.1, page 45) specs
* explain this value but both use it */
- if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO)
+ if (operand_count >= 1 && avc->opcode == OP_UNITINFO)
operands[0] = 0x07;
if (operand_count >= 2)
operands[1] = SUBUNIT_PANEL << 3;
- DBG("reply to %s", avrcp->opcode == OP_UNITINFO ?
+ DBG("reply to %s", avc->opcode == OP_UNITINFO ?
"OP_UNITINFO" : "OP_SUBUNITINFO");
} else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->opcode == OP_VENDORDEP) {
+ avc->opcode == OP_VENDORDEP) {
int r_size;
operand_count -= 3;
avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avctp, avrcp,
+ r_size = handle_vendordep_pdu(control, avctp, avc,
operand_count);
packet_size = AVCTP_HEADER_LENGTH + r_size;
} else {
avctp->cr = AVCTP_RESPONSE;
- avrcp->code = CTYPE_REJECTED;
+ avc->code = CTYPE_REJECTED;
}

ret = write(sock, buf, packet_size);
@@ -2092,10 +2092,10 @@ static DBusMessage *control_is_connected(DBusConnection *conn,

static int avctp_send_passthrough(struct control *control, uint8_t op)
{
- unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2];
+ unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
struct avctp_header *avctp = (void *) buf;
- struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
- uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
+ struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+ uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
int sk = g_io_channel_unix_get_fd(control->io);
static uint8_t transaction = 0;

@@ -2106,9 +2106,9 @@ static int avctp_send_passthrough(struct control *control, uint8_t op)
avctp->cr = AVCTP_COMMAND;
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);

- avrcp->code = CTYPE_CONTROL;
- avrcp->subunit_type = SUBUNIT_PANEL;
- avrcp->opcode = OP_PASSTHROUGH;
+ avc->code = CTYPE_CONTROL;
+ avc->subunit_type = SUBUNIT_PANEL;
+ avc->opcode = OP_PASSTHROUGH;

operands[0] = op & 0x7f;
operands[1] = 0;
--
1.7.6.1