Browse files

Bluetooth: AVRCP 1.3 features changes (1/2)

Adds AVRCP 1.3 Profile Features to Android, for sending meta-data over bluetooth (track/artist/album/etc)

Merged the following from ICS:
Change-Id: de0bdbd24e17c2d174535c5675f2f05017064f5c
Change-Id: c93d82c400136c2645940f12970f0afe0eca7751
Change-Id: 76aafe23e2uef06b1040fb954de8039eddf8c918
Change-Id: f58618dfaf40527ad054853f05d45b5e5f9cabea
Change-Id: 83f8c50831d467af52ba73246258239c8a356cb8
Change-Id: 1bb723ce2ca7abc248346766eeba6951637d3d9c
Change-Id: 93a2ad997ba8455a47f6040ccd4eca6830308796

       modified:   audio/control.c
       modified:   audio/device.c
       modified:   audio/sink.c

Signed-off-by: Trey Briggs <treybriggs@gmail.com>
  • Loading branch information...
1 parent dfe961f commit 044ea765fb2eec56bf693790cca46ff803690994 Trey Briggs committed with Aug 4, 2012
Showing with 850 additions and 17 deletions.
  1. +834 −16 audio/control.c
  2. +1 −1 audio/device.c
  3. +15 −0 audio/sink.c
