[PATCH 5/5] Add AVRCP-Metadata 1.3 Processing
From: David Stockwell <hidden>
Date: 2011-08-11 14:16:47
Subsystem:
the rest · Maintainer:
Linus Torvalds
Add AVRCP-Metadata 1.3 Processing Metadata is transferred using Vendor Dependent messages with the Company ID set to that of the Bluetooth SIG: 0x001958. Includes: * Provides for fragmentation over AVCTP, both incoming and outgoing, when message size exceeds MTU. Necessary for AVRCP v1.4 (metadata browsing). * Improved layering between AVRCP Metadata, AVRCP, and AVCTP for clarity and -- I hope -- easier enhancement. * Support for following PDUs: - GET_CAPABILITIES - GET_ELEMENT_ATTRS - REGISTER_NOTIFICATION - GET_PLAY_STATUS - INFORM_CT_BATTERY_STATUS * Support for following Notifications/Events: - PLAYBACK_STATUS_CHANGED - TRACK_CHANGED - TRACK_REACHED_END - TRACK_REACHED_START - PLAYBACK_POSITION_CHANGED - BATTERY_STATUS_CHANGED * The following read-only Properties - Battery (status) - MTU (debug: will probably delete) - PlayInterval (notifies TG of desired play pos updates) - Version (version of AVRCP: 1.3) * Implemented method: ChangeTrack (TG signals to CT that track has changed, plus stores metadata: Title, Artist, Album, Track Duration, etc.) * Implemented method: ChangePlayback (play status of track on TG). * New signal: GetPlayStatus(void), for use by CT that does not use asynchronous Notifications. Signed-off-by: David Stockwell <redacted> --- audio/control.c | 1863 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 1715 insertions(+), 148 deletions(-)
diff --git a/audio/control.c b/audio/control.c
index 472cab2..33b2f4b 100644
--- a/audio/control.c
+++ b/audio/control.c@@ -34,6 +34,7 @@ #include <signal.h> #include <sys/types.h> #include <sys/stat.h> +#include <sys/uio.h> #include <fcntl.h> #include <netinet/in.h>
@@ -206,6 +207,14 @@ #define EVENT_UIDS_CHANGED 0x0C #define EVENT_VOLUME_CHANGED 0x0D +/* AVRCP Play Statuses */ +#define AVRCP_STATUS_STOPPED 0x00 +#define AVRCP_STATUS_PLAYING 0x01 +#define AVRCP_STATUS_PAUSED 0x02 +#define AVRCP_STATUS_FWD_SEEK 0x03 +#define AVRCP_STATUS_REV_SEEK 0x04 +#define AVRCP_STATUS_ERROR 0xFF + /* BT-SIG defined Company ID for Metadata */ #define BTSIG_COMPANY_ID 0x001958
@@ -229,6 +238,16 @@ struct avctp_header { } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 +struct avctp_header_first { + uint8_t ipid:1; + uint8_t cr:1; + uint8_t packet_type:2; + uint8_t transaction:4; + uint8_t packets; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_FIRST_LENGTH 4 + struct avrcp_header { uint8_t ctype:4; uint8_t _hdr0:4;
@@ -238,16 +257,6 @@ struct avrcp_header { } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 3 -struct avrcp_spec_avc_pdu { - 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_SPECAVCPDU_HEADER_LENGTH 7 - #elif __BYTE_ORDER == __BIG_ENDIAN struct avctp_header {
@@ -259,6 +268,16 @@ struct avctp_header { } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 +struct avctp_header_first { + uint8_t transaction:4; + uint8_t packet_type:2; + uint8_t cr:1; + uint8_t ipid:1; + uint8_t packets; + uint16_t pid; +} __attribute__ ((packed)); +#define AVCTP_HEADER_FIRST_LENGTH 4 + struct avrcp_header { uint8_t _hdr0:4; uint8_t ctype:4;
@@ -268,16 +287,6 @@ struct avrcp_header { } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 3 -struct avrcp_spec_avc_pdu { - 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_SPECAVCPDU_HEADER_LENGTH 7 - #else #error "Unknown byte order" #endif
@@ -312,7 +321,7 @@ struct avrcp_passthru { struct avrcp_header hdr; uint8_t key:7; uint8_t key_state:1; - uint8_t op_len; /* =length(company_id+op_data) */ + uint8_t op_len; /* =length(company_id+op_data) */ uint8_t company_id[3]; uint8_t op_data[]; } __attribute__ ((packed));
@@ -355,7 +364,7 @@ struct avrcp_passthru { struct avrcp_header hdr; uint8_t key_state:1; uint8_t key:7; - uint8_t op_len; /* =length(company_id+op_data) */ + uint8_t op_len; /* =length(company_id+op_data) */ uint8_t company_id[3]; uint8_t op_data[]; } __attribute__ ((packed));
@@ -387,17 +396,83 @@ struct avctp_server { uint32_t ct_record_id; }; +#define META_ATTR_TITLE 1 +#define META_ATTR_ARTIST 2 +#define META_ATTR_ALBUM 3 +#define META_ATTR_TRKNUM 4 +#define META_ATTR_TTLTRK 5 +#define META_ATTR_GENRE 6 +#define META_ATTR_TIME 7 +#define META_ATTR_MAX 7 + +struct meta_elem { + uint8_t *elem; + uint16_t elem_len; + uint16_t charset; +}; + +struct meta_elem_hdr { + uint32_t attr_id; + uint16_t charset; + uint16_t attr_len; + uint8_t value[]; +} __attribute__ ((packed)); + struct control { struct audio_device *dev; avctp_state_t state; +/* + * The following transaction counter is incremented when sending + * AVCTP messages + */ + uint8_t transaction; +/* + * Store role of this unit in conversation with other object + */ + uint8_t role; /* property (r/w) */ +/* + * Version of AVRCP Supported + */ + uint16_t version; /* property (rw) */ +/* + * Buffer allocated for receiving AVCTP packets for Commands + * and Responses. + */ + uint8_t *recv_buf; + int recv_buf_size; + int recv_buf_pos; + int recv_pkts; + int recv_trans_id; + +/* Capabilities: Company IDs and Events Supported/Subscribed */ + + uint32_t cap_company_ids[16]; /* property (rw) */ + uint8_t cap_events[16]; /* property (rw) */ + uint8_t sub_events[16]; + +/* Track Metadata */ + + uint64_t trk_uuid; + struct meta_elem trk_elem[META_ATTR_MAX+1]; + +/* Play Status */ + + uint8_t play_state; + uint32_t play_pos; + uint32_t play_length; + uint32_t play_intvl; + +/* CT attributes */ + uint8_t ct_battery; + uint16_t ct_charset; int uinput; GIOChannel *io; - guint io_id; + guint io_id; - uint16_t mtu; + uint16_t mtu; /* property (ro) */ gboolean target;
@@ -496,7 +571,7 @@ static sdp_record_t *avrcp_tg_record(void) sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVCTP_PSM; - uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f; record = sdp_record_alloc(); if (!record)
@@ -552,6 +627,21 @@ static sdp_record_t *avrcp_tg_record(void) } /** + * @brief set_company_id: Three-byte Company_ID to AVRCP message + * + * AVRCP uses three-byte company_ids, which must be moved from + * internal form to BT/network big-endian order. + */ +static void set_company_id(unsigned char *cid, int company_id) +{ + int cid_idx; + for (cid_idx = 2; cid_idx >= 0; --cid_idx) { + *(cid+cid_idx) = company_id & 0xFF; + company_id >>= 8; + } +} + +/** * @brief get_company_id: Three-byte Company_ID from AVRCP message * * AVRCP uses three-byte company_ids, which must be converted from
@@ -590,12 +680,12 @@ static void send_key(int fd, uint16_t key, int pressed) } /** - * @brief: handle_panel_passthrough: Handles AVRCP 1.0+ PASSTHROUGH command. + * @brief: handle_panel_passthrough: Handles AVRCP 1.0+ PASSTHROUGH. * * Original version only passed the keystroke to uinput. * - * Added a Passthrough signal, with the key state AND the optional - * following company_id and vendor-unique message. + * Added a Passthrough signal, with the key state and optional + * company_id and vendor-unique message. */ static void handle_panel_passthrough(struct control *control,
@@ -698,15 +788,6 @@ static void handle_panel_passthrough(struct control *control,
DBG("AVRCP: unknown button 0x%02X %s", avrcp->key, status);
}
-/* handle vendordep pdu inside an avctp packet */
-static int handle_vendordep_pdu(struct control *control,
- struct avrcp_header *avrcp,
- int operand_count)
-{
- avrcp->ctype = CTYPE_NOT_IMPLEMENTED;
- return AVRCP_HEADER_LENGTH;
-}
-
static void avctp_disconnected(struct audio_device *dev)
{
struct control *control = dev->control;@@ -762,8 +843,8 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state) AUDIO_CONTROL_INTERFACE, "Disconnected", DBUS_TYPE_INVALID); emit_property_changed(dev->conn, dev->path, - AUDIO_CONTROL_INTERFACE, "Connected", - DBUS_TYPE_BOOLEAN, &value); + AUDIO_CONTROL_INTERFACE, "Connected", + DBUS_TYPE_BOOLEAN, &value); if (!audio_device_is_active(dev, NULL)) audio_device_set_authorized(dev, FALSE);
@@ -779,8 +860,8 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state)
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_INVALID);
emit_property_changed(control->dev->conn, control->dev->path,
- AUDIO_CONTROL_INTERFACE, "Connected",
- DBUS_TYPE_BOOLEAN, &value);
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
break;
default:
error("Invalid AVCTP state %d", new_state);@@ -795,109 +876,1144 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state)
}
}
-static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
+/**
+ * @brief avrcp_write: Called to transmit AVRCP command or response.
+ *
+ * This function writes/sends the AVRCP message created from various
+ * functions, wrapping it in one or more AVCTP messages.
+ *
+ * If a message is too long to be "written" in a single packet, message
+ * is broken into multiple packets and sent a packet at a time.
+ *
+ * For efficiency, the AVCTP header for each packet is constructed
+ * separately, and prepended to the avrcp message, or its fragments,
+ * using writev.
+ *
+ * For the AV/C control channel, AVRCP messages must not be larger
+ * than 512 bytes. However, unless metadata is ridiculously large,
+ * this limit should never be hit. Therefore, there really is no
+ * need for AVRCP-level fragmentation.
+ */
+
+static int avrcp_write(struct control *control,
+ struct avrcp_header *avrcp, int avrcp_length)
+{
+ struct iovec buffers[2];
+ uint8_t avctp_buff[4]; /* struct avctp_header_first */
+ int socket = g_io_channel_unix_get_fd(control->io);
+ int write_length;
+
+ memset(&avctp_buff, 0, sizeof(avctp_buff));
+
+ DBG("AVRCP Write ctype %u, %i bytes. AVCTP MTU: %i", avrcp->ctype,
+ avrcp_length, control->mtu);
+
+ if (avrcp_length+sizeof(struct avctp_header) <= control->mtu) {
+ struct avctp_header *avctp =
+ (struct avctp_header *) &avctp_buff;
+ avctp->ipid = 0;
+ if (avrcp->ctype >= CTYPE_NOT_IMPLEMENTED)
+ avctp->cr = AVCTP_RESPONSE;
+ else
+ avctp->cr = AVCTP_COMMAND;
+
+ avctp->transaction = ++(control->transaction);
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = sizeof(*avctp);
+ buffers[1].iov_base = (void *) avrcp;
+ buffers[1].iov_len = avrcp_length;
+
+ write_length = writev(socket, buffers, 2);
+ if (write_length < 0)
+ return -errno;
+
+ DBG("AVRCP wrote %i bytes.", write_length);
+
+ return write_length;
+/*
+ * Fragment AVRCP messages longer than AVCTP MTU
+ */
+ } else {
+ int packet_length = 0;
+ int avrcp_offset = 0;
+ int avctp_packets = 0;
+ struct avctp_header_first *avctp =
+ (struct avctp_header_first *) &avctp_buff;
+
+ /* Count number of MTU-sized fragments */
+
+ DBG("AVRCP fragmented write, %u bytes.", avrcp_length);
+ avctp_packets = 1;
+ avrcp_offset = avrcp_length - control->mtu +
+ sizeof(struct avctp_header_first);
+ while (avrcp_offset > 0) {
+ ++avctp_packets;
+ avrcp_offset -= (control->mtu - 1);
+ }
+
+ /* Send first fragment */
+
+ avctp->packets = avctp_packets;
+ avctp->ipid = 0;
+ if (avrcp->ctype >= CTYPE_NOT_IMPLEMENTED) {
+ avctp->cr = AVCTP_RESPONSE;
+ } else {
+ avctp->cr = AVCTP_COMMAND;
+ control->transaction++;
+ }
+ avctp->transaction = control->transaction;
+ avctp->packet_type = AVCTP_PACKET_FIRST;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = sizeof(*avctp);
+ buffers[1].iov_base = (void *) avrcp;
+ buffers[1].iov_len = control->mtu-sizeof(*avctp);
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ avrcp_offset = buffers[1].iov_len;
+
+ /* Send second to n-1 fragments */
+
+ while ((avrcp_length-avrcp_offset) > (control->mtu-1)) {
+ avctp->packet_type = AVCTP_PACKET_CONTINUE;
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) avrcp+avrcp_offset;
+ buffers[1].iov_len = control->mtu - 1;
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ avrcp_offset += buffers[1].iov_len;
+ }
+
+ /* Send final fragment */
+
+ avctp->packet_type = AVCTP_PACKET_END;
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) avrcp + avrcp_offset;
+ buffers[1].iov_len = avrcp_length - avrcp_offset;
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ write_length = avrcp_offset + buffers[1].iov_len;
+ }
+ return write_length;
+}
+
+/**
+ * @brief register_notification
+ *
+ * Confirm CT's notification request in response to the
+ * Register Notification subscription request from the CT.
+ */
+static int register_notification(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length,
+ uint8_t event_id)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int avrcp_resp_len = sizeof(struct avrcp_vendor_dep) + 11;
+ int write_length = 0;
+
+ avrcp_resp =
+ (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_INTERIM;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *)&avrcp_resp->company_id, BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->pdu_data[0] = event_id;
+ avrcp_resp_len = 1;
+
+ switch (event_id) {
+ case EVENT_PLAYBACK_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = control->play_state;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_TRACK_CHANGED: {
+ struct {
+ uint32_t hi;
+ uint32_t lo;
+ } track_id;
+ track_id.hi = htonl(control->trk_uuid >> 32);
+ track_id.lo = htonl(control->trk_uuid & 0xFFFFFFFF);
+ memcpy((void *) &avrcp_resp->pdu_data[1],
+ (void *) &track_id, 8);
+ avrcp_resp_len += 8;
+ break;
+ }
+ case EVENT_TRACK_REACHED_END: {
+ break;
+ }
+ case EVENT_TRACK_REACHED_START: {
+ break;
+ }
+ case EVENT_PLAYBACK_POS_CHANGED: {
+ /*
+ * Registering Event Track Position Changed:
+ * capture and save the notification interval (in seconds).
+ */
+ uint32_t interval;
+ memcpy((void *) &interval,
+ (void *) &avrcp->pdu_data[1], 4);
+ control->play_intvl = ntohl(interval);
+
+ interval = htonl(control->play_pos);
+ memcpy((void *) &avrcp->pdu_data[1],
+ (void *) &interval, 4);
+ avrcp_resp_len += 4;
+ break;
+ }
+ case EVENT_BATT_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = control->ct_battery;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_SYSTEM_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = 0;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_PLAYER_APP_SETTING_CHANGED: {
+ avrcp_resp->pdu_data[1] = 0;
+ avrcp_resp_len++;
+ break;
+ }
+ default:
+ break;
+ }
+ avrcp_resp->length = htons(avrcp_resp_len);
+ avrcp_resp_len += sizeof(struct avrcp_vendor_dep);
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+
+ control->sub_events[event_id] = TRUE;
+
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_companies
+ *
+ * PDU_GET_CAPABILITIES to get list of Company_IDs supported.
+ */
+static int handle_pdu_get_companies(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int i;
+ int write_length;
+ int avrcp_resp_len =
+ sizeof(struct avrcp_vendor_dep) + 2;
+ int company_id_count = 0;
+ while (control->cap_company_ids[company_id_count] > 0) {
+ avrcp_resp_len += 3;
+ company_id_count++;
+ }
+
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *) &avrcp_resp->company_id, BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->length = htons(avrcp_resp_len -
+ sizeof(struct avrcp_vendor_dep));
+ avrcp_resp->pdu_data[0] = 0x02;
+ avrcp_resp->pdu_data[1] = company_id_count;
+
+ for (i = 0; i < company_id_count; ++i) {
+ set_company_id(avrcp_resp->pdu_data + 2 + 3 * i,
+ control->cap_company_ids[i]);
+ };
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_events
+ *
+ * PDU_GET_CAPABILITIES to get list of Notification Events supported.
+ */
+static int handle_pdu_get_events(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int write_length;
+ int i = 0, i_out = 0;
+ int event_count = 0;
+ int avrcp_resp_len;
+
+ for (i = EVENT_PLAYBACK_STATUS_CHANGED;
+ i <= 15; ++i) {
+ if (control->cap_events[i] > 0) {
+ avrcp_resp_len++;
+ event_count++;
+ }
+ }
+
+ avrcp_resp_len =
+ sizeof(struct avrcp_vendor_dep) + 2 +
+ (event_count);
+ avrcp_resp =
+ (struct avrcp_vendor_dep *)
+ g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id =
+ avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type =
+ avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id(
+ (uint8_t *)&avrcp_resp->company_id,
+ BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->length =
+ htons(avrcp_resp_len -
+ sizeof(struct avrcp_vendor_dep));
+ avrcp_resp->pdu_data[0] = 0x03;
+ avrcp_resp->pdu_data[1] = event_count;
+
+ for (i = EVENT_PLAYBACK_STATUS_CHANGED;
+ i <= 15; ++i) {
+ if (control->cap_events[i] > 0) {
+ avrcp_resp->pdu_data[2 + i_out] = i;
+ i_out++;
+ }
+ }
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_reg_notify
+ *
+ * PDU_REGISTER_NOTIFICATION to register CT for asynchronous notifications.
+ */
+static int handle_pdu_reg_notify(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ int write_length;
+
+ if (avrcp->hdr.ctype != CTYPE_NOTIFY) {
+ DBG("RegisterNotification: expected "
+ "CTYPE_NOTIFY, received: 0x%02X",
+ avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+
+ if (!control->cap_events[avrcp->pdu_data[0]]) {
+ DBG("RegisterNotification: requested "
+ "event 0x%02X not supported",
+ avrcp->pdu_data[0]);
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+
+ /* Add current Event to Subscribed Event list. */
+
+ write_length = register_notification(control, avrcp, avrcp_length,
+ avrcp->pdu_data[0]);
+ if (write_length < 0)
+ return write_length;
+
+ if (avrcp->pdu_data[0] ==
+ EVENT_PLAYBACK_POS_CHANGED) {
+ emit_property_changed(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "PlayInterval",
+ DBUS_TYPE_UINT32,
+ &control->play_intvl);
+ }
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_attrs
+ *
+ * PDU_GET_ELEMENT_ATTRS to respond with Metadata to CT.
+ */
+static int handle_pdu_get_attrs(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int i = 0;
+ uint8_t *pelem;
+ int write_length;
+ int elem_length = 0, avrcp_resp_len = 0;
+
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetElementAttrs: expected CTYPE_STATUS, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* Parse the list of requested elements,
+ * calculate the total size of the metadata. */
+
+ DBG("GetElementAttrs received, %d requested",
+ avrcp->pdu_data[8]);
+ for (i = 0; i < avrcp->pdu_data[8]; ++i) {
+ uint32_t elem_id;
+
+ memcpy(&elem_id, &(avrcp->pdu_data[9 + 4 * i]), 4);
+ elem_id = ntohl(elem_id);
+ DBG("GetElementAttr %d requested", elem_id);
+ if (elem_id > 0 && elem_id <= META_ATTR_MAX) {
+ if (control->trk_elem[elem_id].elem) {
+ elem_length +=
+ sizeof(struct meta_elem_hdr) +
+ control->trk_elem[elem_id].elem_len;
+ DBG("GetElementAttr %d, %d bytes", elem_id,
+ control->trk_elem[elem_id].elem_len);
+ } else {
+ elem_length += sizeof(struct meta_elem_hdr);
+ DBG("GetElementAttr %d: no data", elem_id);
+ }
+ } else {
+ /* Reject the GetElements call as invalid */
+ }
+ }
+ avrcp_resp_len = elem_length + sizeof(struct avrcp_vendor_dep) + 1;
+ DBG("GetElementAttr %d bytes, total %d bytes", elem_length,
+ avrcp_resp_len);
+
+ /* Allocate a buffer for the metadata, plus the AVRCP header */
+
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *)&avrcp_resp->company_id, BTSIG_COMPANY_ID);
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->length = htons(elem_length);
+ avrcp_resp->pdu_data[0] = avrcp->pdu_data[8];
+
+ /* Parse the list of requested elements,
+ * copy the metadata into the response */
+
+ pelem = (uint8_t *) avrcp_resp + sizeof(struct avrcp_vendor_dep) + 1;
+ for (i = 0; i < avrcp->pdu_data[8]; ++i) {
+ uint32_t elem_id;
+ struct meta_elem_hdr elem;
+
+ memcpy(&elem_id, &(avrcp->pdu_data[9 + 4 * i]), 4);
+ elem_id = ntohl(elem_id);
+
+ elem.attr_id = htonl(elem_id);
+ elem.attr_len = htons(control->trk_elem[elem_id].elem_len);
+ elem.charset = htons(control->trk_elem[elem_id].charset);
+
+ memcpy((void *) pelem, (void *) &elem,
+ sizeof(struct meta_elem_hdr));
+ pelem += sizeof(struct meta_elem_hdr);
+
+ memcpy((void *) pelem,
+ (void *) control->trk_elem[elem_id].elem,
+ control->trk_elem[elem_id].elem_len);
+ pelem += control->trk_elem[elem_id].elem_len;
+ }
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_play_stat
+ *
+ * PDU_GET_PLAY_STATUS to respond with Track Status to CT.
+ */
+static int handle_pdu_play_stat(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int write_length;
+ int avrcp_resp_len = 0;
+
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetPlayStatus: expected CTYPE_STATUS, received: 0x%02X",
+ avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* GetPlayStatus has no parameters. */
+
+ DBG("GetPlayStatus received");
+
+ g_dbus_emit_signal(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "GetPlayStatus",
+ DBUS_TYPE_INVALID);
+
+ /* Allocate a buffer for the response, including AVRCP header */
+
+ avrcp_resp_len = sizeof(struct avrcp_vendor_dep) + 9;
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *) &avrcp_resp->company_id, BTSIG_COMPANY_ID);
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->length = htons(9);
+ memcpy(&avrcp_resp->pdu_data[0], &control->play_length, 4);
+ memcpy(&avrcp_resp->pdu_data[4], &control->play_pos, 4);
+ avrcp_resp->pdu_data[8] = control->play_state;
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_battery_stat
+ *
+ * PDU_INFORM_CT_BATTERY_STAT to receive battery state from CT.
+ */
+static int handle_pdu_battery_stat(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ if (avrcp->hdr.ctype != CTYPE_CONTROL) {
+ DBG("BatteryStatus: expected CTYPE_CONTROL, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control, (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* BatteryStatus has one parameter:
+ * store in control and emit property. */
+
+ DBG("BatteryStatus received");
+ if (control->ct_battery != avrcp->pdu_data[0]) {
+ control->ct_battery = avrcp->pdu_data[0];
+ emit_property_changed(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Battery",
+ DBUS_TYPE_BYTE, &control->ct_battery);
+ }
+ /* Response is shorter...just reuse it. */
+
+ avrcp->hdr.ctype = CTYPE_STABLE;
+ avrcp->length = 0;
+
+ return avrcp_write(control, (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_vendor_dep));
+}
+
+/**
+ * @brief handle_avrcp_meta_cmd
+ *
+ * When this function is called, we already know we have a valid
+ * AVRCP Metadata command. From this point, we branch based on the PDU.
+ */
+static int handle_avrcp_meta_cmd(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ switch (avrcp->pdu_id) {
+ case PDU_GET_CAPABILITIES:
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetCapabilities: expected CTYPE_STATUS, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+ if (avrcp->pdu_data[0] == 0x02) {
+ return handle_pdu_get_companies(control,
+ avrcp, avrcp_length);
+ } else if (avrcp->pdu_data[0] == 0x03) {
+ return handle_pdu_get_events(control,
+ avrcp, avrcp_length);
+ }
+
+ /* No other capabilities supported in AVRCP spec */
+
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ avrcp_length);
+
+ case PDU_REGISTER_NOTIFICATION:
+ return handle_pdu_reg_notify(control, avrcp, avrcp_length);
+
+ case PDU_GET_ELEMENT_ATTRS:
+ return handle_pdu_get_attrs(control, avrcp, avrcp_length);
+
+ case PDU_GET_PLAY_STATUS:
+ return handle_pdu_play_stat(control, avrcp, avrcp_length);
+
+ case PDU_INFORM_CT_BATTERY_STAT:
+ return handle_pdu_battery_stat(control, avrcp, avrcp_length);
+
+ case PDU_INFORM_DISPLAYABLE_CHAR_SET:
+
+ case PDU_LIST_PLAYER_APP_SETTING_ATTRS:
+ case PDU_LIST_PLAYER_APP_SETTING_VALS:
+
+ case PDU_GET_CURR_PLAYER_APP_SETTING_VALUE:
+ case PDU_SET_PLAYER_APP_SETTING_VALUE:
+
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+
+ /*
+ * Following cases are not implemented:
+ * Unless metadata is allowed to be unlimited, there is
+ * no case where an AVRCP packet will exceed AVC Control
+ * Channel MTU (512 bytes).
+ */
+ case PDU_REGISTER_CONTINUING_RESPONSE:
+ case PDU_ABORT_CONTINUING_RESPONSE:
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ /*
+ * The following are all AVRCP 1.4+ PDUs.
+ */
+ case PDU_GET_PLAYER_APP_SETTING_ATTR_TEXT:
+ case PDU_GET_PLAYER_APP_SETTING_VALUE_TEXT:
+ case PDU_SET_ABSOLUTE_VOLUME:
+ case PDU_SET_ADDRESSED_PLAYER:
+ case PDU_SET_BROWSED_PLAYER:
+ case PDU_GET_FOLDER_ITEMS:
+ case PDU_CHANGE_PATH:
+ case PDU_GET_ITEM_ATTRS:
+ case PDU_PLAY_ITEM:
+ case PDU_SEARCH:
+ case PDU_ADD_TO_NOW_PLAYING:
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+
+ default: /* invalid PDU */
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ }
+}
+
+/**
+ * @brief handle_avrcp_meta_resp
+ *
+ * When this function is called, we have a response
+ * to our previous AVRCP Metadata command. Branch based on the PDU.
+ *
+ * Only used with the BlueZ as CT case (unimplemented).
+ */
+static int handle_avrcp_meta_resp(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+/*
+ * Operating as TG, there is little reason for handling Metadata responses.
+ */
+ DBG("STUB: handle_avrcp_meta_resp");
+ return 0;
+}
+
+/**
+ * @brief handle_avrcp_message: Parse (assembled) AVRCP message.
+ *
+ * This function returns the AVRCP message, which is usually contained in
+ * a single AVCTP packet.
+ *
+ * If multiple AVCTP packets are needed to transmit a single AVRCP message
+ * (very short MTU), the packets are reassembled in the lower layer and
+ * presented to this layer as a whole message.
+ */
+static int handle_avrcp_message(struct control *control,
+ struct avrcp_header *avrcp, int avrcp_length, int trans_id) {
+
+ DBG("AVRCP %p, length %d, transid %u", avrcp, avrcp_length, trans_id);
+/*
+ * There are two fundamental cases, AVRCP messages received as Commands,
+ * and Responses to messages sent from this system.
+ */
+ if (avrcp->ctype < CTYPE_NOT_IMPLEMENTED) {
+ DBG("AVRCP COMMAND 0x%01X, subunit_type 0x%02X, "
+ "subunit_id 0x%01X, opcode 0x%02X, length %d",
+ avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
+ avrcp->opcode, avrcp_length);
+
+ switch (avrcp->opcode) {
+ case OP_UNITINFO: {
+ struct avrcp_unitinfo *uin =
+ (struct avrcp_unitinfo *) avrcp;
+ if (avrcp->ctype != CTYPE_STATUS) {
+ error("OP_UNITINFO received, ctype expected: "
+ "0x%02X, actual: 0x%02X",
+ CTYPE_STATUS, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ uin->ext_code = 0x07;
+ uin->unit_type = SUBUNIT_PANEL;
+ uin->unit = 0;
+ set_company_id((uint8_t *) &uin->company_id,
+ BTSIG_COMPANY_ID);
+
+ avrcp->ctype = CTYPE_STABLE;
+ return avrcp_write(control, avrcp,
+ sizeof(struct avrcp_unitinfo));
+ }
+ case OP_SUBUNITINFO: {
+ struct avrcp_subunitinfo *suin =
+ (struct avrcp_subunitinfo *) avrcp;
+ if (avrcp->ctype != CTYPE_STATUS) {
+ error("OP_SUBUNITINFO received, ctype "
+ "expected: 0x%02X, actual: 0x%02X",
+ CTYPE_STATUS, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ if (suin->page == 0) {
+ suin->subunit[0].subunit_type = SUBUNIT_PANEL;
+ suin->subunit[0].subunit_id_max = 0;
+ };
+ avrcp->ctype = CTYPE_STABLE;
+ return avrcp_write(control, avrcp,
+ sizeof(struct avrcp_subunitinfo));
+ }
+ case OP_PASSTHROUGH: {
+ struct avrcp_passthru *pass =
+ (struct avrcp_passthru *) avrcp;
+ if (avrcp->ctype != CTYPE_CONTROL) {
+ error("OP_PASSTHROUGH received, ctype "
+ "expected: 0x%02x, actual: 0x%02X",
+ CTYPE_CONTROL, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+ if (avrcp->subunit_type != SUBUNIT_PANEL) {
+ error("OP_PASSTHROUGH received, not directed "
+ "to PANEL: 0x%02X",
+ avrcp->subunit_type);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ handle_panel_passthrough(control,
+ (struct avrcp_passthru *) avrcp,
+ avrcp_length);
+
+ avrcp->ctype = CTYPE_ACCEPTED;
+ pass->op_len = 0;
+ return avrcp_write(control, avrcp,
+ offsetof(struct avrcp_passthru, company_id));
+ }
+ case OP_VENDOR_DEPENDENT: {
+ struct avrcp_vendor_dep *vend =
+ (struct avrcp_vendor_dep *) avrcp;
+ DBG("OP_VENDOR_DEPENDENT received, ctype 0x%02X",
+ avrcp->ctype);
+
+ /* If Version supported is not 1.3 or greater,
+ * reject this call. */
+
+ if (get_company_id((uint8_t *) &vend->company_id) ==
+ BTSIG_COMPANY_ID) {
+ if (control->version >= 0x0103) {
+ return handle_avrcp_meta_cmd(control,
+ vend,
+ avrcp_length);
+ } else {
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+ }
+
+ /* FIXME: If not a standard Metadata VD message,
+ * need to pass to the App as is via signal. */
+
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control, avrcp, avrcp_length);
+ }
+ default:
+ DBG("Unknown opcode received: 0x%02X, ctype: 0x%02X",
+ avrcp->opcode, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control, avrcp, avrcp_length);
+ }
+
+ /* Handle AVRCP message Responses to our Command messages */
+
+ } else {
+ DBG("AVRCP RESPONSE 0x%01X, subunit_type 0x%02X, "
+ "subunit_id 0x%01X, opcode 0x%02X, length %d",
+ avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
+ avrcp->opcode, avrcp_length);
+
+ switch (avrcp->opcode) {
+ case OP_UNITINFO:
+ if (avrcp->ctype != CTYPE_STABLE)
+ error("OP_UNITINFO response, ctype expected: "
+ "0x%02X, actual: 0x%02X",
+ CTYPE_STABLE, avrcp->ctype);
+ break;
+
+ case OP_SUBUNITINFO:
+ if (avrcp->ctype != CTYPE_STABLE)
+ error("OP_SUBUNITINFO response, ctype "
+ "expected: 0x%02X, actual: 0x%02X",
+ CTYPE_STABLE, avrcp->ctype);
+ break;
+
+ case OP_PASSTHROUGH:
+ if (avrcp->ctype != CTYPE_ACCEPTED)
+ error("OP_PASSTHROUGH response, ctype "
+ "expected: 0x%02x, actual: 0x%02X",
+ CTYPE_ACCEPTED, avrcp->ctype);
+
+ if (avrcp->subunit_type != SUBUNIT_PANEL)
+ error("OP_PASSTHROUGH response, not directed "
+ "to PANEL: 0x%02X",
+ avrcp->subunit_type);
+ break;
+
+ case OP_VENDOR_DEPENDENT: {
+ struct avrcp_vendor_dep *vend =
+ (struct avrcp_vendor_dep *) avrcp;
+ DBG("OP_VENDOR_DEPENDENT response, ctype 0x%02x",
+ avrcp->ctype);
+ /*
+ * FIXME: If Version supported is not 1.3 or greater,
+ * reject this call.
+ */
+ if (get_company_id((uint8_t *) &vend->company_id) ==
+ BTSIG_COMPANY_ID)
+ return handle_avrcp_meta_resp(control,
+ vend,
+ avrcp_length);
+ /*
+ * FIXME: If not a standard BT-SIG Metadata VD
+ * message, pass to the App as-is via a TBD signal.
+ */
+ break;
+ }
+
+ default:
+ error("Unknown opcode response: 0x%02X, ctype: "
+ "0x%02X", avrcp->opcode,
+ avrcp->ctype);
+ return -1;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+/**
+ * @brief avctp_packet_failed: Support function for avctp_packet_cb
+ *
+ * Called to deal with unrecoverable receive/send errors when receiving
+ * and responding to received AVCTP packets.
+ */
+static gboolean avctp_packet_failed(struct control *control)
+{
+ DBG("AVCTP session %p got disconnected", control);
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ return FALSE;
+}
+
+/**
+ * @brief avctp_packet_reject: Support function for avctp_packet_cb
+ *
+ * Called when rejecting an invalid AVCTP packet, primarily with an
+ * PID other than AV_REMOTE_SVCLASS_ID.
+ */
+static gboolean avctp_packet_reject(struct control *control,
+ struct avctp_header *avctp, int packet_size)
+{
+ int ret;
+ int socket = g_io_channel_unix_get_fd(control->io);
+
+ ret = write(socket, (uint8_t *) avctp, packet_size);
+ if (ret != packet_size)
+ return avctp_packet_failed(control);
+
+ return TRUE;
+}
+
+/**
+ * @brief avctp_packet_cb: Message received from "other" device.
+ *
+ * This function returns the AVCTP message, (soon) reassembles any
+ * fragmented packets, then peels out the AVRCP message and presents
+ * it to avrcp_message_cb.
+ *
+ * This layering of received message handling should prove easier
+ * to understand and debug (for future maintainers) than handling
+ * all in the same callback function.
+ */
+static gboolean avctp_packet_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
struct control *control = data;
- unsigned char buf[1024], *operands;
+ struct iovec buffers[2];
+ uint8_t buf[1024];
struct avctp_header *avctp;
struct avrcp_header *avrcp;
- int ret, packet_size, operand_count, sock;
+ int ret, packet_size, socket;
if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
- goto failed;
+ return avctp_packet_failed(control);
+
+ socket = g_io_channel_unix_get_fd(control->io);
+ avctp = (struct avctp_header *) buf;
+
+ /*
+ * If not fragmented
+ */
+ if (!control->recv_buf) {
+ ret = read(socket, buf, sizeof(buf));
+ if (ret <= 0)
+ return avctp_packet_failed(control);
+ } else {
- sock = g_io_channel_unix_get_fd(control->io);
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) control->recv_buf +
+ control->recv_buf_pos;
+ buffers[1].iov_len = control->mtu - 1;
- ret = read(sock, buf, sizeof(buf));
- if (ret <= 0)
- goto failed;
+ ret = readv(socket, buffers, 2);
+ if (ret <= 0)
+ return avctp_packet_failed(control);
+ }
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;
+ error("AVCTP packet too small");
+ return avctp_packet_failed(control);
}
packet_size = ret;
- avctp = (struct avctp_header *) buf;
+ DBG("AVCTP %p, transaction %u, packet type %u, C/R %u, IPID %u, "
+ "PID 0x%04X", avctp,
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE) {
+ if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ avctp->cr = AVCTP_RESPONSE;
+ packet_size = sizeof(struct avctp_header);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }
- DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+ if ((packet_size-sizeof(struct avctp_header)) <
+ sizeof(struct avrcp_header)) {
+ error("AVCTP single packet too small for "
+ "AVRCP header");
+ return avctp_packet_failed(control);
+ }
+ avrcp = (struct avrcp_header *) ((void *) avctp +
+ sizeof(struct avctp_header));
+ DBG("AVRCP %p: ctype %u, subunit id %u, subunit type %u, "
+ "opcode %u",
+ avrcp, avrcp->ctype, avrcp->subunit_id,
+ avrcp->subunit_type, avrcp->opcode);
+ } else if (avctp->packet_type == AVCTP_PACKET_FIRST) {
+ struct avctp_header_first *avctpf =
+ (struct avctp_header_first *) avctp;
+ if (avctpf->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctpf->ipid = 1;
+ avctpf->cr = AVCTP_RESPONSE;
+ packet_size = sizeof(struct avctp_header_first);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }
+ if ((packet_size-sizeof(struct avctp_header_first)) <
+ sizeof(struct avrcp_header)) {
+ error("AVCTP first packet too small for AVRCP header");
+ return avctp_packet_failed(control);
+ }
+ avrcp = (struct avrcp_header *) ((void *) avctpf +
+ sizeof(struct avctp_header_first));
+ DBG("AVRCP %p: ctype %u, subunit id %u, subunit type %u, "
+ "opcode %u",
+ avrcp, avrcp->ctype, avrcp->subunit_id,
+ avrcp->subunit_type, avrcp->opcode);
+ }
+
+ /* Make sure the AVCTP cr matches the AVRCP ctype */
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE ||
+ avctp->packet_type == AVCTP_PACKET_FIRST) {
+ if (!avctp->cr && avrcp->ctype >= CTYPE_NOT_IMPLEMENTED) {
+ error("AVRCP Response code 0x%02X received "
+ "in AVCTP Command packet", avrcp->ctype);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->ctype = CTYPE_REJECTED;
+ /* Fragmented Packet in Progress := FALSE */
+ packet_size = sizeof(struct avrcp_header);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+
+
+ } else if (avctp->cr && avrcp->ctype < CTYPE_NOT_IMPLEMENTED) {
+ error("AVRCP Command code 0x%02X received "
+ "in AVCTP Response packet", avrcp->ctype);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->ctype = CTYPE_REJECTED;
+ packet_size = sizeof(struct avrcp_header);
+ /* Fragmented Packet in Progress := FALSE */
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }
+ }
+
+ switch (avctp->packet_type) {
+ case AVCTP_PACKET_SINGLE: {
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u, "
"PID 0x%04X",
avctp->transaction, avctp->packet_type,
avctp->cr, avctp->ipid, ntohs(avctp->pid));
+ avrcp = (struct avrcp_header *)
+ (buf + sizeof(struct avctp_header));
+ packet_size -= sizeof(struct avctp_header);
- ret -= sizeof(struct avctp_header);
- if ((unsigned int) ret < sizeof(struct avrcp_header)) {
- error("Too small AVRCP packet");
- goto failed;
- }
+ /* If (Fragmented Packet in Progress),
+ * Single packet acceptable only if Passthrough */
- avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+ return handle_avrcp_message(control,
+ avrcp, packet_size,
+ avctp->transaction);
+ break;
+ }
+ case AVCTP_PACKET_FIRST: {
+ struct avctp_header_first *avctpf =
+ (struct avctp_header_first *) avctp;
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u, "
+ "PID 0x%04X, packets %d",
+ avctpf->transaction, avctpf->packet_type,
+ avctpf->cr, avctpf->ipid, ntohs(avctpf->pid),
+ avctpf->packets);
+ packet_size -= sizeof(struct avctp_header_first);
+ control->recv_trans_id = avctpf->transaction;
+ control->recv_pkts = avctpf->packets - 1;
+ control->recv_buf = g_malloc(control->recv_pkts * packet_size);
+ control->recv_buf_pos = 0;
+
+ /* Copy first part of AVRCP message to recv_buf */
+
+ memcpy(control->recv_buf,
+ buf + sizeof(struct avctp_header_first), packet_size);
+
+ /* Fragmented Packet in Progress := TRUE */
+
+ control->recv_buf_pos = packet_size;
+ break;
+ }
+ case AVCTP_PACKET_CONTINUE: {
+ /* IF NOT fragmented packet in progress, REJECT */
- operands = buf + sizeof(struct avctp_header) + sizeof(struct
avrcp_header);
- operand_count = ret - sizeof(struct avrcp_header);
+ /* IF received trans_ID does not match first, REJECT */
- DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
- "opcode 0x%02X, %d operands",
- avctp->cr ? "response" : "command",
- avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
- avrcp->opcode, operand_count);
-
- if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = 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->ctype == CTYPE_CONTROL &&
- avrcp->subunit_type == SUBUNIT_PANEL &&
- avrcp->opcode == OP_PASSTHROUGH) {
- handle_panel_passthrough(control,
- (struct avrcp_passthru *) avrcp, ret);
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_ACCEPTED;
- } else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->ctype == CTYPE_STATUS &&
- (avrcp->opcode == OP_UNITINFO
- || avrcp->opcode == OP_SUBUNITINFO)) {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = 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)
- operands[0] = 0x07;
- if (operand_count >= 2)
- operands[1] = SUBUNIT_PANEL << 3;
- DBG("reply to %s", avrcp->opcode == OP_UNITINFO ?
- "OP_UNITINFO" : "OP_SUBUNITINFO");
- } else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->opcode == OP_VENDORDEP) {
- int r_size;
- operand_count -= 3;
- avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avrcp, operand_count);
- packet_size = AVCTP_HEADER_LENGTH + r_size;
- } else {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_REJECTED;
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid);
+ control->recv_pkts--;
+ control->recv_buf_pos = 0;
+ break;
}
- ret = write(sock, buf, packet_size);
- if (ret != packet_size)
- goto failed;
+ case AVCTP_PACKET_END: {
+ /* IF NOT fragmented packet in progress, REJECT */
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, "
+ "IPID %u",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid);
+ avrcp = (struct avrcp_header *) control->recv_buf;
+ control->recv_pkts--;
+ control->recv_buf_size =
+ control->recv_buf_pos + packet_size;
+ control->recv_buf_pos = 0;
+
+ /* Fragmented Packet in Progress := FALSE */
+ /* call avrcp_message_cb */
+
+ return handle_avrcp_message(control,
+ avrcp, packet_size,
+ avctp->transaction);
+ break;
+ }
+ default:
+ break;
+ }
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)@@ -1011,10 +2127,15 @@ static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
init_uinput(control);
avctp_set_state(control, AVCTP_STATE_CONNECTED);
+ if (control->mtu != imtu) {
+ emit_property_changed(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "MTU",
+ DBUS_TYPE_UINT16, &imtu);
+ }
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);
+ (GIOFunc) avctp_packet_cb, control);
}
static void auth_cb(DBusError *derr, void *user_data)@@ -1088,7 +2209,7 @@ static void avctp_confirm_cb(GIOChannel *chan, gpointer data) goto drop; control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL, - control_cb, control); + avctp_packet_cb, control); return; drop:
@@ -1261,6 +2382,413 @@ void avrcp_unregister(const bdaddr_t *src) connection = NULL; } +/** + * @brief send_notification + * + * Called to send a notification response, triggered by a + * ChangeTrack or ChangePlayback method call from the TG. + */ +static int send_notification(struct control *control, + uint8_t event_id) +{ + struct avrcp_vendor_dep *avrcp; + int avrcp_length, write_length; + DBG("Send Notification: %d", event_id); + /* + * If the event is not supported or not subscribed, + * there is nothing to do. + */ + if (!control->cap_events[event_id] || !control->sub_events[event_id]) + return 1; + + avrcp_length = sizeof(struct avrcp_vendor_dep) + 11; + avrcp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_length); + + avrcp->hdr.ctype = CTYPE_CHANGED; + avrcp->hdr.subunit_id = 0x07; + avrcp->hdr.subunit_type = SUBUNIT_PANEL; + avrcp->hdr.opcode = OP_VENDOR_DEPENDENT; + set_company_id((uint8_t *)&avrcp->company_id, BTSIG_COMPANY_ID); + + avrcp->pdu_id = PDU_REGISTER_NOTIFICATION; + avrcp->packet_type = AVCTP_PACKET_SINGLE; + avrcp->pdu_data[0] = event_id; + avrcp_length = 1; + + switch (event_id) { + case EVENT_PLAYBACK_STATUS_CHANGED: { + avrcp->pdu_data[1] = control->play_state; + avrcp_length++; + break; + } + case EVENT_TRACK_CHANGED: { + struct { + uint32_t hi; + uint32_t lo; + } track_id; + track_id.hi = htonl(control->trk_uuid >> 32); + track_id.lo = htonl(control->trk_uuid & 0xFFFFFFFF); + memcpy((void *) &avrcp->pdu_data[1], + (void *) &track_id, 8); + avrcp_length += 8; + break; + } + case EVENT_TRACK_REACHED_END: { + break; + } + case EVENT_TRACK_REACHED_START: { + break; + } + case EVENT_PLAYBACK_POS_CHANGED: { + /* + * Registering Event Track Position Changed: + * capture and save the notification interval + * (in seconds). + */ + uint32_t interval = htonl(control->play_pos); + memcpy((void *) &avrcp->pdu_data[1], + (void *) &interval, 4); + avrcp_length += 4; + break; + } + case EVENT_BATT_STATUS_CHANGED: { + avrcp->pdu_data[1] = control->ct_battery; + avrcp_length++; + break; + } + case EVENT_SYSTEM_STATUS_CHANGED: { + avrcp->pdu_data[1] = 0; + avrcp_length++; + break; + } + case EVENT_PLAYER_APP_SETTING_CHANGED: { + avrcp->pdu_data[1] = 0; + avrcp_length++; + break; + } + default: + break; + } + avrcp->length = htons(avrcp_length); + avrcp_length += sizeof(struct avrcp_vendor_dep); + + write_length = avrcp_write(control, + (struct avrcp_header *) avrcp, + avrcp_length); + + control->sub_events[event_id] = FALSE; + + g_free(avrcp); + return write_length; +} + +/** + * @brief: parse_track_properties + * + * Iterates through the dict parameter to ChangeTrack storing + * metadata elements into Strings for convenience in sending + * on to CT. + */ +static int parse_track_properties(DBusMessageIter *props, + struct control *control, + uint64_t tmp_trkid) +{ + const char *tmp_title, *tmp_artist, *tmp_album, *tmp_genre; + dbus_int32_t tmp_trknum = -1, tmp_numtrks = -1, tmp_trkdur = -1; + gboolean has_title = FALSE; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + DBG("Key %s returned", key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "Title") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_title); + DBG("%s %s returned", key, tmp_title); + has_title = TRUE; + } else if (strcasecmp(key, "Artist") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_artist); + DBG("%s %s returned", key, tmp_artist); + } else if (strcasecmp(key, "Album") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_album); + DBG("%s %s returned", key, tmp_album); + } else if (strcasecmp(key, "Genre") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_genre); + DBG("%s %s returned", key, tmp_genre); + } else if (strcasecmp(key, "TrackNumber") == 0) { + if (var != DBUS_TYPE_INT32) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_trknum); + DBG("%s %d returned", key, tmp_trknum); + } else if (strcasecmp(key, "NumberOfTracks") == 0) { + if (var != DBUS_TYPE_INT32) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_numtrks); + DBG("%s %d returned", key, tmp_numtrks); + } else if (strcasecmp(key, "TrackDuration") == 0) { + if (var != DBUS_TYPE_INT32) + return -EINVAL; + dbus_message_iter_get_basic(&value, &tmp_trkdur); + DBG("%s %d returned", key, tmp_trkdur); + } + dbus_message_iter_next(props); + } + + if (!tmp_title) + return -EINVAL; + + /* Clear the previous Track-ID and Metadata from the Control block. */ + + if (control->trk_elem[META_ATTR_TITLE].elem) + g_free(control->trk_elem[META_ATTR_TITLE].elem); + control->trk_elem[META_ATTR_TITLE].elem_len = 0; + + if (control->trk_elem[META_ATTR_ARTIST].elem) + g_free(control->trk_elem[META_ATTR_ARTIST].elem); + control->trk_elem[META_ATTR_ARTIST].elem_len = 0; + + if (control->trk_elem[META_ATTR_ALBUM].elem) + g_free(control->trk_elem[META_ATTR_ALBUM].elem); + control->trk_elem[META_ATTR_ALBUM].elem_len = 0; + + if (control->trk_elem[META_ATTR_GENRE].elem) + g_free(control->trk_elem[META_ATTR_GENRE].elem); + control->trk_elem[META_ATTR_GENRE].elem_len = 0; + + if (control->trk_elem[META_ATTR_TRKNUM].elem) + g_free(control->trk_elem[META_ATTR_TRKNUM].elem); + control->trk_elem[META_ATTR_TRKNUM].elem_len = 0; + + if (control->trk_elem[META_ATTR_TTLTRK].elem) + g_free(control->trk_elem[META_ATTR_TTLTRK].elem); + control->trk_elem[META_ATTR_TTLTRK].elem_len = 0; + + if (control->trk_elem[META_ATTR_TIME].elem) + g_free(control->trk_elem[META_ATTR_TIME].elem); + control->trk_elem[META_ATTR_TIME].elem_len = 0; + + /* Move the retrieved Track-ID and Metadata to the Control block. */ + + control->trk_uuid = tmp_trkid; + + control->trk_elem[META_ATTR_TITLE].elem = + (uint8_t *) g_strdup(tmp_title); + control->trk_elem[META_ATTR_TITLE].elem_len = + strlen(tmp_title); + control->trk_elem[META_ATTR_TITLE].charset = + control->ct_charset; + + if (tmp_artist) { + control->trk_elem[META_ATTR_ARTIST].elem = + (uint8_t *) g_strdup(tmp_artist); + control->trk_elem[META_ATTR_ARTIST].elem_len = + strlen(tmp_artist); + control->trk_elem[META_ATTR_ARTIST].charset = + control->ct_charset; + } + + if (tmp_album) { + control->trk_elem[META_ATTR_ALBUM].elem = + (uint8_t *) g_strdup(tmp_album); + control->trk_elem[META_ATTR_ALBUM].elem_len = + strlen(tmp_album); + control->trk_elem[META_ATTR_ALBUM].charset = + control->ct_charset; + } + + if (tmp_genre) { + control->trk_elem[META_ATTR_GENRE].elem = + (uint8_t *) g_strdup(tmp_genre); + control->trk_elem[META_ATTR_GENRE].elem_len = + strlen(tmp_genre); + control->trk_elem[META_ATTR_GENRE].charset = + control->ct_charset; + } + + if (tmp_trknum >= 0) { + control->trk_elem[META_ATTR_TRKNUM].elem = + (uint8_t *) g_strdup_printf("%d", tmp_trknum); + control->trk_elem[META_ATTR_TRKNUM].elem_len = + strlen((const char *) + control->trk_elem[META_ATTR_TRKNUM].elem); + control->trk_elem[META_ATTR_TRKNUM].charset = + control->ct_charset; + } + + if (tmp_numtrks >= 0) { + control->trk_elem[META_ATTR_TTLTRK].elem = + (uint8_t *) g_strdup_printf("%d", tmp_numtrks); + control->trk_elem[META_ATTR_TTLTRK].elem_len = + strlen((const char *) + control->trk_elem[META_ATTR_TTLTRK].elem); + control->trk_elem[META_ATTR_TTLTRK].charset = + control->ct_charset; + } + + if (tmp_trkdur >= 0) { + control->trk_elem[META_ATTR_TIME].elem = + (uint8_t *) g_strdup_printf("%d", tmp_trkdur); + control->trk_elem[META_ATTR_TIME].elem_len = + strlen((const char *) + control->trk_elem[META_ATTR_TIME].elem); + control->trk_elem[META_ATTR_TIME].charset = + control->ct_charset; + } + + return 0; +} + +/** + * @brief: control_change_track + * + * Implements ChangeTrack method + */ +static DBusMessage *control_change_track(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + int write_length; + + DBusMessageIter args, props; + uint64_t tmp_trkid, prev_trkid; + + DBG("Change Track received"); + + prev_trkid = control->trk_uuid; + + dbus_message_iter_init(msg, &args); + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UINT64) + return btd_error_invalid_args(msg); + dbus_message_iter_get_basic(&args, &tmp_trkid); + dbus_message_iter_next(&args); + +#if __WORDSIZE == 32 + DBG("Track ID: 0x%llx", tmp_trkid); +#else + DBG("Track ID: 0x%lx", tmp_trkid); +#endif + + /* Recurse to fetch the a{sv} Hash Table */ + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) + return btd_error_invalid_args(msg); + + if (parse_track_properties(&props, control, tmp_trkid) < 0) + return btd_error_invalid_args(msg); + + /* Notify CT of any events triggered by change-of-track */ + + if (prev_trkid != (uint64_t) -1) { + + if (control->play_pos == control->play_length) { + write_length = send_notification(control, + EVENT_TRACK_REACHED_END); + if (write_length < 0) + return btd_error_failed(msg, + strerror(-write_length)); + } + + control->play_state = AVRCP_STATUS_STOPPED; + write_length = send_notification(control, + EVENT_PLAYBACK_STATUS_CHANGED); + if (write_length < 0) + return btd_error_failed(msg, strerror(-write_length)); + } + + write_length = send_notification(control, EVENT_TRACK_CHANGED); + if (write_length < 0) + return btd_error_failed(msg, strerror(-write_length)); + + write_length = send_notification(control, EVENT_TRACK_REACHED_START); + if (write_length < 0) + return btd_error_failed(msg, strerror(-write_length)); + + return dbus_message_new_method_return(msg); +} + +/** + * @brief: control_change_playback + * + * Implements ChangePlayback method + */ +static DBusMessage *control_change_playback(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + struct audio_device *device = data; + struct control *control = device->control; + int write_length; + + DBusMessageIter args, props, value; + const char *key; + + dbus_message_iter_init(msg, &args); + + if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + dbus_message_iter_get_basic(&args, &key); + dbus_message_iter_next(&args); + + dbus_message_iter_recurse(&args, &props); + if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + dbus_message_iter_recurse(&props, &value); + if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_INT32) + return btd_error_invalid_args(msg); + + if (strcasecmp(key, "playing") == 0) { + dbus_message_iter_get_basic(&value, &control->play_pos); + control->play_state = AVRCP_STATUS_PLAYING; + } else if (strcasecmp(key, "stopped") == 0) { + control->play_state = AVRCP_STATUS_STOPPED; + } else if (strcasecmp(key, "paused") == 0) { + control->play_state = AVRCP_STATUS_PAUSED; + } else if (strcasecmp(key, "forward-seek") == 0) { + dbus_message_iter_get_basic(&value, &control->play_pos); + control->play_state = AVRCP_STATUS_FWD_SEEK; + } else if (strcasecmp(key, "reverse-seek") == 0) { + dbus_message_iter_get_basic(&value, &control->play_pos); + control->play_state = AVRCP_STATUS_REV_SEEK; + } else if (strcasecmp(key, "at-start") == 0) { + control->play_pos = 0; + control->play_state = AVRCP_STATUS_STOPPED; + } else if (strcasecmp(key, "at-end") == 0) { + control->play_pos = control->play_length; + control->play_state = AVRCP_STATUS_STOPPED; + } else if (strcasecmp(key, "error") == 0) { + control->play_state = AVRCP_STATUS_ERROR; + } else { + return btd_error_invalid_args(msg); + } + + write_length = send_notification(control, + EVENT_PLAYBACK_STATUS_CHANGED); + if (write_length < 0) + return btd_error_failed(msg, strerror(-write_length)); + + return dbus_message_new_method_return(msg); +} + static DBusMessage *control_is_connected(DBusConnection *conn, DBusMessage *msg, void *data)
@@ -1282,38 +2810,36 @@ static DBusMessage *control_is_connected(DBusConnection *conn,
return reply;
}
-static int avctp_send_passthrough(struct control *control, uint8_t op)
+static int avrcp_send_passthrough(struct control *control, uint8_t op)
{
- unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_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];
- int sk = g_io_channel_unix_get_fd(control->io);
- static uint8_t transaction = 0;
+ uint8_t buf[sizeof(struct avrcp_passthru)];
+ struct avrcp_passthru *avrcp = (void *) &buf;
+ /* Do not send anything beyond op length (zero) */
+ int avrcp_length = offsetof(struct avrcp_passthru, company_id);
+ int err;
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);
-
- avrcp->ctype = CTYPE_CONTROL;
- avrcp->subunit_type = SUBUNIT_PANEL;
- avrcp->opcode = OP_PASSTHROUGH;
+ avrcp->hdr.ctype = CTYPE_CONTROL;
+ avrcp->hdr.subunit_type = SUBUNIT_PANEL;
+ avrcp->hdr.opcode = OP_PASSTHROUGH;
- operands[0] = op & 0x7f;
- operands[1] = 0;
+ avrcp->key = op;
+ avrcp->key_state = 0;
+ avrcp->op_len = 0;
- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
+ err = avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ if (err < 0)
+ return err;
/* Button release */
- avctp->transaction = transaction++;
- operands[0] |= 0x80;
+ avrcp->key_state = 1;
- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
+ err = avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ if (err < 0)
+ return err;
return 0;
}@@ -1331,7 +2857,7 @@ static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, if (!control->target) return btd_error_not_supported(msg); - err = avctp_send_passthrough(control, VOL_UP_OP); + err = avrcp_send_passthrough(control, VOL_UP_OP); if (err < 0) return btd_error_failed(msg, strerror(-err));
@@ -1351,7 +2877,7 @@ static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg, if (!control->target) return btd_error_not_supported(msg); - err = avctp_send_passthrough(control, VOL_DOWN_OP); + err = avrcp_send_passthrough(control, VOL_DOWN_OP); if (err < 0) return btd_error_failed(msg, strerror(-err));
@@ -1362,10 +2888,12 @@ static DBusMessage *control_get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct audio_device *device = data;
+ struct control *control = device->control;
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter dict;
gboolean value;
+ char *version;
reply = dbus_message_new_method_return(msg);
if (!reply)@@ -1378,29 +2906,45 @@ static DBusMessage *control_get_properties(DBusConnection *conn,
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+ /* Battery */
+ dict_append_entry(&dict, "Battery", DBUS_TYPE_BYTE,
+ &control->ct_battery);
/* Connected */
value = (device->control->state == AVCTP_STATE_CONNECTED);
dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+ /* MTU */
+ dict_append_entry(&dict, "MTU", DBUS_TYPE_UINT16, &control->mtu);
+ /* PlayInterval */
+ dict_append_entry(&dict, "PlayInterval", DBUS_TYPE_UINT32,
+ &control->play_intvl);
+ /* Version */
+ version = g_strdup_printf("%d.%d", control->version / 256,
+ control->version % 256);
+ dict_append_entry(&dict, "Version", DBUS_TYPE_STRING, &version);
dbus_message_iter_close_container(&iter, &dict);
+ g_free(version);
return reply;
}
static GDBusMethodTable control_methods[] = {
{ "IsConnected", "", "b", control_is_connected,
- G_DBUS_METHOD_FLAG_DEPRECATED },
+ G_DBUS_METHOD_FLAG_DEPRECATED },
{ "GetProperties", "", "a{sv}",control_get_properties },
{ "VolumeUp", "", "", volume_up },
{ "VolumeDown", "", "", volume_down },
+ { "ChangeTrack", "ta{sv}", "", control_change_track },
+ { "ChangePlayback", "si", "", control_change_playback },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable control_signals[] = {
- { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
- { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
- { "PropertyChanged", "sv" },
- { "Passthrough", "ybis" },
+ { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "PropertyChanged", "sv" },
+ { "GetPlayStatus", "" },
+ { "Passthrough", "ybis" },
{ NULL, NULL }
};
@@ -1451,6 +2995,29 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16) control->state = AVCTP_STATE_DISCONNECTED; control->uinput = -1; + /* + * FIXME: Initialize Company IDs, Event Capabilities from + * gbl_ default settings, to be set from audio.conf. + */ + + control->role = 1; /* TG */ + control->ct_battery = 0; + control->ct_charset = 0x6A; /* UTF-8 */ + + control->version = 0x0103; /* AVRCP v1.3 */ + + control->cap_company_ids[0] = BTSIG_COMPANY_ID; + + control->cap_events[EVENT_PLAYBACK_STATUS_CHANGED] = TRUE; + control->cap_events[EVENT_TRACK_CHANGED] = TRUE; + control->cap_events[EVENT_TRACK_REACHED_END] = TRUE; + control->cap_events[EVENT_TRACK_REACHED_START] = TRUE; + control->cap_events[EVENT_PLAYBACK_POS_CHANGED] = TRUE; + + control->play_state = AVRCP_STATUS_STOPPED; + control->trk_uuid = (uint64_t) -1; + control->play_pos = (uint32_t) -1; + if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) control->target = TRUE; -- 1.7.4.4