Return-Path: From: Luiz Augusto von Dentz To: linux-bluetooth@vger.kernel.org Subject: [PATCH BlueZ 5/5 v2] AVRCP: move handling of vendor dependent PDU from control.c to avrcp.c Date: Mon, 12 Sep 2011 19:34:01 +0300 Message-Id: <1315845241-17077-5-git-send-email-luiz.dentz@gmail.com> In-Reply-To: <1315845241-17077-1-git-send-email-luiz.dentz@gmail.com> References: <1315845241-17077-1-git-send-email-luiz.dentz@gmail.com> Sender: linux-bluetooth-owner@vger.kernel.org List-ID: From: Luiz Augusto von Dentz This helps to isolate AVRCP PDU handling and MediaPlayer interface. --- Makefile.am | 1 + audio/avctp.c | 5 + audio/avctp.h | 1 + audio/avrcp.c | 1661 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ audio/avrcp.h | 34 ++ audio/control.c | 1636 +------------------------------------------------------ audio/control.h | 13 +- audio/device.c | 4 + audio/device.h | 2 + audio/manager.c | 10 +- 10 files changed, 1720 insertions(+), 1647 deletions(-) create mode 100644 audio/avrcp.c create mode 100644 audio/avrcp.h diff --git a/Makefile.am b/Makefile.am index ef546d4..6988d03 100644 --- a/Makefile.am +++ b/Makefile.am @@ -144,6 +144,7 @@ builtin_sources += audio/main.c \ audio/headset.h audio/headset.c \ audio/control.h audio/control.c \ audio/avctp.h audio/avctp.c \ + audio/avrcp.h audio/avrcp.c \ audio/device.h audio/device.c \ audio/source.h audio/source.c \ audio/sink.h audio/sink.c \ diff --git a/audio/avctp.c b/audio/avctp.c index 75875bf..cf34107 100644 --- a/audio/avctp.c +++ b/audio/avctp.c @@ -1028,3 +1028,8 @@ void avctp_disconnect(struct avctp *session) avctp_set_state(session, AVCTP_STATE_DISCONNECTED); } + +struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst) +{ + return avctp_get_internal(src, dst); +} diff --git a/audio/avctp.h b/audio/avctp.h index 157ecc6..2dab8fa 100644 --- a/audio/avctp.h +++ b/audio/avctp.h @@ -83,6 +83,7 @@ int avctp_register(const bdaddr_t *src, gboolean master); void avctp_unregister(const bdaddr_t *src); struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst); +struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst); void avctp_disconnect(struct avctp *session); unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb, diff --git a/audio/avrcp.c b/audio/avrcp.c new file mode 100644 index 0000000..c9eae6e --- /dev/null +++ b/audio/avrcp.c @@ -0,0 +1,1661 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * Copyright (C) 2011 Texas Instruments, Inc. + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "log.h" +#include "error.h" +#include "device.h" +#include "manager.h" +#include "avctp.h" +#include "avrcp.h" +#include "sdpd.h" +#include "glib-helper.h" +#include "dbus-common.h" + +/* Company IDs for vendor dependent commands */ +#define IEEEID_BTSIG 0x001958 + +/* Error codes for metadata transfer */ +#define E_INVALID_COMMAND 0x00 +#define E_INVALID_PARAM 0x01 +#define E_PARAM_NOT_FOUND 0x02 +#define E_INTERNAL 0x03 + +/* PDU types for metadata transfer */ +#define AVRCP_GET_CAPABILITIES 0x10 +#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 +#define AVRCP_LIST_PLAYER_VALUES 0x12 +#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13 +#define AVRCP_SET_PLAYER_VALUE 0x14 +#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15 +#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16 +#define AVRCP_DISPLAYABLE_CHARSET 0x17 +#define AVRCP_CT_BATTERY_STATUS 0x18 +#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 +#define AVRCP_GET_PLAY_STATUS 0x30 +#define AVRCP_REGISTER_NOTIFICATION 0x31 + +/* Notification events */ +#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED 0x01 +#define AVRCP_EVENT_TRACK_CHANGED 0x02 + +/* Capabilities for AVRCP_GET_CAPABILITIES pdu */ +#define CAP_COMPANY_ID 0x02 +#define CAP_EVENTS_SUPPORTED 0x03 + +enum player_setting { + PLAYER_SETTING_EQUALIZER = 1, + PLAYER_SETTING_REPEAT = 2, + PLAYER_SETTING_SHUFFLE = 3, + PLAYER_SETTING_SCAN = 4, +}; + +enum equalizer_mode { + EQUALIZER_MODE_OFF = 1, + EQUALIZER_MODE_ON = 2, +}; + +enum repeat_mode { + REPEAT_MODE_OFF = 1, + REPEAT_MODE_SINGLE = 2, + REPEAT_MODE_ALL = 3, + REPEAT_MODE_GROUP = 4, +}; + +enum shuffle_mode { + SHUFFLE_MODE_OFF = 1, + SHUFFLE_MODE_ALL = 2, + SHUFFLE_MODE_GROUP = 3, +}; + +enum scan_mode { + SCAN_MODE_OFF = 1, + SCAN_MODE_ALL = 2, + SCAN_MODE_GROUP = 3, +}; + +enum play_status { + PLAY_STATUS_STOPPED = 0x00, + PLAY_STATUS_PLAYING = 0x01, + PLAY_STATUS_PAUSED = 0x02, + PLAY_STATUS_FWD_SEEK = 0x03, + PLAY_STATUS_REV_SEEK = 0x04, + PLAY_STATUS_ERROR = 0xFF +}; + +enum battery_status { + BATTERY_STATUS_NORMAL = 0, + BATTERY_STATUS_WARNING = 1, + BATTERY_STATUS_CRITICAL = 2, + BATTERY_STATUS_EXTERNAL = 3, + BATTERY_STATUS_FULL_CHARGE = 4, +}; + +enum media_info_id { + MEDIA_INFO_TITLE = 1, + MEDIA_INFO_ARTIST = 2, + MEDIA_INFO_ALBUM = 3, + MEDIA_INFO_TRACK = 4, + MEDIA_INFO_N_TRACKS = 5, + MEDIA_INFO_GENRE = 6, + MEDIA_INFO_CURRENT_POSITION = 7, +}; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t packet_type:2; + uint8_t rsvd:6; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct avrcp_header { + uint8_t company_id[3]; + uint8_t pdu_id; + uint8_t rsvd:6; + uint8_t packet_type:2; + uint16_t params_len; + uint8_t params[0]; +} __attribute__ ((packed)); +#define AVRCP_HEADER_LENGTH 7 + +#else +#error "Unknown byte order" +#endif + +struct avrcp_server { + bdaddr_t src; + uint32_t tg_record_id; + uint32_t ct_record_id; +}; + +struct media_info { + char *title; + char *artist; + char *album; + char *genre; + uint32_t ntracks; + uint32_t track; + uint32_t track_len; + uint32_t elapsed; +}; + +struct media_player { + struct avctp *session; + struct audio_device *dev; + uint8_t settings[PLAYER_SETTING_SCAN + 1]; + enum play_status status; + + struct media_info mi; + GTimer *timer; + unsigned int handler; + uint16_t registered_events; + uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1]; +}; + +static GSList *servers = NULL; + +/* Company IDs supported by this device */ +static uint32_t company_ids[] = { + IEEEID_BTSIG, +}; + +static sdp_record_t *avrcp_ct_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrct; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrct); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP CT", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *avrcp_tg_record(void) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, l2cap, avctp, avrtg; + sdp_profile_desc_t profile[1]; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *psm, *version, *features; + uint16_t lp = AVCTP_PSM; + uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + /* Service Class ID List */ + sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); + svclass_id = sdp_list_append(0, &avrtg); + sdp_set_service_classes(record, svclass_id); + + /* Protocol Descriptor List */ + sdp_uuid16_create(&l2cap, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap); + psm = sdp_data_alloc(SDP_UINT16, &lp); + proto[0] = sdp_list_append(proto[0], psm); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&avctp, AVCTP_UUID); + proto[1] = sdp_list_append(0, &avctp); + version = sdp_data_alloc(SDP_UINT16, &avctp_ver); + proto[1] = sdp_list_append(proto[1], version); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + /* Bluetooth Profile Descriptor List */ + sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); + profile[0].version = avrcp_ver; + pfseq = sdp_list_append(0, &profile[0]); + sdp_set_profile_descs(record, pfseq); + + features = sdp_data_alloc(SDP_UINT16, &feat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + sdp_set_info_attr(record, "AVRCP TG", 0, 0); + + free(psm); + free(version); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static unsigned int attr_get_max_val(uint8_t attr) +{ + switch (attr) { + case PLAYER_SETTING_EQUALIZER: + return EQUALIZER_MODE_ON; + case PLAYER_SETTING_REPEAT: + return REPEAT_MODE_GROUP; + case PLAYER_SETTING_SHUFFLE: + return SHUFFLE_MODE_GROUP; + case PLAYER_SETTING_SCAN: + return SCAN_MODE_GROUP; + } + + return 0; +} + +static const char *attrval_to_str(uint8_t attr, uint8_t value) +{ + switch (attr) { + case PLAYER_SETTING_EQUALIZER: + switch (value) { + case EQUALIZER_MODE_ON: + return "on"; + case EQUALIZER_MODE_OFF: + return "off"; + } + + break; + case PLAYER_SETTING_REPEAT: + switch (value) { + case REPEAT_MODE_OFF: + return "off"; + case REPEAT_MODE_SINGLE: + return "singletrack"; + case REPEAT_MODE_ALL: + return "alltracks"; + case REPEAT_MODE_GROUP: + return "group"; + } + + break; + /* Shuffle and scan have the same values */ + case PLAYER_SETTING_SHUFFLE: + case PLAYER_SETTING_SCAN: + switch (value) { + case SCAN_MODE_OFF: + return "off"; + case SCAN_MODE_ALL: + return "alltracks"; + case SCAN_MODE_GROUP: + return "group"; + } + + break; + } + + return NULL; +} + +static int attrval_to_val(uint8_t attr, const char *value) +{ + int ret; + + switch (attr) { + case PLAYER_SETTING_EQUALIZER: + if (!strcmp(value, "off")) + ret = EQUALIZER_MODE_OFF; + else if (!strcmp(value, "on")) + ret = EQUALIZER_MODE_ON; + else + ret = -EINVAL; + + return ret; + case PLAYER_SETTING_REPEAT: + if (!strcmp(value, "off")) + ret = REPEAT_MODE_OFF; + else if (!strcmp(value, "singletrack")) + ret = REPEAT_MODE_SINGLE; + else if (!strcmp(value, "alltracks")) + ret = REPEAT_MODE_ALL; + else if (!strcmp(value, "group")) + ret = REPEAT_MODE_GROUP; + else + ret = -EINVAL; + + return ret; + case PLAYER_SETTING_SHUFFLE: + if (!strcmp(value, "off")) + ret = SHUFFLE_MODE_OFF; + else if (!strcmp(value, "alltracks")) + ret = SHUFFLE_MODE_ALL; + else if (!strcmp(value, "group")) + ret = SHUFFLE_MODE_GROUP; + else + ret = -EINVAL; + + return ret; + case PLAYER_SETTING_SCAN: + if (!strcmp(value, "off")) + ret = SCAN_MODE_OFF; + else if (!strcmp(value, "alltracks")) + ret = SCAN_MODE_ALL; + else if (!strcmp(value, "group")) + ret = SCAN_MODE_GROUP; + else + ret = -EINVAL; + + return ret; + } + + return -EINVAL; +} + +static const char *attr_to_str(uint8_t attr) +{ + switch (attr) { + case PLAYER_SETTING_EQUALIZER: + return "Equalizer"; + case PLAYER_SETTING_REPEAT: + return "Repeat"; + case PLAYER_SETTING_SHUFFLE: + return "Shuffle"; + case PLAYER_SETTING_SCAN: + return "Scan"; + } + + return NULL; +} + +static int attr_to_val(const char *str) +{ + if (!strcmp(str, "Equalizer")) + return PLAYER_SETTING_EQUALIZER; + else if (!strcmp(str, "Repeat")) + return PLAYER_SETTING_REPEAT; + else if (!strcmp(str, "Shuffle")) + return PLAYER_SETTING_SHUFFLE; + else if (!strcmp(str, "Scan")) + return PLAYER_SETTING_SCAN; + + return -EINVAL; +} + +static int play_status_to_val(const char *status) +{ + if (!strcmp(status, "stopped")) + return PLAY_STATUS_STOPPED; + else if (!strcmp(status, "playing")) + return PLAY_STATUS_PLAYING; + else if (!strcmp(status, "paused")) + return PLAY_STATUS_PAUSED; + else if (!strcmp(status, "forward-seek")) + return PLAY_STATUS_FWD_SEEK; + else if (!strcmp(status, "reverse-seek")) + return PLAY_STATUS_REV_SEEK; + else if (!strcmp(status, "error")) + return PLAY_STATUS_ERROR; + + return -EINVAL; +} + +static const char *battery_status_to_str(enum battery_status status) +{ + switch (status) { + case BATTERY_STATUS_NORMAL: + return "normal"; + case BATTERY_STATUS_WARNING: + return "warning"; + case BATTERY_STATUS_CRITICAL: + return "critical"; + case BATTERY_STATUS_EXTERNAL: + return "external"; + case BATTERY_STATUS_FULL_CHARGE: + return "fullcharge"; + } + + return NULL; +} + +static int avrcp_send_event(struct media_player *mp, uint8_t id, void *data) +{ + uint8_t buf[AVRCP_HEADER_LENGTH + 9]; + struct avrcp_header *pdu = (void *) buf; + uint16_t size; + int err; + + if (mp->session) + return -ENOTCONN; + + if (!(mp->registered_events & (1 << id))) + return 0; + + memset(buf, 0, sizeof(buf)); + + pdu->company_id[0] = IEEEID_BTSIG >> 16; + pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF; + pdu->company_id[2] = IEEEID_BTSIG & 0xFF; + + pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; + pdu->params[0] = id; + + DBG("id=%u", id); + + switch (id) { + case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: + size = 2; + pdu->params[1] = *((uint8_t *)data); + + break; + case AVRCP_EVENT_TRACK_CHANGED: { + size = 9; + + /* + * AVRCP 1.3 supports only one track identifier: PLAYING + * (0x0). When 1.4 version is added, this shall be changed to + * contain the identifier of the track. + */ + memset(&pdu->params[1], 0, 8); + + break; + } + default: + error("Unknown event %u", id); + return -EINVAL; + } + + pdu->params_len = htons(size); + + err = avctp_send_vendordep(mp->session, mp->transaction_events[id], + AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL, + buf, size); + if (err < 0) + return err; + + /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ + mp->registered_events ^= 1 << id; + + return 0; +} + +static void mp_get_playback_status(struct media_player *mp, uint8_t *status, + uint32_t *elapsed, uint32_t *track_len) +{ + if (status) + *status = mp->status; + if (track_len) + *track_len = mp->mi.track_len; + + if (!elapsed) + return; + + *elapsed = mp->mi.elapsed; + + if (mp->status == PLAY_STATUS_PLAYING) { + double timedelta = g_timer_elapsed(mp->timer, NULL); + uint32_t sec, msec; + + sec = (uint32_t) timedelta; + msec = (uint32_t)((timedelta - sec) * 1000); + + *elapsed += sec * 1000 + msec; + } +} + +static void mp_set_playback_status(struct media_player *mp, uint8_t status, + uint32_t elapsed) +{ + DBG("Change playback: %u %u", status, elapsed); + + mp->mi.elapsed = elapsed; + g_timer_start(mp->timer); + + if (status == mp->status) + return; + + mp->status = status; + + avrcp_send_event(mp, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED, &status); +} + +/* + * Copy media_info field to a buffer, intended to be used in a response to + * GetElementAttributes message. + * + * It assumes there's enough space in the buffer and on success it returns the + * size written. + * + * If @param id is not valid, -EINVAL is returned. If there's no such media + * attribute, -ENOENT is returned. + */ +static int mp_get_media_attribute(struct media_player *mp, + uint32_t id, uint8_t *buf) +{ + struct media_info_elem { + uint32_t id; + uint16_t charset; + uint16_t len; + uint8_t val[]; + }; + const struct media_info *mi = &mp->mi; + struct media_info_elem *elem = (void *)buf; + uint16_t len; + char valstr[20]; + + switch (id) { + case MEDIA_INFO_TITLE: + if (mi->title) { + len = strlen(mi->title); + memcpy(elem->val, mi->title, len); + } else { + len = 0; + } + + break; + case MEDIA_INFO_ARTIST: + if (mi->artist == NULL) + return -ENOENT; + + len = strlen(mi->artist); + memcpy(elem->val, mi->artist, len); + break; + case MEDIA_INFO_ALBUM: + if (mi->album == NULL) + return -ENOENT; + + len = strlen(mi->album); + memcpy(elem->val, mi->album, len); + break; + case MEDIA_INFO_GENRE: + if (mi->genre == NULL) + return -ENOENT; + + len = strlen(mi->genre); + memcpy(elem->val, mi->genre, len); + break; + + case MEDIA_INFO_TRACK: + if (!mi->track) + return -ENOENT; + + snprintf(valstr, 20, "%u", mi->track); + len = strlen(valstr); + memcpy(elem->val, valstr, len); + break; + case MEDIA_INFO_N_TRACKS: + if (!mi->ntracks) + return -ENOENT; + + snprintf(valstr, 20, "%u", mi->ntracks); + len = strlen(valstr); + memcpy(elem->val, valstr, len); + break; + case MEDIA_INFO_CURRENT_POSITION: + if (mi->elapsed != 0xFFFFFFFF) { + uint32_t elapsed; + + mp_get_playback_status(mp, NULL, &elapsed, NULL); + + snprintf(valstr, 20, "%u", elapsed); + len = strlen(valstr); + memcpy(elem->val, valstr, len); + } else { + return -ENOENT; + } + + break; + default: + return -EINVAL; + } + + elem->id = htonl(id); + elem->charset = htons(0x6A); /* Always use UTF-8 */ + elem->len = htons(len); + + return sizeof(struct media_info_elem) + len; +} + +static void mp_set_attribute(struct media_player *mp, + uint8_t attr, uint8_t val) +{ + DBG("Change attribute: %u %u", attr, val); + + mp->settings[attr] = val; +} + +static int mp_get_attribute(struct media_player *mp, uint8_t attr) +{ + DBG("Get attribute: %u", attr); + + return mp->settings[attr]; +} + +static void mp_set_media_attributes(struct media_player *mp, + struct media_info *mi) +{ + g_free(mp->mi.title); + mp->mi.title = g_strdup(mi->title); + + g_free(mp->mi.artist); + mp->mi.artist = g_strdup(mi->artist); + + g_free(mp->mi.album); + mp->mi.album = g_strdup(mi->album); + + g_free(mp->mi.genre); + mp->mi.genre = g_strdup(mi->genre); + + mp->mi.ntracks = mi->ntracks; + mp->mi.track = mi->track; + mp->mi.track_len = mi->track_len; + + /* + * elapsed is special. Whenever the track changes, we reset it to 0, + * so client doesn't have to make another call to change_playback + */ + mp->mi.elapsed = 0; + g_timer_start(mp->timer); + + DBG("Track changed:\n\ttitle: %s\n\tartist: %s\n\talbum: %s\n" + "\tgenre: %s\n\tNumber of tracks: %u\n" + "\tTrack number: %u\n\tTrack duration: %u", + mi->title, mi->artist, mi->album, mi->genre, + mi->ntracks, mi->track, mi->track_len); + + avrcp_send_event(mp, AVRCP_EVENT_TRACK_CHANGED, NULL); +} + +static uint8_t avrcp_handle_get_capabilities(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 1) + goto err; + + DBG("id=%u", pdu->params[0]); + + switch (pdu->params[0]) { + case CAP_COMPANY_ID: + for (i = 0; i < G_N_ELEMENTS(company_ids); i++) { + pdu->params[2 + i * 3] = company_ids[i] >> 16; + pdu->params[3 + i * 3] = (company_ids[i] >> 8) & 0xFF; + pdu->params[4 + i * 3] = company_ids[i] & 0xFF; + } + + pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids))); + pdu->params[1] = G_N_ELEMENTS(company_ids); + + return AVC_CTYPE_STABLE; + case CAP_EVENTS_SUPPORTED: + pdu->params_len = htons(4); + pdu->params[1] = 2; + pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED; + pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED; + + return AVC_CTYPE_STABLE; + } + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_list_player_attributes(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 0) { + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + if (!mp) + goto done; + + for (i = 1; i <= PLAYER_SETTING_SCAN; i++) { + if (!mp_get_attribute(mp, i)) { + DBG("Ignoring setting %u: not supported by player", i); + continue; + } + + len++; + pdu->params[len] = i; + } + +done: + pdu->params[0] = len; + pdu->params_len = htons(len + 1); + + return AVC_CTYPE_STABLE; +} + +static uint8_t avrcp_handle_list_player_values(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len != 1 || !mp) + goto err; + + len = attr_get_max_val(pdu->params[0]); + if (!len) { + error("Attribute is invalid: %u", pdu->params[0]); + goto err; + } + + for (i = 1; i <= len; i++) + pdu->params[i] = i; + + pdu->params[0] = len; + pdu->params_len = htons(len + 1); + + return AVC_CTYPE_STABLE; + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_get_element_attributes(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + uint64_t *identifier = (void *) &pdu->params[0]; + uint16_t pos; + uint8_t nattr; + int size; + unsigned int i; + + if (len < 8 || *identifier != 0) + goto err; + + len = 0; + pos = 1; /* Keep track of current position in reponse */ + nattr = pdu->params[8]; + + if (!nattr) { + /* + * Return all available information, at least + * title must be returned. + */ + for (i = 1; i <= MEDIA_INFO_CURRENT_POSITION; i++) { + size = mp_get_media_attribute(mp, i, + &pdu->params[pos]); + + if (size > 0) { + len++; + pos += size; + } + } + } else { + uint32_t *attr_ids; + + attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr); + + for (i = 0; i < nattr; i++) { + uint32_t attr = ntohl(attr_ids[i]); + + size = mp_get_media_attribute(mp, attr, + &pdu->params[pos]); + + if (size > 0) { + len++; + pos += size; + } + } + + g_free(attr_ids); + + if (!len) + goto err; + } + + pdu->params[0] = len; + pdu->params_len = htons(pos); + + return AVC_CTYPE_STABLE; +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_get_current_player_value(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + uint8_t *settings; + unsigned int i; + + if (mp == NULL || len <= 1 || pdu->params[0] != len - 1) + goto err; + + /* + * Save a copy of requested settings because we can override them + * while responding + */ + settings = g_malloc(pdu->params[0]); + memcpy(settings, &pdu->params[1], pdu->params[0]); + len = 0; + + /* + * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs + * and send a response with the existent ones. Only if all IDs are + * non-existent we should send an error. + */ + for (i = 0; i < pdu->params[0]; i++) { + uint8_t val; + + if (settings[i] < PLAYER_SETTING_EQUALIZER || + settings[i] > PLAYER_SETTING_SCAN) { + DBG("Ignoring %u", settings[i]); + continue; + } + + val = mp_get_attribute(mp, settings[i]); + if (!val) { + DBG("Ignoring %u: not supported by player", + settings[i]); + continue; + } + + pdu->params[len] = settings[i]; + pdu->params[len + 1] = val; + len += 2; + } + + g_free(settings); + + if (len) { + pdu->params[0] = len; + pdu->params_len = htons(2 * len + 1); + + return AVC_CTYPE_STABLE; + } + + error("No valid attributes in request"); + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_set_player_value(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + unsigned int i; + + if (len < 3) + goto err; + + len = 0; + + /* + * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs + * and set the existent ones. Sec. 5.2.4 is not clear however how to + * indicate that a certain ID was not accepted. If at least one + * attribute is valid, we respond with no parameters. Otherwise an + * E_INVALID_PARAM is sent. + */ + for (i = 1; i < pdu->params[0]; i += 2) { + uint8_t attr = pdu->params[i]; + uint8_t val = pdu->params[i + 1]; + const char *attrstr; + const char *valstr; + + attrstr = attr_to_str(attr); + if (!attrstr) + continue; + + valstr = attrval_to_str(attr, val); + if (!valstr) + continue; + + len++; + + mp_set_attribute(mp, attr, val); + emit_property_changed(mp->dev->conn, mp->dev->path, + MEDIA_PLAYER_INTERFACE, attrstr, + DBUS_TYPE_STRING, &valstr); + } + + if (len) { + pdu->params_len = 0; + + return AVC_CTYPE_STABLE; + } + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_displayable_charset(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + + if (len < 3) { + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + /* + * We acknowledge the commands, but we always use UTF-8 for + * encoding since CT is obliged to support it. + */ + pdu->params_len = 0; + return AVC_CTYPE_STABLE; +} + +static uint8_t avrcp_handle_ct_battery_status(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + const char *valstr; + + if (len != 1) + goto err; + + valstr = battery_status_to_str(pdu->params[0]); + if (valstr == NULL) + goto err; + + emit_property_changed(mp->dev->conn, mp->dev->path, + MEDIA_PLAYER_INTERFACE, "Battery", + DBUS_TYPE_STRING, &valstr); + pdu->params_len = 0; + + return AVC_CTYPE_STABLE; + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static uint8_t avrcp_handle_get_play_status(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + uint32_t elapsed; + uint32_t track_len; + uint8_t status; + + if (len != 0) { + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; + } + + mp_get_playback_status(mp, &status, &elapsed, &track_len); + track_len = htonl(track_len); + elapsed = htonl(elapsed); + + memcpy(&pdu->params[0], &track_len, 4); + memcpy(&pdu->params[4], &elapsed, 4); + pdu->params[8] = status; + + pdu->params_len = htons(9); + + return AVC_CTYPE_STABLE; +} + +static uint8_t avrcp_handle_register_notification(struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction) +{ + uint16_t len = ntohs(pdu->params_len); + uint8_t status; + + /* + * 1 byte for EventID, 4 bytes for Playback interval but the latest + * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP + * 1.3 spec, section 5.4.2. + */ + if (len != 5) + goto err; + + switch (pdu->params[0]) { + case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: + len = 2; + mp_get_playback_status(mp, &status, NULL, NULL); + pdu->params[1] = status; + + break; + case AVRCP_EVENT_TRACK_CHANGED: + len = 9; + + memset(&pdu->params[1], 0, 8); + + break; + default: + /* All other events are not supported yet */ + goto err; + } + + /* Register event and save the transaction used */ + mp->registered_events |= (1 << pdu->params[0]); + mp->transaction_events[pdu->params[0]] = transaction; + + pdu->params_len = htons(len); + + return AVC_CTYPE_INTERIM; + +err: + pdu->params_len = htons(1); + pdu->params[0] = E_INVALID_PARAM; + return AVC_CTYPE_REJECTED; +} + +static struct pdu_handler { + uint8_t pdu_id; + uint8_t code; + uint8_t (*func) (struct media_player *mp, + struct avrcp_header *pdu, + uint8_t transaction); +} handlers[] = { + { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, + avrcp_handle_get_capabilities }, + { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, + avrcp_handle_list_player_attributes }, + { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, + avrcp_handle_list_player_values }, + { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, + avrcp_handle_get_element_attributes }, + { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, + avrcp_handle_get_current_player_value }, + { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, + avrcp_handle_set_player_value }, + { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, + NULL }, + { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, + NULL }, + { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS, + avrcp_handle_displayable_charset }, + { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS, + avrcp_handle_ct_battery_status }, + { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, + avrcp_handle_get_play_status }, + { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, + avrcp_handle_register_notification }, + { }, +}; + +/* handle vendordep pdu inside an avctp packet */ +static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, + uint8_t *code, uint8_t *subunit, + uint8_t *operands, size_t operand_count, + void *user_data) +{ + struct media_player *mp = user_data; + struct pdu_handler *handler; + struct avrcp_header *pdu = (void *) operands; + uint32_t company_id = (pdu->company_id[0] << 16) | + (pdu->company_id[1] << 8) | + (pdu->company_id[2]); + + if (company_id != IEEEID_BTSIG) { + *code = AVC_CTYPE_NOT_IMPLEMENTED; + return 0; + } + + DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", + pdu->pdu_id, company_id, pdu->params_len); + + pdu->packet_type = 0; + pdu->rsvd = 0; + + if (operand_count + 3 < AVRCP_HEADER_LENGTH) + goto err_metadata; + + for (handler = handlers; handler; handler++) { + if (handler->pdu_id == pdu->pdu_id) + break; + } + + if (!handler || handler->code != *code) { + pdu->params[0] = E_INVALID_COMMAND; + goto err_metadata; + } + + if (!handler->func) { + pdu->params[0] = E_INVALID_PARAM; + goto err_metadata; + } + + *code = handler->func(mp, pdu, transaction); + + return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); + +err_metadata: + pdu->params_len = htons(1); + *code = AVC_CTYPE_REJECTED; + + return AVRCP_HEADER_LENGTH + 1; +} + +static void state_changed(struct audio_device *dev, avctp_state_t old_state, + avctp_state_t new_state, void *user_data) +{ + struct media_player *mp = dev->media_player; + + + if (!mp) + return; + + switch (new_state) { + case AVCTP_STATE_DISCONNECTED: + mp->session = NULL; + + if (mp->handler) { + avctp_unregister_pdu_handler(mp->handler); + mp->handler = 0; + } + + break; + case AVCTP_STATE_CONNECTING: + mp->session = avctp_connect(&dev->src, &dev->dst); + + if (!mp->handler) + mp->handler = avctp_register_pdu_handler( + AVC_OP_VENDORDEP, + handle_vendordep_pdu, + mp); + break; + default: + return; + } +} + +static void media_info_init(struct media_info *mi) +{ + memset(mi, 0, sizeof(*mi)); + + /* + * As per section 5.4.1 of AVRCP 1.3 spec, return 0xFFFFFFFF if TG + * does not support these attributes (i.e. they were never set via + * D-Bus) + */ + mi->track_len = 0xFFFFFFFF; + mi->elapsed = 0xFFFFFFFF; +} + +gboolean avrcp_connect(struct audio_device *dev) +{ + struct avctp *session; + + session = avctp_connect(&dev->src, &dev->dst); + if (session) + return FALSE; + + return TRUE; +} + +void avrcp_disconnect(struct audio_device *dev) +{ + struct avctp *session; + + session = avctp_get(&dev->src, &dev->dst); + if (!session) + return; + + avctp_disconnect(session); +} + +static unsigned int avctp_id = 0; + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) +{ + sdp_record_t *record; + gboolean tmp, master = TRUE; + GError *err = NULL; + struct avrcp_server *server; + + if (config) { + tmp = g_key_file_get_boolean(config, "General", + "Master", &err); + if (err) { + DBG("audio.conf: %s", err->message); + g_error_free(err); + } else + master = tmp; + } + + server = g_new0(struct avrcp_server, 1); + if (!server) + return -ENOMEM; + + record = avrcp_tg_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP target service record"); + g_free(server); + sdp_record_free(record); + return -1; + } + server->tg_record_id = record->handle; + + record = avrcp_ct_record(); + if (!record) { + error("Unable to allocate new service record"); + g_free(server); + return -1; + } + + if (add_record_to_server(src, record) < 0) { + error("Unable to register AVRCP mpler service record"); + sdp_record_free(record); + g_free(server); + return -1; + } + server->ct_record_id = record->handle; + + if (avctp_register(src, master) < 0) { + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + g_free(server); + return -1; + } + + bacpy(&server->src, src); + + servers = g_slist_append(servers, server); + + return 0; +} + +static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src) +{ + for (; list; list = list->next) { + struct avrcp_server *server = list->data; + + if (bacmp(&server->src, src) == 0) + return server; + } + + return NULL; +} + +void avrcp_unregister(const bdaddr_t *src) +{ + struct avrcp_server *server; + + server = find_server(servers, src); + if (!server) + return; + + servers = g_slist_remove(servers, server); + + remove_record_from_server(server->ct_record_id); + remove_record_from_server(server->tg_record_id); + + avctp_unregister(&server->src); + g_free(server); + + if (servers) + return; + + if (avctp_id) + avctp_remove_state_cb(avctp_id); +} + +static DBusMessage *mp_set_property(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct media_player *mp = device->media_player; + DBusMessageIter iter; + DBusMessageIter var; + const char *attrstr, *valstr; + int attr, val; + + if (!dbus_message_iter_init(msg, &iter)) + return btd_error_invalid_args(msg); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&iter, &attrstr); + + attr = attr_to_val(attrstr); + if (attr < 0) + return btd_error_not_supported(msg); + + dbus_message_iter_next(&iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) + return btd_error_invalid_args(msg); + + dbus_message_iter_recurse(&iter, &var); + + /* Only string arguments are supported for now */ + if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) + return btd_error_invalid_args(msg); + + dbus_message_iter_get_basic(&var, &valstr); + + val = attrval_to_val(attr, valstr); + if (val < 0) + return btd_error_not_supported(msg); + + mp_set_attribute(mp, attr, val); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *mp_change_playback(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct media_player *mp = device->media_player; + const char *statusstr; + int status; + uint32_t elapsed; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &statusstr, + DBUS_TYPE_UINT32, &elapsed, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + status = play_status_to_val(statusstr); + if (status < 0) + return btd_error_invalid_args(msg); + + mp_set_playback_status(mp, status, elapsed); + + return dbus_message_new_method_return(msg); +} + +static gboolean media_info_parse(DBusMessageIter *iter, struct media_info *mi) +{ + DBusMessageIter dict; + DBusMessageIter var; + int ctype; + + ctype = dbus_message_iter_get_arg_type(iter); + if (ctype != DBUS_TYPE_ARRAY) + return FALSE; + + media_info_init(mi); + dbus_message_iter_recurse(iter, &dict); + + while ((ctype = dbus_message_iter_get_arg_type(&dict)) != + DBUS_TYPE_INVALID) { + DBusMessageIter entry; + const char *key; + + if (ctype != DBUS_TYPE_DICT_ENTRY) + return FALSE; + + dbus_message_iter_recurse(&dict, &entry); + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) + return FALSE; + + dbus_message_iter_recurse(&entry, &var); + + if (!strcmp(key, "Title")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->title); + } else if (!strcmp(key, "Artist")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->artist); + } else if (!strcmp(key, "Album")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->album); + } else if (!strcmp(key, "Genre")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_STRING) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->genre); + } else if (!strcmp(key, "NumberOfTracks")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_UINT32) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->ntracks); + } else if (!strcmp(key, "TrackNumber")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_UINT32) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->track); + } else if (!strcmp(key, "TrackDuration")) { + if (dbus_message_iter_get_arg_type(&var) != + DBUS_TYPE_UINT32) + return FALSE; + + dbus_message_iter_get_basic(&var, &mi->track_len); + } else { + return FALSE; + } + + dbus_message_iter_next(&dict); + } + + if (mi->title == NULL) + return FALSE; + + return TRUE; +} + +static DBusMessage *mp_change_track(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct audio_device *device = data; + struct media_player *mp = device->media_player; + DBusMessageIter iter; + struct media_info mi; + + + dbus_message_iter_init(msg, &iter); + if (!media_info_parse(&iter, &mi)) + return btd_error_invalid_args(msg); + + mp_set_media_attributes(mp, &mi); + + return dbus_message_new_method_return(msg); +} + +static GDBusMethodTable mp_methods[] = { + { "SetProperty", "sv", "", mp_set_property }, + { "ChangePlayback", "su", "", mp_change_playback }, + { "ChangeTrack", "a{sv}", "", mp_change_track }, + { } +}; + +static GDBusSignalTable mp_signals[] = { + { "PropertyChanged", "sv" }, + { } +}; + +static void mp_path_unregister(void *data) +{ + struct audio_device *dev = data; + struct media_player *mp = dev->media_player; + + DBG("Unregistered interface %s on path %s", + MEDIA_PLAYER_INTERFACE, dev->path); + + if (mp->handler) + avctp_unregister_pdu_handler(mp->handler); + + g_timer_destroy(mp->timer); + g_free(mp); +} + +void media_player_unregister(struct audio_device *dev) +{ + g_dbus_unregister_interface(dev->conn, dev->path, + MEDIA_PLAYER_INTERFACE); +} + +struct media_player *media_player_init(struct audio_device *dev) +{ + struct media_player *mp; + + if (!g_dbus_register_interface(dev->conn, dev->path, + MEDIA_PLAYER_INTERFACE, + mp_methods, mp_signals, NULL, + dev, mp_path_unregister)) { + error("D-Bus failed do register %s on path %s", + MEDIA_PLAYER_INTERFACE, dev->path); + return NULL; + } + + DBG("Registered interface %s on path %s", + MEDIA_PLAYER_INTERFACE, dev->path); + + mp = g_new0(struct media_player, 1); + mp->timer = g_timer_new(); + mp->dev = dev; + media_info_init(&mp->mi); + + if (!avctp_id) + avctp_id = avctp_add_state_cb(state_changed, NULL); + + return mp; +} diff --git a/audio/avrcp.h b/audio/avrcp.h new file mode 100644 index 0000000..1fd912d --- /dev/null +++ b/audio/avrcp.h @@ -0,0 +1,34 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer" + +int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); +void avrcp_unregister(const bdaddr_t *src); + +gboolean avrcp_connect(struct audio_device *dev); +void avrcp_disconnect(struct audio_device *dev); + +struct media_player *media_player_init(struct audio_device *dev); +void media_player_unregister(struct audio_device *dev); diff --git a/audio/control.c b/audio/control.c index dceb004..a75e992 100644 --- a/audio/control.c +++ b/audio/control.c @@ -55,1245 +55,15 @@ #include "glib-helper.h" #include "dbus-common.h" -/* Company IDs for vendor dependent commands */ -#define IEEEID_BTSIG 0x001958 - -/* Error codes for metadata transfer */ -#define E_INVALID_COMMAND 0x00 -#define E_INVALID_PARAM 0x01 -#define E_PARAM_NOT_FOUND 0x02 -#define E_INTERNAL 0x03 - -/* PDU types for metadata transfer */ -#define AVRCP_GET_CAPABILITIES 0x10 -#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11 -#define AVRCP_LIST_PLAYER_VALUES 0x12 -#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13 -#define AVRCP_SET_PLAYER_VALUE 0x14 -#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15 -#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16 -#define AVRCP_DISPLAYABLE_CHARSET 0x17 -#define AVRCP_CT_BATTERY_STATUS 0x18 -#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20 -#define AVRCP_GET_PLAY_STATUS 0x30 -#define AVRCP_REGISTER_NOTIFICATION 0x31 - -/* Notification events */ -#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED 0x01 -#define AVRCP_EVENT_TRACK_CHANGED 0x02 - -/* Capabilities for AVRCP_GET_CAPABILITIES pdu */ -#define CAP_COMPANY_ID 0x02 -#define CAP_EVENTS_SUPPORTED 0x03 - -enum player_setting { - PLAYER_SETTING_EQUALIZER = 1, - PLAYER_SETTING_REPEAT = 2, - PLAYER_SETTING_SHUFFLE = 3, - PLAYER_SETTING_SCAN = 4, -}; - -enum equalizer_mode { - EQUALIZER_MODE_OFF = 1, - EQUALIZER_MODE_ON = 2, -}; - -enum repeat_mode { - REPEAT_MODE_OFF = 1, - REPEAT_MODE_SINGLE = 2, - REPEAT_MODE_ALL = 3, - REPEAT_MODE_GROUP = 4, -}; - -enum shuffle_mode { - SHUFFLE_MODE_OFF = 1, - SHUFFLE_MODE_ALL = 2, - SHUFFLE_MODE_GROUP = 3, -}; - -enum scan_mode { - SCAN_MODE_OFF = 1, - SCAN_MODE_ALL = 2, - SCAN_MODE_GROUP = 3, -}; - -enum play_status { - PLAY_STATUS_STOPPED = 0x00, - PLAY_STATUS_PLAYING = 0x01, - PLAY_STATUS_PAUSED = 0x02, - PLAY_STATUS_FWD_SEEK = 0x03, - PLAY_STATUS_REV_SEEK = 0x04, - PLAY_STATUS_ERROR = 0xFF -}; - -enum battery_status { - BATTERY_STATUS_NORMAL = 0, - BATTERY_STATUS_WARNING = 1, - BATTERY_STATUS_CRITICAL = 2, - BATTERY_STATUS_EXTERNAL = 3, - BATTERY_STATUS_FULL_CHARGE = 4, -}; - -enum media_info_id { - MEDIA_INFO_TITLE = 1, - MEDIA_INFO_ARTIST = 2, - MEDIA_INFO_ALBUM = 3, - MEDIA_INFO_TRACK = 4, - MEDIA_INFO_N_TRACKS = 5, - MEDIA_INFO_GENRE = 6, - MEDIA_INFO_CURRENT_POSITION = 7, -}; - -static DBusConnection *connection = NULL; - -static GSList *servers = NULL; static unsigned int avctp_id = 0; -#if __BYTE_ORDER == __LITTLE_ENDIAN - -struct avrcp_header { - uint8_t company_id[3]; - uint8_t pdu_id; - uint8_t packet_type:2; - uint8_t rsvd:6; - uint16_t params_len; - uint8_t params[0]; -} __attribute__ ((packed)); -#define AVRCP_HEADER_LENGTH 7 - -#elif __BYTE_ORDER == __BIG_ENDIAN - -struct avrcp_header { - uint8_t company_id[3]; - uint8_t pdu_id; - uint8_t rsvd:6; - uint8_t packet_type:2; - uint16_t params_len; - uint8_t params[0]; -} __attribute__ ((packed)); -#define AVRCP_HEADER_LENGTH 7 - -#else -#error "Unknown byte order" -#endif - -struct avrcp_server { - bdaddr_t src; - uint32_t tg_record_id; - uint32_t ct_record_id; -}; - -struct media_info { - char *title; - char *artist; - char *album; - char *genre; - uint32_t ntracks; - uint32_t track; - uint32_t track_len; - uint32_t elapsed; -}; - -struct media_player { - uint8_t settings[PLAYER_SETTING_SCAN + 1]; - enum play_status status; - - struct media_info mi; - GTimer *timer; - unsigned int handler; -}; - struct control { struct audio_device *dev; - struct media_player *mp; struct avctp *session; gboolean target; - - uint16_t registered_events; - uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1]; }; -/* Company IDs supported by this device */ -static uint32_t company_ids[] = { - IEEEID_BTSIG, -}; - -static sdp_record_t *avrcp_ct_record(void) -{ - sdp_list_t *svclass_id, *pfseq, *apseq, *root; - uuid_t root_uuid, l2cap, avctp, avrct; - sdp_profile_desc_t profile[1]; - sdp_list_t *aproto, *proto[2]; - sdp_record_t *record; - sdp_data_t *psm, *version, *features; - uint16_t lp = AVCTP_PSM; - uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f; - - record = sdp_record_alloc(); - if (!record) - return NULL; - - sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); - root = sdp_list_append(0, &root_uuid); - sdp_set_browse_groups(record, root); - - /* Service Class ID List */ - sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); - svclass_id = sdp_list_append(0, &avrct); - sdp_set_service_classes(record, svclass_id); - - /* Protocol Descriptor List */ - sdp_uuid16_create(&l2cap, L2CAP_UUID); - proto[0] = sdp_list_append(0, &l2cap); - psm = sdp_data_alloc(SDP_UINT16, &lp); - proto[0] = sdp_list_append(proto[0], psm); - apseq = sdp_list_append(0, proto[0]); - - sdp_uuid16_create(&avctp, AVCTP_UUID); - proto[1] = sdp_list_append(0, &avctp); - version = sdp_data_alloc(SDP_UINT16, &avctp_ver); - proto[1] = sdp_list_append(proto[1], version); - apseq = sdp_list_append(apseq, proto[1]); - - aproto = sdp_list_append(0, apseq); - sdp_set_access_protos(record, aproto); - - /* Bluetooth Profile Descriptor List */ - sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); - profile[0].version = avrcp_ver; - pfseq = sdp_list_append(0, &profile[0]); - sdp_set_profile_descs(record, pfseq); - - features = sdp_data_alloc(SDP_UINT16, &feat); - sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); - - sdp_set_info_attr(record, "AVRCP CT", 0, 0); - - free(psm); - free(version); - sdp_list_free(proto[0], 0); - sdp_list_free(proto[1], 0); - sdp_list_free(apseq, 0); - sdp_list_free(pfseq, 0); - sdp_list_free(aproto, 0); - sdp_list_free(root, 0); - sdp_list_free(svclass_id, 0); - - return record; -} - -static sdp_record_t *avrcp_tg_record(void) -{ - sdp_list_t *svclass_id, *pfseq, *apseq, *root; - uuid_t root_uuid, l2cap, avctp, avrtg; - sdp_profile_desc_t profile[1]; - sdp_list_t *aproto, *proto[2]; - sdp_record_t *record; - sdp_data_t *psm, *version, *features; - uint16_t lp = AVCTP_PSM; - uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f; - - record = sdp_record_alloc(); - if (!record) - return NULL; - - sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); - root = sdp_list_append(0, &root_uuid); - sdp_set_browse_groups(record, root); - - /* Service Class ID List */ - sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); - svclass_id = sdp_list_append(0, &avrtg); - sdp_set_service_classes(record, svclass_id); - - /* Protocol Descriptor List */ - sdp_uuid16_create(&l2cap, L2CAP_UUID); - proto[0] = sdp_list_append(0, &l2cap); - psm = sdp_data_alloc(SDP_UINT16, &lp); - proto[0] = sdp_list_append(proto[0], psm); - apseq = sdp_list_append(0, proto[0]); - - sdp_uuid16_create(&avctp, AVCTP_UUID); - proto[1] = sdp_list_append(0, &avctp); - version = sdp_data_alloc(SDP_UINT16, &avctp_ver); - proto[1] = sdp_list_append(proto[1], version); - apseq = sdp_list_append(apseq, proto[1]); - - aproto = sdp_list_append(0, apseq); - sdp_set_access_protos(record, aproto); - - /* Bluetooth Profile Descriptor List */ - sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); - profile[0].version = avrcp_ver; - pfseq = sdp_list_append(0, &profile[0]); - sdp_set_profile_descs(record, pfseq); - - features = sdp_data_alloc(SDP_UINT16, &feat); - sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); - - sdp_set_info_attr(record, "AVRCP TG", 0, 0); - - free(psm); - free(version); - sdp_list_free(proto[0], 0); - sdp_list_free(proto[1], 0); - sdp_list_free(apseq, 0); - sdp_list_free(aproto, 0); - sdp_list_free(pfseq, 0); - sdp_list_free(root, 0); - sdp_list_free(svclass_id, 0); - - return record; -} - -static unsigned int attr_get_max_val(uint8_t attr) -{ - switch (attr) { - case PLAYER_SETTING_EQUALIZER: - return EQUALIZER_MODE_ON; - case PLAYER_SETTING_REPEAT: - return REPEAT_MODE_GROUP; - case PLAYER_SETTING_SHUFFLE: - return SHUFFLE_MODE_GROUP; - case PLAYER_SETTING_SCAN: - return SCAN_MODE_GROUP; - } - - return 0; -} - -static const char *attrval_to_str(uint8_t attr, uint8_t value) -{ - switch (attr) { - case PLAYER_SETTING_EQUALIZER: - switch (value) { - case EQUALIZER_MODE_ON: - return "on"; - case EQUALIZER_MODE_OFF: - return "off"; - } - - break; - case PLAYER_SETTING_REPEAT: - switch (value) { - case REPEAT_MODE_OFF: - return "off"; - case REPEAT_MODE_SINGLE: - return "singletrack"; - case REPEAT_MODE_ALL: - return "alltracks"; - case REPEAT_MODE_GROUP: - return "group"; - } - - break; - /* Shuffle and scan have the same values */ - case PLAYER_SETTING_SHUFFLE: - case PLAYER_SETTING_SCAN: - switch (value) { - case SCAN_MODE_OFF: - return "off"; - case SCAN_MODE_ALL: - return "alltracks"; - case SCAN_MODE_GROUP: - return "group"; - } - - break; - } - - return NULL; -} - -static int attrval_to_val(uint8_t attr, const char *value) -{ - int ret; - - switch (attr) { - case PLAYER_SETTING_EQUALIZER: - if (!strcmp(value, "off")) - ret = EQUALIZER_MODE_OFF; - else if (!strcmp(value, "on")) - ret = EQUALIZER_MODE_ON; - else - ret = -EINVAL; - - return ret; - case PLAYER_SETTING_REPEAT: - if (!strcmp(value, "off")) - ret = REPEAT_MODE_OFF; - else if (!strcmp(value, "singletrack")) - ret = REPEAT_MODE_SINGLE; - else if (!strcmp(value, "alltracks")) - ret = REPEAT_MODE_ALL; - else if (!strcmp(value, "group")) - ret = REPEAT_MODE_GROUP; - else - ret = -EINVAL; - - return ret; - case PLAYER_SETTING_SHUFFLE: - if (!strcmp(value, "off")) - ret = SHUFFLE_MODE_OFF; - else if (!strcmp(value, "alltracks")) - ret = SHUFFLE_MODE_ALL; - else if (!strcmp(value, "group")) - ret = SHUFFLE_MODE_GROUP; - else - ret = -EINVAL; - - return ret; - case PLAYER_SETTING_SCAN: - if (!strcmp(value, "off")) - ret = SCAN_MODE_OFF; - else if (!strcmp(value, "alltracks")) - ret = SCAN_MODE_ALL; - else if (!strcmp(value, "group")) - ret = SCAN_MODE_GROUP; - else - ret = -EINVAL; - - return ret; - } - - return -EINVAL; -} - -static const char *attr_to_str(uint8_t attr) -{ - switch (attr) { - case PLAYER_SETTING_EQUALIZER: - return "Equalizer"; - case PLAYER_SETTING_REPEAT: - return "Repeat"; - case PLAYER_SETTING_SHUFFLE: - return "Shuffle"; - case PLAYER_SETTING_SCAN: - return "Scan"; - } - - return NULL; -} - -static int attr_to_val(const char *str) -{ - if (!strcmp(str, "Equalizer")) - return PLAYER_SETTING_EQUALIZER; - else if (!strcmp(str, "Repeat")) - return PLAYER_SETTING_REPEAT; - else if (!strcmp(str, "Shuffle")) - return PLAYER_SETTING_SHUFFLE; - else if (!strcmp(str, "Scan")) - return PLAYER_SETTING_SCAN; - - return -EINVAL; -} - -static int play_status_to_val(const char *status) -{ - if (!strcmp(status, "stopped")) - return PLAY_STATUS_STOPPED; - else if (!strcmp(status, "playing")) - return PLAY_STATUS_PLAYING; - else if (!strcmp(status, "paused")) - return PLAY_STATUS_PAUSED; - else if (!strcmp(status, "forward-seek")) - return PLAY_STATUS_FWD_SEEK; - else if (!strcmp(status, "reverse-seek")) - return PLAY_STATUS_REV_SEEK; - else if (!strcmp(status, "error")) - return PLAY_STATUS_ERROR; - - return -EINVAL; -} - -static const char *battery_status_to_str(enum battery_status status) -{ - switch (status) { - case BATTERY_STATUS_NORMAL: - return "normal"; - case BATTERY_STATUS_WARNING: - return "warning"; - case BATTERY_STATUS_CRITICAL: - return "critical"; - case BATTERY_STATUS_EXTERNAL: - return "external"; - case BATTERY_STATUS_FULL_CHARGE: - return "fullcharge"; - } - - return NULL; -} - -static int avrcp_send_event(struct control *control, uint8_t id, void *data) -{ - uint8_t buf[AVRCP_HEADER_LENGTH + 9]; - struct avrcp_header *pdu = (void *) buf; - uint16_t size; - int err; - - if (control->session) - return -ENOTCONN; - - if (!(control->registered_events & (1 << id))) - return 0; - - memset(buf, 0, sizeof(buf)); - - pdu->company_id[0] = IEEEID_BTSIG >> 16; - pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF; - pdu->company_id[2] = IEEEID_BTSIG & 0xFF; - - pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; - pdu->params[0] = id; - - DBG("id=%u", id); - - switch (id) { - case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: - size = 2; - pdu->params[1] = *((uint8_t *)data); - - break; - case AVRCP_EVENT_TRACK_CHANGED: { - size = 9; - - /* - * AVRCP 1.3 supports only one track identifier: PLAYING - * (0x0). When 1.4 version is added, this shall be changed to - * contain the identifier of the track. - */ - memset(&pdu->params[1], 0, 8); - - break; - } - default: - error("Unknown event %u", id); - return -EINVAL; - } - - pdu->params_len = htons(size); - - err = avctp_send_vendordep(control->session, control->transaction_events[id], - AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL, - buf, size); - if (err < 0) - return err; - - /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ - control->registered_events ^= 1 << id; - - return 0; -} - -static void mp_get_playback_status(struct media_player *mp, uint8_t *status, - uint32_t *elapsed, uint32_t *track_len) -{ - if (status) - *status = mp->status; - if (track_len) - *track_len = mp->mi.track_len; - - if (!elapsed) - return; - - *elapsed = mp->mi.elapsed; - - if (mp->status == PLAY_STATUS_PLAYING) { - double timedelta = g_timer_elapsed(mp->timer, NULL); - uint32_t sec, msec; - - sec = (uint32_t) timedelta; - msec = (uint32_t)((timedelta - sec) * 1000); - - *elapsed += sec * 1000 + msec; - } -} - -static void mp_set_playback_status(struct control *control, uint8_t status, - uint32_t elapsed) -{ - struct media_player *mp = control->mp; - - DBG("Change playback: %u %u", status, elapsed); - - mp->mi.elapsed = elapsed; - g_timer_start(mp->timer); - - if (status == mp->status) - return; - - mp->status = status; - - avrcp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED, - &status); -} - -/* - * Copy media_info field to a buffer, intended to be used in a response to - * GetElementAttributes message. - * - * It assumes there's enough space in the buffer and on success it returns the - * size written. - * - * If @param id is not valid, -EINVAL is returned. If there's no such media - * attribute, -ENOENT is returned. - */ -static int mp_get_media_attribute(struct media_player *mp, - uint32_t id, uint8_t *buf) -{ - struct media_info_elem { - uint32_t id; - uint16_t charset; - uint16_t len; - uint8_t val[]; - }; - const struct media_info *mi = &mp->mi; - struct media_info_elem *elem = (void *)buf; - uint16_t len; - char valstr[20]; - - switch (id) { - case MEDIA_INFO_TITLE: - if (mi->title) { - len = strlen(mi->title); - memcpy(elem->val, mi->title, len); - } else { - len = 0; - } - - break; - case MEDIA_INFO_ARTIST: - if (mi->artist == NULL) - return -ENOENT; - - len = strlen(mi->artist); - memcpy(elem->val, mi->artist, len); - break; - case MEDIA_INFO_ALBUM: - if (mi->album == NULL) - return -ENOENT; - - len = strlen(mi->album); - memcpy(elem->val, mi->album, len); - break; - case MEDIA_INFO_GENRE: - if (mi->genre == NULL) - return -ENOENT; - - len = strlen(mi->genre); - memcpy(elem->val, mi->genre, len); - break; - - case MEDIA_INFO_TRACK: - if (!mi->track) - return -ENOENT; - - snprintf(valstr, 20, "%u", mi->track); - len = strlen(valstr); - memcpy(elem->val, valstr, len); - break; - case MEDIA_INFO_N_TRACKS: - if (!mi->ntracks) - return -ENOENT; - - snprintf(valstr, 20, "%u", mi->ntracks); - len = strlen(valstr); - memcpy(elem->val, valstr, len); - break; - case MEDIA_INFO_CURRENT_POSITION: - if (mi->elapsed != 0xFFFFFFFF) { - uint32_t elapsed; - - mp_get_playback_status(mp, NULL, &elapsed, NULL); - - snprintf(valstr, 20, "%u", elapsed); - len = strlen(valstr); - memcpy(elem->val, valstr, len); - } else { - return -ENOENT; - } - - break; - default: - return -EINVAL; - } - - elem->id = htonl(id); - elem->charset = htons(0x6A); /* Always use UTF-8 */ - elem->len = htons(len); - - return sizeof(struct media_info_elem) + len; -} - -static void mp_set_attribute(struct media_player *mp, - uint8_t attr, uint8_t val) -{ - DBG("Change attribute: %u %u", attr, val); - - mp->settings[attr] = val; -} - -static int mp_get_attribute(struct media_player *mp, uint8_t attr) -{ - DBG("Get attribute: %u", attr); - - return mp->settings[attr]; -} - -static void mp_set_media_attributes(struct control *control, - struct media_info *mi) -{ - struct media_player *mp = control->mp; - - g_free(mp->mi.title); - mp->mi.title = g_strdup(mi->title); - - g_free(mp->mi.artist); - mp->mi.artist = g_strdup(mi->artist); - - g_free(mp->mi.album); - mp->mi.album = g_strdup(mi->album); - - g_free(mp->mi.genre); - mp->mi.genre = g_strdup(mi->genre); - - mp->mi.ntracks = mi->ntracks; - mp->mi.track = mi->track; - mp->mi.track_len = mi->track_len; - - /* - * elapsed is special. Whenever the track changes, we reset it to 0, - * so client doesn't have to make another call to change_playback - */ - mp->mi.elapsed = 0; - g_timer_start(mp->timer); - - DBG("Track changed:\n\ttitle: %s\n\tartist: %s\n\talbum: %s\n" - "\tgenre: %s\n\tNumber of tracks: %u\n" - "\tTrack number: %u\n\tTrack duration: %u", - mi->title, mi->artist, mi->album, mi->genre, - mi->ntracks, mi->track, mi->track_len); - - avrcp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL); -} - -static uint8_t avrcp_handle_get_capabilities(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - - if (len != 1) - goto err; - - DBG("id=%u", pdu->params[0]); - - switch (pdu->params[0]) { - case CAP_COMPANY_ID: - for (i = 0; i < G_N_ELEMENTS(company_ids); i++) { - pdu->params[2 + i * 3] = company_ids[i] >> 16; - pdu->params[3 + i * 3] = (company_ids[i] >> 8) & 0xFF; - pdu->params[4 + i * 3] = company_ids[i] & 0xFF; - } - - pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids))); - pdu->params[1] = G_N_ELEMENTS(company_ids); - - return AVC_CTYPE_STABLE; - case CAP_EVENTS_SUPPORTED: - pdu->params_len = htons(4); - pdu->params[1] = 2; - pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED; - pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED; - - return AVC_CTYPE_STABLE; - } - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_list_player_attributes(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - struct media_player *mp = control->mp; - unsigned int i; - - if (len != 0) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - if (!mp) - goto done; - - for (i = 1; i <= PLAYER_SETTING_SCAN; i++) { - if (!mp_get_attribute(mp, i)) { - DBG("Ignoring setting %u: not supported by player", i); - continue; - } - - len++; - pdu->params[len] = i; - } - -done: - pdu->params[0] = len; - pdu->params_len = htons(len + 1); - - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_list_player_values(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - struct media_player *mp = control->mp; - unsigned int i; - - if (len != 1 || !mp) - goto err; - - len = attr_get_max_val(pdu->params[0]); - if (!len) { - error("Attribute is invalid: %u", pdu->params[0]); - goto err; - } - - for (i = 1; i <= len; i++) - pdu->params[i] = i; - - pdu->params[0] = len; - pdu->params_len = htons(len + 1); - - return AVC_CTYPE_STABLE; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_element_attributes(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint64_t *identifier = (void *) &pdu->params[0]; - uint16_t pos; - uint8_t nattr; - int size; - unsigned int i; - - if (len < 8 || *identifier != 0 || !control->mp) - goto err; - - len = 0; - pos = 1; /* Keep track of current position in reponse */ - nattr = pdu->params[8]; - - if (!control->mp) - goto done; - - if (!nattr) { - /* - * Return all available information, at least - * title must be returned. - */ - for (i = 1; i <= MEDIA_INFO_CURRENT_POSITION; i++) { - size = mp_get_media_attribute(control->mp, i, - &pdu->params[pos]); - - if (size > 0) { - len++; - pos += size; - } - } - } else { - uint32_t *attr_ids; - - attr_ids = g_memdup(&pdu->params[9], sizeof(uint32_t) * nattr); - - for (i = 0; i < nattr; i++) { - uint32_t attr = ntohl(attr_ids[i]); - - size = mp_get_media_attribute(control->mp, attr, - &pdu->params[pos]); - - if (size > 0) { - len++; - pos += size; - } - } - - g_free(attr_ids); - - if (!len) - goto err; - } - -done: - pdu->params[0] = len; - pdu->params_len = htons(pos); - - return AVC_CTYPE_STABLE; -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_current_player_value(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - struct media_player *mp = control->mp; - uint8_t *settings; - unsigned int i; - - if (mp == NULL || len <= 1 || pdu->params[0] != len - 1) - goto err; - - /* - * Save a copy of requested settings because we can override them - * while responding - */ - settings = g_malloc(pdu->params[0]); - memcpy(settings, &pdu->params[1], pdu->params[0]); - len = 0; - - /* - * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs - * and send a response with the existent ones. Only if all IDs are - * non-existent we should send an error. - */ - for (i = 0; i < pdu->params[0]; i++) { - uint8_t val; - - if (settings[i] < PLAYER_SETTING_EQUALIZER || - settings[i] > PLAYER_SETTING_SCAN) { - DBG("Ignoring %u", settings[i]); - continue; - } - - val = mp_get_attribute(mp, settings[i]); - if (!val) { - DBG("Ignoring %u: not supported by player", - settings[i]); - continue; - } - - pdu->params[len] = settings[i]; - pdu->params[len + 1] = val; - len += 2; - } - - g_free(settings); - - if (len) { - pdu->params[0] = len; - pdu->params_len = htons(2 * len + 1); - - return AVC_CTYPE_STABLE; - } - - error("No valid attributes in request"); - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_set_player_value(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - - if (len < 3 || !control->mp) - goto err; - - len = 0; - - /* - * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs - * and set the existent ones. Sec. 5.2.4 is not clear however how to - * indicate that a certain ID was not accepted. If at least one - * attribute is valid, we respond with no parameters. Otherwise an - * E_INVALID_PARAM is sent. - */ - for (i = 1; i < pdu->params[0]; i += 2) { - uint8_t attr = pdu->params[i]; - uint8_t val = pdu->params[i + 1]; - const char *attrstr; - const char *valstr; - - attrstr = attr_to_str(attr); - if (!attrstr) - continue; - - valstr = attrval_to_str(attr, val); - if (!valstr) - continue; - - len++; - - mp_set_attribute(control->mp, attr, val); - emit_property_changed(control->dev->conn, control->dev->path, - MEDIA_PLAYER_INTERFACE, attrstr, - DBUS_TYPE_STRING, &valstr); - } - - if (len) { - pdu->params_len = 0; - - return AVC_CTYPE_STABLE; - } - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_displayable_charset(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - - if (len < 3) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - /* - * We acknowledge the commands, but we always use UTF-8 for - * encoding since CT is obliged to support it. - */ - pdu->params_len = 0; - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_ct_battery_status(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - const char *valstr; - - if (len != 1) - goto err; - - valstr = battery_status_to_str(pdu->params[0]); - if (valstr == NULL) - goto err; - - emit_property_changed(control->dev->conn, control->dev->path, - MEDIA_PLAYER_INTERFACE, "Battery", - DBUS_TYPE_STRING, &valstr); - pdu->params_len = 0; - - return AVC_CTYPE_STABLE; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_play_status(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint32_t elapsed; - uint32_t track_len; - uint8_t status; - - if (len != 0) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - if (control->mp) { - mp_get_playback_status(control->mp, &status, - &elapsed, &track_len); - track_len = htonl(track_len); - elapsed = htonl(elapsed); - } else { - track_len = 0xFFFFFFFF; - elapsed = 0xFFFFFFFF; - status = PLAY_STATUS_ERROR; - } - - memcpy(&pdu->params[0], &track_len, 4); - memcpy(&pdu->params[4], &elapsed, 4); - pdu->params[8] = status; - - pdu->params_len = htons(9); - - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_register_notification(struct control *control, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint8_t status; - - /* - * 1 byte for EventID, 4 bytes for Playback interval but the latest - * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP - * 1.3 spec, section 5.4.2. - */ - if (len != 5) - goto err; - - switch (pdu->params[0]) { - case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: - len = 2; - if (control->mp) { - mp_get_playback_status(control->mp, &status, - NULL, NULL); - pdu->params[1] = status; - } else { - pdu->params[1] = PLAY_STATUS_ERROR; - } - - break; - case AVRCP_EVENT_TRACK_CHANGED: - len = 9; - - if (!control->mp) - memset(&pdu->params[1], 0xFF, 8); - else - memset(&pdu->params[1], 0, 8); - - break; - default: - /* All other events are not supported yet */ - goto err; - } - - /* Register event and save the transaction used */ - control->registered_events |= (1 << pdu->params[0]); - control->transaction_events[pdu->params[0]] = transaction; - - pdu->params_len = htons(len); - - return AVC_CTYPE_INTERIM; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static struct pdu_handler { - uint8_t pdu_id; - uint8_t code; - uint8_t (*func) (struct control *control, - struct avrcp_header *pdu, - uint8_t transaction); -} handlers[] = { - { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, - avrcp_handle_get_capabilities }, - { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, - avrcp_handle_list_player_attributes }, - { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, - avrcp_handle_list_player_values }, - { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, - avrcp_handle_get_element_attributes }, - { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, - avrcp_handle_get_current_player_value }, - { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, - avrcp_handle_set_player_value }, - { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, - NULL }, - { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, - NULL }, - { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS, - avrcp_handle_displayable_charset }, - { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS, - avrcp_handle_ct_battery_status }, - { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, - avrcp_handle_get_play_status }, - { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, - avrcp_handle_register_notification }, - { }, -}; - -/* handle vendordep pdu inside an avctp packet */ -static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, - uint8_t *code, uint8_t *subunit, - uint8_t *operands, size_t operand_count, - void *user_data) -{ - struct control *control = user_data; - struct pdu_handler *handler; - struct avrcp_header *pdu = (void *) operands; - uint32_t company_id = (pdu->company_id[0] << 16) | - (pdu->company_id[1] << 8) | - (pdu->company_id[2]); - - if (company_id != IEEEID_BTSIG) { - *code = AVC_CTYPE_NOT_IMPLEMENTED; - return 0; - } - - DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", - pdu->pdu_id, company_id, pdu->params_len); - - pdu->packet_type = 0; - pdu->rsvd = 0; - - if (operand_count + 3 < AVRCP_HEADER_LENGTH) - goto err_metadata; - - for (handler = handlers; handler; handler++) { - if (handler->pdu_id == pdu->pdu_id) - break; - } - - if (!handler || handler->code != *code) { - pdu->params[0] = E_INVALID_COMMAND; - goto err_metadata; - } - - if (!handler->func) { - pdu->params[0] = E_INVALID_PARAM; - goto err_metadata; - } - - *code = handler->func(control, pdu, transaction); - - return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); - -err_metadata: - pdu->params_len = htons(1); - *code = AVC_CTYPE_REJECTED; - - return AVRCP_HEADER_LENGTH + 1; -} - static void state_changed(struct audio_device *dev, avctp_state_t old_state, avctp_state_t new_state, void *user_data) { @@ -1304,11 +74,6 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state, case AVCTP_STATE_DISCONNECTED: control->session = NULL; - if (control->mp && control->mp->handler) { - avctp_unregister_pdu_handler(control->mp->handler); - control->mp->handler = 0; - } - if (old_state != AVCTP_STATE_CONNECTED) break; @@ -1325,14 +90,8 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state, if (control->session) break; - control->session = avctp_connect(&dev->src, &dev->dst); - if (!control->mp) - break; + control->session = avctp_get(&dev->src, &dev->dst); - control->mp->handler = avctp_register_pdu_handler( - AVC_OP_VENDORDEP, - handle_vendordep_pdu, - control); break; case AVCTP_STATE_CONNECTED: value = TRUE; @@ -1348,149 +107,6 @@ static void state_changed(struct audio_device *dev, avctp_state_t old_state, } } -static void media_info_init(struct media_info *mi) -{ - memset(mi, 0, sizeof(*mi)); - - /* - * As per section 5.4.1 of AVRCP 1.3 spec, return 0xFFFFFFFF if TG - * does not support these attributes (i.e. they were never set via - * D-Bus) - */ - mi->track_len = 0xFFFFFFFF; - mi->elapsed = 0xFFFFFFFF; -} - -gboolean avrcp_connect(struct audio_device *dev) -{ - struct control *control = dev->control; - - if (control->session) - return TRUE; - - control->session = avctp_connect(&dev->src, &dev->dst); - if (!control->session) - return FALSE; - - return TRUE; -} - -void avrcp_disconnect(struct audio_device *dev) -{ - struct control *control = dev->control; - - if (!(control && control->session)) - return; - - avctp_disconnect(control->session); -} - -int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) -{ - sdp_record_t *record; - gboolean tmp, master = TRUE; - GError *err = NULL; - struct avrcp_server *server; - - if (config) { - tmp = g_key_file_get_boolean(config, "General", - "Master", &err); - if (err) { - DBG("audio.conf: %s", err->message); - g_error_free(err); - } else - master = tmp; - } - - server = g_new0(struct avrcp_server, 1); - if (!server) - return -ENOMEM; - - if (!connection) - connection = dbus_connection_ref(conn); - - record = avrcp_tg_record(); - if (!record) { - error("Unable to allocate new service record"); - g_free(server); - return -1; - } - - if (add_record_to_server(src, record) < 0) { - error("Unable to register AVRCP target service record"); - g_free(server); - sdp_record_free(record); - return -1; - } - server->tg_record_id = record->handle; - - record = avrcp_ct_record(); - if (!record) { - error("Unable to allocate new service record"); - g_free(server); - return -1; - } - - if (add_record_to_server(src, record) < 0) { - error("Unable to register AVRCP controller service record"); - sdp_record_free(record); - g_free(server); - return -1; - } - server->ct_record_id = record->handle; - - if (avctp_register(src, master) < 0) { - remove_record_from_server(server->ct_record_id); - remove_record_from_server(server->tg_record_id); - g_free(server); - return -1; - } - - bacpy(&server->src, src); - - servers = g_slist_append(servers, server); - - return 0; -} - -static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src) -{ - for (; list; list = list->next) { - struct avrcp_server *server = list->data; - - if (bacmp(&server->src, src) == 0) - return server; - } - - return NULL; -} - -void avrcp_unregister(const bdaddr_t *src) -{ - struct avrcp_server *server; - - server = find_server(servers, src); - if (!server) - return; - - servers = g_slist_remove(servers, server); - - remove_record_from_server(server->ct_record_id); - remove_record_from_server(server->tg_record_id); - - avctp_unregister(&server->src); - g_free(server); - - if (servers) - return; - - if (avctp_id) - avctp_remove_state_cb(avctp_id); - - dbus_connection_unref(connection); - connection = NULL; -} - static DBusMessage *control_is_connected(DBusConnection *conn, DBusMessage *msg, void *data) @@ -1597,191 +213,6 @@ static GDBusSignalTable control_signals[] = { { NULL, NULL } }; -static DBusMessage *mp_set_property(DBusConnection *conn, - DBusMessage *msg, void *data) -{ - struct audio_device *device = data; - struct control *control = device->control; - DBusMessageIter iter; - DBusMessageIter var; - const char *attrstr, *valstr; - int attr, val; - - if (!dbus_message_iter_init(msg, &iter)) - return btd_error_invalid_args(msg); - - if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) - return btd_error_invalid_args(msg); - - dbus_message_iter_get_basic(&iter, &attrstr); - - attr = attr_to_val(attrstr); - if (attr < 0) - return btd_error_not_supported(msg); - - dbus_message_iter_next(&iter); - - if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) - return btd_error_invalid_args(msg); - - dbus_message_iter_recurse(&iter, &var); - - /* Only string arguments are supported for now */ - if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING) - return btd_error_invalid_args(msg); - - dbus_message_iter_get_basic(&var, &valstr); - - val = attrval_to_val(attr, valstr); - if (val < 0) - return btd_error_not_supported(msg); - - mp_set_attribute(control->mp, attr, val); - - return dbus_message_new_method_return(msg); -} - -static DBusMessage *mp_change_playback(DBusConnection *conn, - DBusMessage *msg, void *data) -{ - struct audio_device *device = data; - struct control *control = device->control; - const char *statusstr; - int status; - uint32_t elapsed; - - if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &statusstr, - DBUS_TYPE_UINT32, &elapsed, - DBUS_TYPE_INVALID)) - return btd_error_invalid_args(msg); - - status = play_status_to_val(statusstr); - if (status < 0) - return btd_error_invalid_args(msg); - - mp_set_playback_status(control, status, elapsed); - - return dbus_message_new_method_return(msg); -} - -static gboolean media_info_parse(DBusMessageIter *iter, struct media_info *mi) -{ - DBusMessageIter dict; - DBusMessageIter var; - int ctype; - - ctype = dbus_message_iter_get_arg_type(iter); - if (ctype != DBUS_TYPE_ARRAY) - return FALSE; - - media_info_init(mi); - dbus_message_iter_recurse(iter, &dict); - - while ((ctype = dbus_message_iter_get_arg_type(&dict)) != - DBUS_TYPE_INVALID) { - DBusMessageIter entry; - const char *key; - - if (ctype != DBUS_TYPE_DICT_ENTRY) - return FALSE; - - dbus_message_iter_recurse(&dict, &entry); - if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) - return FALSE; - - dbus_message_iter_get_basic(&entry, &key); - dbus_message_iter_next(&entry); - - if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) - return FALSE; - - dbus_message_iter_recurse(&entry, &var); - - if (!strcmp(key, "Title")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_STRING) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->title); - } else if (!strcmp(key, "Artist")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_STRING) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->artist); - } else if (!strcmp(key, "Album")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_STRING) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->album); - } else if (!strcmp(key, "Genre")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_STRING) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->genre); - } else if (!strcmp(key, "NumberOfTracks")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_UINT32) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->ntracks); - } else if (!strcmp(key, "TrackNumber")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_UINT32) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->track); - } else if (!strcmp(key, "TrackDuration")) { - if (dbus_message_iter_get_arg_type(&var) != - DBUS_TYPE_UINT32) - return FALSE; - - dbus_message_iter_get_basic(&var, &mi->track_len); - } else { - return FALSE; - } - - dbus_message_iter_next(&dict); - } - - if (mi->title == NULL) - return FALSE; - - return TRUE; -} - -static DBusMessage *mp_change_track(DBusConnection *conn, - DBusMessage *msg, void *data) -{ - struct audio_device *device = data; - struct control *control = device->control; - DBusMessageIter iter; - struct media_info mi; - - - dbus_message_iter_init(msg, &iter); - if (!media_info_parse(&iter, &mi)) - return btd_error_invalid_args(msg); - - mp_set_media_attributes(control, &mi); - - return dbus_message_new_method_return(msg); -} - -static GDBusMethodTable mp_methods[] = { - { "SetProperty", "sv", "", mp_set_property }, - { "ChangePlayback", "su", "", mp_change_playback }, - { "ChangeTrack", "a{sv}", "", mp_change_track }, - { } -}; - -static GDBusSignalTable mp_signals[] = { - { "PropertyChanged", "sv" }, - { } -}; - static void path_unregister(void *data) { struct audio_device *dev = data; @@ -1797,78 +228,19 @@ static void path_unregister(void *data) dev->control = NULL; } -static void mp_path_unregister(void *data) -{ - struct audio_device *dev = data; - struct control *control = dev->control; - struct media_player *mp = control->mp; - - DBG("Unregistered interface %s on path %s", - MEDIA_PLAYER_INTERFACE, dev->path); - - if (mp->handler) - avctp_unregister_pdu_handler(mp->handler); - - g_timer_destroy(mp->timer); - g_free(mp); - control->mp = NULL; -} - -static void mp_unregister(struct control *control) -{ - struct audio_device *dev = control->dev; - - g_dbus_unregister_interface(dev->conn, dev->path, - MEDIA_PLAYER_INTERFACE); -} - void control_unregister(struct audio_device *dev) { - struct control *control = dev->control; - - if (control->mp) - mp_unregister(control); - g_dbus_unregister_interface(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE); } -static void mp_register(struct control *control) -{ - struct audio_device *dev = control->dev; - struct media_player *mp; - - mp = g_new0(struct media_player, 1); - - if (!g_dbus_register_interface(dev->conn, dev->path, - MEDIA_PLAYER_INTERFACE, - mp_methods, mp_signals, NULL, - dev, mp_path_unregister)) { - error("D-Bus failed do register %s on path %s", - MEDIA_PLAYER_INTERFACE, dev->path); - g_free(mp); - return; - } - - DBG("Registered interface %s on path %s", - MEDIA_PLAYER_INTERFACE, dev->path); - - mp->timer = g_timer_new(); - media_info_init(&mp->mi); - control->mp = mp; -} - -void control_update(struct control *control, uint16_t uuid16, - gboolean media_player) +void control_update(struct control *control, uint16_t uuid16) { if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) control->target = TRUE; - else if (media_player && !control->mp) - mp_register(control); } -struct control *control_init(struct audio_device *dev, uint16_t uuid16, - gboolean media_player) +struct control *control_init(struct audio_device *dev, uint16_t uuid16) { struct control *control; @@ -1884,7 +256,7 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16, control = g_new0(struct control, 1); control->dev = dev; - control_update(control, uuid16, media_player); + control_update(control, uuid16); if (!avctp_id) avctp_id = avctp_add_state_cb(state_changed, NULL); diff --git a/audio/control.h b/audio/control.h index f5cfef2..2219e5f 100644 --- a/audio/control.h +++ b/audio/control.h @@ -23,17 +23,8 @@ */ #define AUDIO_CONTROL_INTERFACE "org.bluez.Control" -#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer" -int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config); -void avrcp_unregister(const bdaddr_t *src); - -gboolean avrcp_connect(struct audio_device *dev); -void avrcp_disconnect(struct audio_device *dev); - -struct control *control_init(struct audio_device *dev, uint16_t uuid16, - gboolean media_player); -void control_update(struct control *control, uint16_t uuid16, - gboolean media_player); +struct control *control_init(struct audio_device *dev, uint16_t uuid16); +void control_update(struct control *control, uint16_t uuid16); void control_unregister(struct audio_device *dev); gboolean control_is_active(struct audio_device *dev); diff --git a/audio/device.c b/audio/device.c index 16f0701..9ec6fc4 100644 --- a/audio/device.c +++ b/audio/device.c @@ -53,6 +53,7 @@ #include "avdtp.h" #include "control.h" #include "avctp.h" +#include "avrcp.h" #include "headset.h" #include "gateway.h" #include "sink.h" @@ -752,6 +753,9 @@ void audio_device_unregister(struct audio_device *device) if (device->control) control_unregister(device); + if (device->media_player) + media_player_unregister(device); + g_dbus_unregister_interface(device->conn, device->path, AUDIO_INTERFACE); diff --git a/audio/device.h b/audio/device.h index 5671f77..3975108 100644 --- a/audio/device.h +++ b/audio/device.h @@ -44,6 +44,7 @@ struct target; struct sink; struct headset; struct gateway; +struct media_player; struct dev_priv; struct audio_device { @@ -63,6 +64,7 @@ struct audio_device { struct source *source; struct control *control; struct target *target; + struct media_player *media_player; guint hs_preauth_id; diff --git a/audio/manager.c b/audio/manager.c index 880872f..dd4fa6b 100644 --- a/audio/manager.c +++ b/audio/manager.c @@ -63,6 +63,7 @@ #include "gateway.h" #include "sink.h" #include "source.h" +#include "avrcp.h" #include "control.h" #include "manager.h" #include "sdpd.h" @@ -220,11 +221,12 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device) DBG("Found AV %s", uuid16 == AV_REMOTE_SVCLASS_ID ? "Remote" : "Target"); if (device->control) - control_update(device->control, uuid16, - enabled.media_player); + control_update(device->control, uuid16); else - device->control = control_init(device, uuid16, - enabled.media_player); + device->control = control_init(device, uuid16); + + if (enabled.media_player && !device->media_player) + device->media_player = media_player_init(device); if (device->sink && sink_is_active(device)) avrcp_connect(device); break; -- 1.7.6.1