View
850 audio/control.c
@@ -60,6 +60,7 @@
#include "dbus-common.h"
#define AVCTP_PSM 23
+#define AVCTP_BROWSING_PSM 0x001B
/* Message types */
#define AVCTP_COMMAND 0
@@ -78,11 +79,15 @@
#define CTYPE_ACCEPTED 0x9
#define CTYPE_REJECTED 0xA
#define CTYPE_STABLE 0xC
+#define CTYPE_NOTIFY 0x3
+#define CTYPE_INTERIM 0xF
+#define CTYPE_CHANGED 0xD
/* opcodes */
#define OP_UNITINFO 0x30
#define OP_SUBUNITINFO 0x31
#define OP_PASSTHROUGH 0x7c
+#define OP_VENDORDEPENDENT 0x0
/* subunits of interest */
#define SUBUNIT_PANEL 0x09
@@ -102,6 +107,72 @@
#define BACKWARD_OP 0x4c
#define QUIRK_NO_RELEASE 1 << 0
+#define QUIRK_NO_CAPABILITIES 1 << 1
+#define QUIRK_NO_METADATA 1 << 2
+#define QUIRK_NO_NOTIFICATIONS 1 << 3
+
+/* BT SIG IDs */
+#define SIG_ID_BTSIG 0X1958
+
+/* AVRCP1.3 PDU IDs */
+#define PDU_GET_CAPABILITY_ID 0x10
+#define PDU_LIST_PLAYER_APP_SETT_ATTR 0x11
+#define PDU_GET_ELEMENT_ATTRIBUTES 0x20
+#define PDU_RGR_NOTIFICATION_ID 0X31
+#define PDU_REQ_CONTINUE_RSP_ID 0X40
+#define PDU_ABORT_CONTINUE_RSP_ID 0X41
+#define PDU_GET_PLAY_STATUS_ID 0x30
+
+/* AVRCP1.3 Capability IDs */
+#define CAP_COMPANY_ID 0X2
+#define CAP_EVENTS_SUPPORTED_ID 0X3
+
+/* AVRCP1.3 Supported Events */
+#define EVENT_PLAYBACK_STATUS_CHANGED 0x1
+#define EVENT_TRACK_CHANGED 0x2
+#define EVENT_AVAILABLE_PLAYERS_CHANGED 0xa
+#define EVENT_ADDRESSED_PLAYER_CHANGED 0xb
+
+/* AVRCP1.3 Error/Staus Codes */
+#define ERROR_INVALID_PDU 0x00
+#define ERROR_INVALID_PARAMETER 0x01
+#define ERROR_PARAM_NOT_FOUND 0X02
+#define ERROR_INTERNAL 0X03
+#define STATUS_OP_COMPLETED 0X04
+#define STATUS_UID_CHANGED 0X05
+#define ERROR_INVALID_DIRECTION 0X07
+#define ERROR_NO_DIRECTORY 0X08
+#define ERROR_UID_NOT_EXIST 0X09
+
+/* AVRCP1.3 MetaData Attributes ID */
+#define METADATA_TITLE 0X1
+#define METADATA_ARTIST 0X2
+#define METADATA_ALBUM 0X3
+#define METADATA_MEDIA_NUMBER 0X4
+#define METADATA_TOTAL_MEDIA 0X5
+#define METADATA_GENRE 0X6
+#define METADATA_PLAYING_TIME 0X7
+
+#define METADATA_SUPPORTED_MASK 0x5F
+#define METADATA_MAX_FIELDS 6
+#define METADATA_MAX_STRING_LEN 150
+#define METADATA_MAX_NUMBER_LEN 40
+#define DEFAULT_METADATA_STRING "Unknown"
+#define DEFAULT_METADATA_NUMBER "1234567890"
+#define AVRCP_MAX_PKT_SIZE 512
+
+/* AVRCP1.3 Character set */
+#define CHARACTER_SET_UTF8 0X6A
+
+/* AVRCP1.3 Playback status */
+#define STATUS_STOPPED 0X00
+#define STATUS_PLAYING 0X01
+#define STATUS_PAUSED 0X02
+#define STATUS_FWD_SEEK 0X03
+#define STATUS_REV_SEEK 0X04
+#define STATUS_ERROR 0XFF
+
+#define IEEEID_BTSIG 0x001958
static DBusConnection *connection = NULL;
static gchar *input_device_name = NULL;
@@ -116,16 +187,24 @@ struct avctp_header {
uint8_t transaction:4;
uint16_t pid;
} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
struct avrcp_header {
+ struct avctp_header avctp;
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
+
+struct avrcp_params {
+ struct avrcp_header avrcp;
+ uint8_t company_id[3];
+ uint8_t pdu_id;
+ uint8_t packet_type:2;
+ uint8_t reserved:6;
+ uint16_t param_len;
+} __attribute__ ((packed));
#elif __BYTE_ORDER == __BIG_ENDIAN
@@ -136,21 +215,72 @@ struct avctp_header {
uint8_t ipid:1;
uint16_t pid;
} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
struct avrcp_header {
+ struct avctp_header avctp;
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
+
+struct avrcp_params {
+ struct avrcp_header avrcp;
+ uint8_t company_id[3];
+ uint8_t pdu_id;
+ uint8_t reserved:6;
+ uint8_t packet_type:2;
+ uint16_t param_len;
+} __attribute__ ((packed));
#else
#error "Unknown byte order"
#endif
+struct avrcp_caps {
+ struct avrcp_params params;
+ uint8_t capability_id;
+} __attribute__ ((packed));
+
+struct avrcp_cmd_get_elem_attributes_req {
+ struct avrcp_params params;
+ uint64_t identifier;
+ uint8_t count;
+ uint32_t attributes[0];
+} __attribute__ ((packed));
+
+struct meta_data_field {
+ uint32_t att_id;
+ uint16_t char_set_id;
+ uint16_t att_len;
+ char val[0];
+} __attribute__ ((packed));
+
+struct avrcp_play_status {
+ struct avrcp_params params;
+ uint32_t song_len;
+ uint32_t song_pos;
+ uint8_t play_status;
+} __attribute__ ((packed));
+
+struct avrcp_event {
+ struct avrcp_caps caps;
+ union {
+ struct {
+ uint32_t track_high;
+ uint32_t track_low;
+ } track_changed;
+ struct {
+ uint8_t status;
+ } playback_status_changed;
+ struct player_changed {
+ uint16_t player_id;
+ uint16_t uid_counter;
+ } player_changed;
+ } event;
+} __attribute__ ((packed));
+
struct avctp_state_callback {
avctp_state_cb cb;
void *user_data;
@@ -166,6 +296,28 @@ struct avctp_server {
#endif
};
+struct meta_data {
+ gchar *title;
+ gchar *artist;
+ gchar *album;
+ gchar *media_number;
+ gchar *total_media_count;
+ gchar *playing_time;
+ gchar *remaining_mdata;
+ int remaining_mdata_len;
+ uint8_t trans_id_event_track;
+ uint8_t trans_id_event_playback;
+ uint8_t trans_id_event_addressed_player;
+ uint8_t trans_id_event_available_player;
+ uint8_t trans_id_get_play_status;
+ gboolean reg_track_changed;
+ gboolean reg_playback_status;
+ gboolean reg_addressed_player;
+ gboolean reg_available_player;
+ gboolean req_get_play_status;
+ uint8_t current_play_status;
+};
+
struct control {
struct audio_device *dev;
@@ -181,6 +333,10 @@ struct control {
gboolean target;
uint8_t key_quirks[256];
+ uint8_t avrcp_quirks;
+
+ gboolean ignore_pause;
+ struct meta_data *mdata;
};
static struct {
@@ -202,6 +358,15 @@ static GSList *avctp_callbacks = NULL;
static void auth_cb(DBusError *derr, void *user_data);
+static int send_meta_data(struct control *control, uint8_t trans_id,
+ uint8_t att_mask);
+static int send_meta_data_continue_response(struct control *control,
+ uint8_t trans_id);
+static int send_notification(struct control *control,
+ uint16_t event_id, uint16_t event_data);
+static int send_play_status(struct control *control, uint32_t song_len,
+ uint32_t song_position, uint8_t play_status);
+
static sdp_record_t *avrcp_ct_record(void)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -275,7 +440,8 @@ 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 browsing_psm = AVCTP_BROWSING_PSM;
+ uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
#ifdef ANDROID
feat = 0x0001;
@@ -491,14 +657,23 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state)
}
}
+static void set_company_id(uint8_t cid[3], const uint32_t cid_in)
+{
+ cid[0] = cid_in >> 16;
+ cid[1] = cid_in >> 8;
+ cid[2] = cid_in;
+}
+
static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
struct control *control = data;
unsigned char buf[1024], *operands;
struct avctp_header *avctp;
struct avrcp_header *avrcp;
+ struct avrcp_params *params;
int ret, packet_size, operand_count, sock;
+ struct meta_data *mdata = control->mdata;
if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
goto failed;
@@ -525,18 +700,16 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
avctp->transaction, avctp->packet_type,
avctp->cr, avctp->ipid, ntohs(avctp->pid));
- ret -= sizeof(struct avctp_header);
if ((unsigned int) ret < sizeof(struct avrcp_header)) {
error("Too small AVRCP packet");
goto failed;
}
- avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
-
- ret -= sizeof(struct avrcp_header);
+ avrcp = (struct avrcp_header *) buf;
- operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header);
- operand_count = ret;
+ operands = buf + sizeof(struct avrcp_header);
+ operand_count = ret - sizeof(struct avrcp_header);
+ params = (struct avrcp_params *)buf;
DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
"opcode 0x%02X, %d operands",
@@ -574,6 +747,128 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
operands[1] = SUBUNIT_PANEL << 3;
DBG("reply to %s", avrcp->opcode == OP_UNITINFO ?
"OP_UNITINFO" : "OP_SUBUNITINFO");
+ } else if (avctp->cr == AVCTP_COMMAND &&
+ (avrcp->code == CTYPE_STATUS || avrcp->code == CTYPE_NOTIFY
+ || avrcp->code == CTYPE_CONTROL) &&
+ avrcp->opcode == OP_VENDORDEPENDENT) {
+ int error_code = -1;
+ struct avrcp_caps *caps = (struct avrcp_caps *) buf;
+ operands = (unsigned char *)caps + sizeof(struct avrcp_caps);
+
+ DBG("Got Vendor Dep opcode");
+
+ if (params->pdu_id == PDU_GET_CAPABILITY_ID &&
+ !(control->avrcp_quirks & QUIRK_NO_CAPABILITIES)) {
+ DBG("Pdu id is PDU_GET_CAPABILITY_ID");
+ avctp->cr = AVCTP_RESPONSE;
+ if (caps->capability_id == CAP_COMPANY_ID) {
+ avrcp->code = CTYPE_STABLE;
+ params->param_len = htons(5);
+ operands[0] = 0x1; // Capability Count
+ set_company_id(operands + 1, IEEEID_BTSIG);
+ packet_size = sizeof(struct avrcp_caps) + 4;
+ } else if (caps->capability_id == CAP_EVENTS_SUPPORTED_ID) {
+ avrcp->code = CTYPE_STABLE;
+ params->param_len = htons(4);
+ operands[0] = 0x2; // Capability Count
+ operands[1] = EVENT_PLAYBACK_STATUS_CHANGED;
+ operands[2] = EVENT_TRACK_CHANGED;
+ packet_size = sizeof(struct avrcp_caps) + 3;
+ } else {
+ error_code = ERROR_INVALID_PARAMETER;
+ }
+ } else if (params->pdu_id == PDU_LIST_PLAYER_APP_SETT_ATTR &&
+ !(control->avrcp_quirks & QUIRK_NO_CAPABILITIES)) {
+ operands = (unsigned char *)caps + sizeof(struct avrcp_params);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_STABLE;
+ params->param_len = htons(1);
+ operands[0] = 0; // attribute count
+ packet_size = sizeof(struct avrcp_params) + 1;
+ } else if (params->pdu_id == PDU_GET_ELEMENT_ATTRIBUTES &&
+ !(control->avrcp_quirks & QUIRK_NO_METADATA)) {
+ DBG("Pdu id is PDU_GET_ELEMENT_ATTRIBUTES");
+ struct avrcp_cmd_get_elem_attributes_req *attr =
+ (struct avrcp_cmd_get_elem_attributes_req *) params;
+ DBG("Received att_count is %d", attr->count);
+ uint8_t att_mask = 0, index;
+ for (index = 0; index < attr->count; index++) {
+ int att_val = htonl(attr->attributes[index]);
+ int mask = 1 << (att_val - 1);
+ if (mask & METADATA_SUPPORTED_MASK) {
+ att_mask |= mask;
+ }
+ }
+ if (att_mask == 0) {
+ att_mask = METADATA_SUPPORTED_MASK;
+ }
+ DBG("MetaData mask is %d att_count is %d", att_mask, attr->count);
+ send_meta_data(control, avctp->transaction, att_mask);
+ return TRUE;
+ } else if (params->pdu_id == PDU_REQ_CONTINUE_RSP_ID) {
+ if (mdata->remaining_mdata_len == 0) {
+ error_code = ERROR_INVALID_PARAMETER;
+ } else {
+ send_meta_data_continue_response(control, avctp->transaction);
+ return TRUE;
+ }
+
+ } else if (params->pdu_id == PDU_ABORT_CONTINUE_RSP_ID) {
+ if (mdata->remaining_mdata_len == 0) {
+ error_code = ERROR_INVALID_PARAMETER;
+ } else {
+ mdata->remaining_mdata_len = 0;
+ g_free(mdata->remaining_mdata);
+ mdata->remaining_mdata = NULL;
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_STABLE;
+ }
+ } else if (params->pdu_id == PDU_RGR_NOTIFICATION_ID &&
+ !(control->avrcp_quirks & QUIRK_NO_NOTIFICATIONS)) {
+ avctp->cr = AVCTP_RESPONSE;
+ if (caps->capability_id == EVENT_TRACK_CHANGED) {
+ mdata->trans_id_event_track = avctp->transaction;
+ mdata->reg_track_changed = TRUE;
+ avrcp->code = CTYPE_INTERIM;
+ int index;
+ if (mdata->current_play_status == STATUS_STOPPED) {
+ for (index = 0; index < 8; index++, operands++)
+ *operands = 0xFF;
+ } else {
+ for (index = 0; index < 8; index++, operands++)
+ *operands = 0x00;
+ }
+ params->param_len = htons(9);
+ packet_size = sizeof(struct avrcp_caps) + 8;
+ } else if (caps->capability_id == EVENT_PLAYBACK_STATUS_CHANGED) {
+ mdata->trans_id_event_playback = avctp->transaction;
+ mdata->reg_playback_status = TRUE;
+ avrcp->code = CTYPE_INTERIM;
+ params->param_len = htons(2);
+ *operands = mdata->current_play_status;
+ packet_size = sizeof(struct avrcp_caps) + 1;
+ } else {
+ error_code = ERROR_INVALID_PARAMETER;
+ }
+ } else if (params->pdu_id == PDU_GET_PLAY_STATUS_ID &&
+ !(control->avrcp_quirks & QUIRK_NO_NOTIFICATIONS)) {
+ g_dbus_emit_signal(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "GetPlayStatus",
+ DBUS_TYPE_INVALID);
+ mdata->trans_id_get_play_status = avctp->transaction;
+ mdata->req_get_play_status = TRUE;
+ return TRUE;
+ } else {
+ error_code = ERROR_INVALID_PDU;
+ }
+
+ if (error_code >= 0) {
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->code = CTYPE_REJECTED;
+ params->param_len = htons(1);
+ caps->capability_id = error_code;
+ packet_size = sizeof(struct avrcp_caps);
+ }
} else {
avctp->cr = AVCTP_RESPONSE;
avrcp->code = CTYPE_REJECTED;
@@ -993,11 +1288,11 @@ 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];
- 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);
+ unsigned char buf[sizeof(struct avrcp_header) + 2];
+ struct avrcp_header *avrcp = (struct avrcp_header *) buf;
+ struct avctp_header *avctp = &avrcp->avctp;
+ uint8_t *operands = &buf[sizeof(struct avrcp_header)];
+ int err, sk = g_io_channel_unix_get_fd(control->io);
static uint8_t transaction = 0;
memset(buf, 0, sizeof(buf));
@@ -1077,6 +1372,121 @@ static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
return dbus_message_new_method_return(msg);
}
+static DBusMessage *update_notification(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ struct meta_data *mdata = control->mdata;
+ DBusMessage *reply;
+ uint16_t event_id;
+ uint64_t event_data;
+ int err;
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT16, &event_id,
+ DBUS_TYPE_UINT64, &event_data,
+ DBUS_TYPE_INVALID) == FALSE) {
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+ }
+
+ DBG("Notification data is %d %d", (int)event_id, (int)event_data);
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotConnected",
+ "Device not Connected");
+
+ send_notification(control, event_id, event_data);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *update_play_status(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ struct meta_data *mdata = control->mdata;
+ DBusMessage *reply;
+ uint32_t duration, position;
+ uint32_t play_status;
+ int err;
+ DBG("update_play_status called");
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &duration,
+ DBUS_TYPE_UINT32, &position,
+ DBUS_TYPE_UINT32, &play_status,
+ DBUS_TYPE_INVALID) == FALSE) {
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+ }
+
+ DBG("PlayStatus data is %d %d %d", duration, position, play_status);
+ mdata->current_play_status = (uint8_t)play_status;
+
+ if (control->state != AVCTP_STATE_CONNECTED)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".NotConnected",
+ "Device not Connected");
+
+ send_play_status(control, duration, position, play_status);
+
+ return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *update_metadata(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ struct meta_data *mdata = control->mdata;
+ DBusMessage *reply;
+ const gchar *title, *artist, *album;
+ const gchar *media_number, *total_media_count, *playing_time;
+ int err;
+
+ if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &title,
+ DBUS_TYPE_STRING, &artist,
+ DBUS_TYPE_STRING, &album,
+ DBUS_TYPE_STRING, &media_number,
+ DBUS_TYPE_STRING, &total_media_count,
+ DBUS_TYPE_STRING, &playing_time,
+ DBUS_TYPE_INVALID) == FALSE) {
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+ }
+
+ DBG("MetaData is %s %s %s %s %s %s", title, artist, album, media_number,
+ total_media_count, playing_time);
+ if (strlen(title) < METADATA_MAX_STRING_LEN)
+ strcpy(mdata->title, title);
+ else
+ strncpy(mdata->title, title, (METADATA_MAX_STRING_LEN-1));
+ if (strlen(artist) < METADATA_MAX_STRING_LEN)
+ strcpy(mdata->artist, artist);
+ else
+ strncpy(mdata->artist, artist, (METADATA_MAX_STRING_LEN-1));
+ if (strlen(album) < METADATA_MAX_STRING_LEN)
+ strcpy(mdata->album, album);
+ else
+ strncpy(mdata->album, album, (METADATA_MAX_STRING_LEN-1));
+ if (strlen(media_number) < METADATA_MAX_NUMBER_LEN)
+ strcpy(mdata->media_number, media_number);
+ else
+ strncpy(mdata->media_number, media_number, (METADATA_MAX_NUMBER_LEN-1));
+ if (strlen(total_media_count) < METADATA_MAX_NUMBER_LEN)
+ strcpy(mdata->total_media_count, total_media_count);
+ else
+ strncpy(mdata->total_media_count, total_media_count, (METADATA_MAX_NUMBER_LEN-1));
+ if (strlen(playing_time) < METADATA_MAX_NUMBER_LEN)
+ strcpy(mdata->playing_time, playing_time);
+ else
+ strncpy(mdata->playing_time, playing_time, (METADATA_MAX_NUMBER_LEN-1));
+
+ return dbus_message_new_method_return(msg);
+}
+
static DBusMessage *control_get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
@@ -1112,16 +1522,53 @@ static GDBusMethodTable control_methods[] = {
{ "GetProperties", "", "a{sv}",control_get_properties },
{ "VolumeUp", "", "", volume_up },
{ "VolumeDown", "", "", volume_down },
+ { "UpdateMetaData", "ssssss", "", update_metadata },
+ { "UpdatePlayStatus", "uuu", "", update_play_status },
+ { "UpdateNotification", "qt", "", update_notification },
{ NULL, NULL, NULL, NULL }
};
static GDBusSignalTable control_signals[] = {
{ "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
{ "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
{ "PropertyChanged", "sv" },
+ { "GetPlayStatus", "" },
{ NULL, NULL }
};
+static void metadata_cleanup(struct meta_data *mdata) {
+ if (mdata->title) {
+ g_free(mdata->title);
+ mdata->title = NULL;
+ }
+ if (mdata->artist) {
+ g_free(mdata->artist);
+ mdata->artist = NULL;
+ }
+ if (mdata->album) {
+ g_free(mdata->album);
+ mdata->album = NULL;
+ }
+ if (mdata->media_number) {
+ g_free(mdata->media_number);
+ mdata->media_number = NULL;
+ }
+ if (mdata->total_media_count) {
+ g_free(mdata->total_media_count);
+ mdata->total_media_count = NULL;
+ }
+ if (mdata->playing_time) {
+ g_free(mdata->playing_time);
+ mdata->playing_time = NULL;
+ }
+ if (mdata->remaining_mdata) {
+ g_free(mdata->remaining_mdata);
+ mdata->remaining_mdata = NULL;
+ mdata->remaining_mdata_len = 0;
+ }
+
+}
+
static void path_unregister(void *data)
{
struct audio_device *dev = data;
@@ -1133,6 +1580,8 @@ static void path_unregister(void *data)
if (control->state != AVCTP_STATE_DISCONNECTED)
avctp_disconnected(dev);
+ metadata_cleanup(control->mdata);
+ g_free(control->mdata);
g_free(control);
dev->control = NULL;
}
@@ -1151,9 +1600,34 @@ void control_update(struct audio_device *dev, uint16_t uuid16)
control->target = TRUE;
}
+static uint8_t get_avrcp_quirks(struct audio_device *dev)
+{
+ char name[MAX_NAME_LENGTH + 1];
+ uint8_t quirks = 0;
+
+ device_get_name(dev->btd_dev, name, sizeof(name));
+
+ if (g_str_equal(name, "Ford Audio")) {
+ /* The Sony car stereo Ford is using under their brand as
+ * '6000 CD' has a completely broken AVRCP 1.3 implementation.
+ * After recognizing AVRCP 1.3 TG capabilities and exchanging
+ * a few PDUs, the car stereo disconnects and reconnects BT,
+ * also resetting USB devices if connected to it.
+ * To avoid that and allow at least HFP and A2DP to work,
+ * prevent any AVRCP 1.3 PDUs from being sent.
+ */
+ quirks |= QUIRK_NO_CAPABILITIES;
+ quirks |= QUIRK_NO_METADATA;
+ quirks |= QUIRK_NO_NOTIFICATIONS;
+ }
+
+ return quirks;
+}
+
struct control *control_init(struct audio_device *dev, uint16_t uuid16)
{
struct control *control;
+ struct meta_data *mdata;
if (!g_dbus_register_interface(dev->conn, dev->path,
AUDIO_CONTROL_INTERFACE,
@@ -1168,10 +1642,54 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16)
control->dev = dev;
control->state = AVCTP_STATE_DISCONNECTED;
control->uinput = -1;
+ control->avrcp_quirks = get_avrcp_quirks(dev);
if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
control->target = TRUE;
+ mdata = g_new0(struct meta_data, 1);
+ if (!mdata) {
+ DBG("No Memory available for meta data");
+ return NULL;
+ }
+ mdata->title = g_new0(gchar, METADATA_MAX_STRING_LEN);
+ mdata->artist = g_new0(gchar, METADATA_MAX_STRING_LEN);
+ mdata->album = g_new0(gchar, METADATA_MAX_STRING_LEN);
+ mdata->media_number = g_new0(gchar, METADATA_MAX_NUMBER_LEN);
+ mdata->total_media_count = g_new0(gchar, METADATA_MAX_NUMBER_LEN);
+ mdata->playing_time = g_new0(gchar, METADATA_MAX_NUMBER_LEN);
+
+ if (!(mdata->title) || !(mdata->artist) || !(mdata->album) ||
+ !(mdata->media_number) || !(mdata->total_media_count) ||
+ !(mdata->playing_time)) {
+ DBG("No Memory available for meta data");
+ metadata_cleanup(mdata);
+ g_free(mdata);
+ return NULL;
+ }
+
+ strcpy(mdata->title,DEFAULT_METADATA_STRING);
+ strcpy(mdata->artist,DEFAULT_METADATA_STRING);
+ strcpy(mdata->album,DEFAULT_METADATA_STRING);
+ strcpy(mdata->media_number,DEFAULT_METADATA_NUMBER);
+ strcpy(mdata->total_media_count,DEFAULT_METADATA_NUMBER);
+ strcpy(mdata->playing_time,DEFAULT_METADATA_NUMBER);
+ mdata->remaining_mdata = NULL;
+ mdata->remaining_mdata_len = 0;
+ mdata->trans_id_event_track = 0;
+ mdata->trans_id_event_playback = 0;
+ mdata->trans_id_event_addressed_player = 0;
+ mdata->trans_id_event_available_player = 0;
+ mdata->trans_id_get_play_status = 0;
+ mdata->reg_track_changed = FALSE;
+ mdata->reg_playback_status = FALSE;
+ mdata->reg_addressed_player = FALSE;
+ mdata->reg_available_player = FALSE;
+ mdata->req_get_play_status = FALSE;
+ mdata->current_play_status = STATUS_STOPPED;
+
+ control->mdata = mdata;
+
return control;
}
@@ -1215,3 +1733,303 @@ gboolean avctp_remove_state_cb(unsigned int id)
return FALSE;
}
+
+static int send_meta_data_continue_response(struct control *control,
+ uint8_t trans_id)
+{
+ struct meta_data *mdata = control->mdata;
+ int header_len = sizeof(struct avrcp_params);
+ int meta_data_len = mdata->remaining_mdata_len;
+ unsigned char buf[meta_data_len + header_len];
+ struct avrcp_params *params = (struct avrcp_params *) buf;
+ struct avrcp_header *avrcp = &params->avrcp;
+ struct avctp_header *avctp = &avrcp->avctp;
+ int sk = g_io_channel_unix_get_fd(control->io);
+
+ memset(buf, 0, sizeof(buf));
+
+ avctp->transaction = trans_id;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avrcp->code = CTYPE_STABLE;
+ avrcp->subunit_type = SUBUNIT_PANEL;
+ avrcp->opcode = OP_VENDORDEPENDENT;
+
+ set_company_id(params->company_id, IEEEID_BTSIG);
+ params->pdu_id = PDU_GET_ELEMENT_ATTRIBUTES;
+
+ memcpy(buf + header_len, mdata->remaining_mdata, mdata->remaining_mdata_len);
+
+ if (meta_data_len + header_len - sizeof(struct avctp_header) > AVRCP_MAX_PKT_SIZE) {
+ int possible_len = AVRCP_MAX_PKT_SIZE - header_len + sizeof(struct avctp_header);
+
+ memcpy(mdata->remaining_mdata, buf + header_len + possible_len,
+ mdata->remaining_mdata_len - possible_len);
+ mdata->remaining_mdata_len -= possible_len;
+
+ params->packet_type = AVCTP_PACKET_CONTINUE;
+ meta_data_len = possible_len;
+ } else {
+ params->packet_type = AVCTP_PACKET_END;
+
+ g_free(mdata->remaining_mdata);
+ mdata->remaining_mdata = NULL;
+ mdata->remaining_mdata_len = 0;
+ }
+
+ params->param_len = htons(meta_data_len);
+
+ return write(sk, buf, meta_data_len + header_len);
+}
+
+static int send_meta_data(struct control *control, uint8_t trans_id, uint8_t att_mask)
+{
+ struct meta_data *mdata = control->mdata;
+ int header_len = sizeof(struct avrcp_caps);
+ /* calculate for the worst case */
+ int meta_data_len = strlen(mdata->title) + strlen(mdata->artist) +
+ strlen(mdata->album) + strlen(mdata->media_number) +
+ strlen(mdata->total_media_count) + strlen(mdata->playing_time) +
+ (sizeof(struct meta_data_field) * METADATA_MAX_FIELDS);
+ unsigned char buf[meta_data_len + header_len];
+ struct avrcp_caps *caps = (struct avrcp_caps *) buf;
+ struct avrcp_params *params = &caps->params;
+ struct avrcp_header *avrcp = &params->avrcp;
+ struct avctp_header *avctp = &avrcp->avctp;
+ struct meta_data_field *mdata_field = (struct meta_data_field *) &buf[header_len];
+ int count = 0, len = 0, total_len = 0, sk = g_io_channel_unix_get_fd(control->io);
+ uint8_t *op;
+
+ memset(buf, 0, sizeof(buf));
+
+ avctp->transaction = trans_id;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avrcp->code = CTYPE_STABLE;
+ avrcp->subunit_type = SUBUNIT_PANEL;
+ avrcp->opcode = OP_VENDORDEPENDENT;
+
+ set_company_id(params->company_id, IEEEID_BTSIG);
+ params->pdu_id = PDU_GET_ELEMENT_ATTRIBUTES;
+ params->packet_type = AVCTP_PACKET_SINGLE;
+ DBG("Att mask is %d", att_mask);
+
+ meta_data_len = 0;
+ op = (uint8_t *) mdata_field;
+
+ if (att_mask & (1 << (METADATA_TITLE - 1))) {
+ mdata_field->att_id = htonl(METADATA_TITLE);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->title);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->title, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ DBG("METADATA_TITLE %d %d", len, meta_data_len);
+ count++;
+ }
+
+ if (att_mask & (1 << (METADATA_ARTIST - 1))) {
+ if (len > 0) {
+ op += sizeof(struct meta_data_field) + len;
+ }
+ mdata_field = (struct meta_data_field *)op;
+ mdata_field->att_id = htonl(METADATA_ARTIST);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->artist);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->artist, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ count++;
+ }
+
+ if (att_mask & (1 << (METADATA_ALBUM - 1))) {
+ if (len > 0) {
+ op += sizeof(struct meta_data_field) + len;
+ }
+ mdata_field = (struct meta_data_field *)op;
+ mdata_field->att_id = htonl(METADATA_ALBUM);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->album);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->album, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ count++;
+ }
+
+ if (att_mask & (1 << (METADATA_MEDIA_NUMBER - 1))) {
+ if (len > 0) {
+ op += sizeof(struct meta_data_field) + len;
+ }
+ mdata_field = (struct meta_data_field *)op;
+ mdata_field->att_id = htonl(METADATA_MEDIA_NUMBER);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->media_number);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->media_number, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ count++;
+ }
+
+ if (att_mask & (1 << (METADATA_TOTAL_MEDIA - 1))) {
+ if (len > 0) {
+ op += sizeof(struct meta_data_field) + len;
+ }
+ mdata_field = (struct meta_data_field *)op;
+ mdata_field->att_id = htonl(METADATA_TOTAL_MEDIA);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->total_media_count);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->total_media_count, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ count++;
+ }
+
+ if (att_mask & (1 << (METADATA_PLAYING_TIME - 1))) {
+ if (len > 0) {
+ op += sizeof(struct meta_data_field) + len;
+ }
+ mdata_field = (struct meta_data_field *)op;
+ mdata_field->att_id = htonl(METADATA_PLAYING_TIME);
+ mdata_field->char_set_id = htons(CHARACTER_SET_UTF8);
+ len = strlen(mdata->playing_time);
+ mdata_field->att_len = htons(len);
+ strncpy(mdata_field->val, mdata->playing_time, len);
+ meta_data_len += len + sizeof(struct meta_data_field);
+ count++;
+ }
+
+ caps->capability_id = count;
+
+ if ((meta_data_len + header_len - sizeof(struct avctp_header)) > AVRCP_MAX_PKT_SIZE) {
+ int possible_len = AVRCP_MAX_PKT_SIZE - header_len + sizeof(struct avctp_header);
+ DBG("meta len is %d header len is %d -> possible %d", meta_data_len, header_len, possible_len);
+
+ params->packet_type = AVCTP_PACKET_START;
+
+ mdata->remaining_mdata = g_new0(gchar, meta_data_len - possible_len);
+ if (!(mdata->remaining_mdata)) {
+ return -ENOMEM;
+ }
+ mdata->remaining_mdata_len = meta_data_len - possible_len;
+ DBG("Remain meta data len is %d", mdata->remaining_mdata_len);
+
+ memcpy(mdata->remaining_mdata, buf + header_len + possible_len,
+ mdata->remaining_mdata_len);
+ meta_data_len = possible_len;
+ }
+ params->param_len = htons(meta_data_len + header_len - sizeof(struct avrcp_params));
+
+ return write(sk, buf, meta_data_len + header_len);
+}
+
+static int send_notification(struct control *control,
+ uint16_t event_id, uint16_t event_data)
+{
+ struct meta_data *mdata = control->mdata;
+ struct avrcp_event event;
+ struct avrcp_caps *caps = &event.caps;
+ struct avrcp_params *params = &caps->params;
+ struct avrcp_header *avrcp = &params->avrcp;
+ struct avctp_header *avctp = &avrcp->avctp;
+ int len = 0, total_len = 0, sk = g_io_channel_unix_get_fd(control->io);
+
+ memset(&event, 0, sizeof(event));
+
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ avrcp->code = CTYPE_CHANGED;
+ avrcp->subunit_type = SUBUNIT_PANEL;
+ avrcp->opcode = OP_VENDORDEPENDENT;
+
+ set_company_id(params->company_id, IEEEID_BTSIG);
+ params->pdu_id = PDU_RGR_NOTIFICATION_ID;
+ params->packet_type = AVCTP_PACKET_SINGLE;
+ caps->capability_id = event_id;
+
+ switch(event_id) {
+ case EVENT_TRACK_CHANGED:
+ if (mdata->reg_track_changed == FALSE)
+ return 0;
+ total_len = sizeof(event.caps) + sizeof(event.event.track_changed);
+ event.event.track_changed.track_high = 0;
+ event.event.track_changed.track_low = htonl(event_data);
+ avctp->transaction = mdata->trans_id_event_track;
+ mdata->reg_track_changed = FALSE;
+ break;
+ case EVENT_PLAYBACK_STATUS_CHANGED:
+ mdata->current_play_status = (uint8_t) event_data;
+ if (mdata->reg_playback_status == FALSE)
+ return 0;
+ total_len = sizeof(event.caps) + sizeof(event.event.playback_status_changed);
+ event.event.playback_status_changed.status = event_data;
+ avctp->transaction = mdata->trans_id_event_playback;
+ mdata->reg_playback_status = FALSE;
+ break;
+ case EVENT_ADDRESSED_PLAYER_CHANGED:
+ if (mdata->reg_addressed_player == FALSE)
+ return 0;
+ total_len = sizeof(event.caps) + sizeof(event.event.player_changed);
+ event.event.player_changed.player_id = htons(event_data);
+ event.event.player_changed.uid_counter = 0;
+ avctp->transaction = mdata->trans_id_event_addressed_player;
+ mdata->reg_addressed_player = FALSE;
+ break;
+ case EVENT_AVAILABLE_PLAYERS_CHANGED:
+ if (mdata->reg_available_player == FALSE)
+ return 0;
+ avctp->transaction = mdata->trans_id_event_available_player;
+ mdata->reg_available_player = FALSE;
+ total_len = sizeof(event.caps);
+ break;
+ }
+ params->param_len = htons(total_len + 1 - sizeof(event.caps));
+ DBG("Send Notification totallen %d", total_len);
+ return write(sk, (unsigned char *) &event, total_len);
+}
+
+static int send_play_status(struct control *control, uint32_t song_len,
+ uint32_t song_position, uint8_t play_status)
+{
+ struct meta_data *mdata = control->mdata;
+ struct avrcp_play_status status;
+ struct avrcp_header *avrcp = &status.params.avrcp;
+ struct avctp_header *avctp = &avrcp->avctp;
+ struct avrcp_params *params = &status.params;
+ int sk = g_io_channel_unix_get_fd(control->io);
+ uint8_t *op = (uint8_t *) params + sizeof(struct avrcp_header);
+
+ DBG("send_play_status called");
+
+ if (mdata->req_get_play_status == FALSE)
+ return 0;
+
+ DBG("send_play_status executing");
+ memset(&status, 0, sizeof(status));
+ mdata->req_get_play_status = FALSE;
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->cr = AVCTP_RESPONSE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+ avctp->transaction = mdata->trans_id_get_play_status;
+
+ avrcp->code = CTYPE_STABLE;
+ avrcp->subunit_type = SUBUNIT_PANEL;
+ avrcp->opcode = OP_VENDORDEPENDENT;
+
+ set_company_id(params->company_id, IEEEID_BTSIG);
+ params->pdu_id = PDU_GET_PLAY_STATUS_ID;
+ params->packet_type = AVCTP_PACKET_SINGLE;
+ params->param_len = htons(sizeof(status) - sizeof(status.params));
+
+ status.song_len = htonl(song_len);
+ status.song_pos = htonl(song_position);
+ status.play_status = play_status;
+
+ return write(sk, (unsigned char *) &status, sizeof(status));
+}
+
View
2 audio/device.c
@@ -60,7 +60,7 @@
#define AUDIO_INTERFACE "org.bluez.Audio"
-#define CONTROL_CONNECT_TIMEOUT 2
+#define CONTROL_CONNECT_TIMEOUT 4
#define AVDTP_CONNECT_TIMEOUT 1
#define HEADSET_CONNECT_TIMEOUT 1
View
15 audio/sink.c
@@ -721,6 +721,21 @@ gboolean sink_is_active(struct audio_device *dev)
return FALSE;
}
+gboolean sink_is_streaming(struct audio_device *dev)
+{
+ struct sink *sink = dev->sink;
+
+ if (sink == NULL) {
+ DBG("Sink is Null");
+ return FALSE;
+ }
+
+ if (sink_get_state(dev) == AVDTP_STATE_STREAMING)
+ return TRUE;
+
+ return FALSE;
+}
+
avdtp_state_t sink_get_state(struct audio_device *dev)
{
struct sink *sink = dev->sink;

0 comments on commit 044ea76

Please sign in to comment.