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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx>

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

diff --git a/Makefile.am b/Makefile.am
index f4113b1..ef546d4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -143,6 +143,7 @@ builtin_sources += audio/main.c \
 			audio/gateway.h audio/gateway.c \
 			audio/headset.h audio/headset.c \
 			audio/control.h audio/control.c \
+			audio/avctp.h audio/avctp.c \
 			audio/device.h audio/device.c \
 			audio/source.h audio/source.c \
 			audio/sink.h audio/sink.c \
diff --git a/audio/avctp.c b/audio/avctp.c
new file mode 100644
index 0000000..75875bf
--- /dev/null
+++ b/audio/avctp.c
@@ -0,0 +1,1030 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@xxxxxxxxxxxx>
+ *  Copyright (C) 2011  Texas Instruments, Inc.
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+
+#include "adapter.h"
+#include "../src/device.h"
+
+#include "log.h"
+#include "error.h"
+#include "uinput.h"
+#include "btio.h"
+#include "manager.h"
+#include "device.h"
+#include "avctp.h"
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+/* Message types */
+#define AVCTP_COMMAND		0
+#define AVCTP_RESPONSE		1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE	0
+#define AVCTP_PACKET_START	1
+#define AVCTP_PACKET_CONTINUE	2
+#define AVCTP_PACKET_END	3
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+	uint8_t ipid:1;
+	uint8_t cr:1;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+	uint8_t code:4;
+	uint8_t _hdr0:4;
+	uint8_t subunit_id:3;
+	uint8_t subunit_type:5;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVC_HEADER_LENGTH 3
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t cr:1;
+	uint8_t ipid:1;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avc_header {
+	uint8_t _hdr0:4;
+	uint8_t code:4;
+	uint8_t subunit_type:5;
+	uint8_t subunit_id:3;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVC_HEADER_LENGTH 3
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+	avctp_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avctp_server {
+	bdaddr_t src;
+	GIOChannel *io;
+	GSList *sessions;
+};
+
+struct avctp {
+	struct avctp_server *server;
+	bdaddr_t dst;
+
+	avctp_state_t state;
+
+	int uinput;
+
+	GIOChannel *io;
+	guint io_id;
+
+	uint16_t mtu;
+
+	uint8_t key_quirks[256];
+};
+
+struct avctp_pdu_handler {
+	uint8_t opcode;
+	avctp_pdu_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+static struct {
+	const char *name;
+	uint8_t avc;
+	uint16_t uinput;
+} key_map[] = {
+	{ "PLAY",		PLAY_OP,		KEY_PLAYCD },
+	{ "STOP",		STAVC_OP_OP,		KEY_STOPCD },
+	{ "PAUSE",		PAUSE_OP,		KEY_PAUSECD },
+	{ "FORWARD",		FORWARD_OP,		KEY_NEXTSONG },
+	{ "BACKWARD",		BACKWARD_OP,		KEY_PREVIOUSSONG },
+	{ "REWIND",		REWIND_OP,		KEY_REWIND },
+	{ "FAST FORWARD",	FAST_FORWARD_OP,	KEY_FASTFORWARD },
+	{ NULL }
+};
+
+static GSList *callbacks = NULL;
+static GSList *servers = NULL;
+static GSList *handlers = NULL;
+
+static void auth_cb(DBusError *derr, void *user_data);
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+	struct uinput_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type	= type;
+	event.code	= code;
+	event.value	= value;
+
+	return write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+	if (fd < 0)
+		return;
+
+	send_event(fd, EV_KEY, key, pressed);
+	send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static size_t handle_panel_passthrough(struct avctp *session,
+					uint8_t transaction, uint8_t *code,
+					uint8_t *subunit, uint8_t *operands,
+					size_t operand_count, void *user_data)
+{
+	const char *status;
+	int pressed, i;
+
+	if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
+		*code = AVC_CTYPE_REJECTED;
+		return 0;
+	}
+
+	if (operand_count == 0)
+		goto done;
+
+	if (operands[0] & 0x80) {
+		status = "released";
+		pressed = 0;
+	} else {
+		status = "pressed";
+		pressed = 1;
+	}
+
+	for (i = 0; key_map[i].name != NULL; i++) {
+		uint8_t key_quirks;
+
+		if ((operands[0] & 0x7F) != key_map[i].avc)
+			continue;
+
+		DBG("AVRCP: %s %s", key_map[i].name, status);
+
+		key_quirks = session->key_quirks[key_map[i].avc];
+
+		if (key_quirks & QUIRK_NO_RELEASE) {
+			if (!pressed) {
+				DBG("AVRCP: Ignoring release");
+				break;
+			}
+
+			DBG("AVRCP: treating key press as press + release");
+			send_key(session->uinput, key_map[i].uinput, 1);
+			send_key(session->uinput, key_map[i].uinput, 0);
+			break;
+		}
+
+		send_key(session->uinput, key_map[i].uinput, pressed);
+		break;
+	}
+
+	if (key_map[i].name == NULL)
+		DBG("AVRCP: unknown button 0x%02X %s",
+						operands[0] & 0x7F, status);
+
+done:
+	*code = AVC_CTYPE_ACCEPTED;
+	return operand_count;
+}
+
+static size_t handle_unit_info(struct avctp *session,
+					uint8_t transaction, uint8_t *code,
+					uint8_t *subunit, uint8_t *operands,
+					size_t operand_count, void *user_data)
+{
+	if (*code != AVC_CTYPE_STATUS) {
+		*code = AVC_CTYPE_REJECTED;
+		return 0;
+	}
+
+	*code = AVC_CTYPE_STABLE;
+
+	/* The first operand should be 0x07 for the UNITINFO response.
+	 * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+	 * Interface Command Set (section 9.2.1, page 45) specs
+	 * explain this value but both use it */
+	if (operand_count >= 1)
+		operands[0] = 0x07;
+	if (operand_count >= 2)
+		operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+	DBG("reply to AVC_OP_UNITINFO");
+
+	return 0;
+}
+
+static size_t handle_subunit_info(struct avctp *session,
+					uint8_t transaction, uint8_t *code,
+					uint8_t *subunit, uint8_t *operands,
+					size_t operand_count, void *user_data)
+{
+	if (*code != AVC_CTYPE_STATUS) {
+		*code = AVC_CTYPE_REJECTED;
+		return 0;
+	}
+
+	*code = AVC_CTYPE_STABLE;
+
+	/* The first operand should be 0x07 for the UNITINFO response.
+	 * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+	 * Interface Command Set (section 9.2.1, page 45) specs
+	 * explain this value but both use it */
+	if (operand_count >= 2)
+		operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+	DBG("reply to AVC_OP_SUBUNITINFO");
+
+	return 0;
+}
+
+static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode)
+{
+	for (; list; list = list->next) {
+		struct avctp_pdu_handler *handler = list->data;
+
+		if (handler->opcode == opcode)
+			return handler;
+	}
+
+	return NULL;
+}
+
+static void avctp_disconnected(struct avctp *session)
+{
+	struct avctp_server *server = session->server;
+
+	if (!session)
+		return;
+
+	if (session->io) {
+		g_io_channel_shutdown(session->io, TRUE, NULL);
+		g_io_channel_unref(session->io);
+		session->io = NULL;
+	}
+
+	if (session->io_id) {
+		g_source_remove(session->io_id);
+		session->io_id = 0;
+
+		if (session->state == AVCTP_STATE_CONNECTING) {
+			struct audio_device *dev;
+
+			dev = manager_get_device(&session->server->src,
+							&session->dst, FALSE);
+			audio_device_cancel_authorization(dev, auth_cb,
+								session);
+		}
+	}
+
+	if (session->uinput >= 0) {
+		char address[18];
+
+		ba2str(&session->dst, address);
+		DBG("AVCTP: closing uinput for %s", address);
+
+		ioctl(session->uinput, UI_DEV_DESTROY);
+		close(session->uinput);
+		session->uinput = -1;
+	}
+
+	server->sessions = g_slist_remove(server->sessions, session);
+	g_free(session);
+}
+
+static void avctp_set_state(struct avctp *session, avctp_state_t new_state)
+{
+	GSList *l;
+	struct audio_device *dev;
+	avctp_state_t old_state = session->state;
+
+	dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+	if (dev == NULL) {
+		error("avdtp_set_state(): no matching audio device");
+		return;
+	}
+
+	session->state = new_state;
+
+	for (l = callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		cb->cb(dev, old_state, new_state, cb->user_data);
+	}
+
+	switch (new_state) {
+	case AVCTP_STATE_DISCONNECTED:
+		DBG("AVCTP Disconnected");
+
+		avctp_disconnected(session);
+
+		if (old_state != AVCTP_STATE_CONNECTED)
+			break;
+
+		if (!audio_device_is_active(dev, NULL))
+			audio_device_set_authorized(dev, FALSE);
+
+		break;
+	case AVCTP_STATE_CONNECTING:
+		DBG("AVCTP Connecting");
+		break;
+	case AVCTP_STATE_CONNECTED:
+		DBG("AVCTP Connected");
+		break;
+	default:
+		error("Invalid AVCTP state %d", new_state);
+		return;
+	}
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct avctp *session = data;
+	uint8_t buf[1024], *operands, code, subunit;
+	struct avctp_header *avctp;
+	struct avc_header *avc;
+	int ret, packet_size, operand_count, sock;
+	struct avctp_pdu_handler *handler;
+
+	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+		goto failed;
+
+	sock = g_io_channel_unix_get_fd(session->io);
+
+	ret = read(sock, buf, sizeof(buf));
+	if (ret <= 0)
+		goto failed;
+
+	DBG("Got %d bytes of data for AVCTP session %p", ret, session);
+
+	if ((unsigned int) ret < sizeof(struct avctp_header)) {
+		error("Too small AVCTP packet");
+		goto failed;
+	}
+
+	avctp = (struct avctp_header *) buf;
+
+	DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+			"PID 0x%04X",
+			avctp->transaction, avctp->packet_type,
+			avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+	ret -= sizeof(struct avctp_header);
+	if ((unsigned int) ret < sizeof(struct avc_header)) {
+		error("Too small AVRCP packet");
+		goto failed;
+	}
+
+	avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
+
+	ret -= sizeof(struct avc_header);
+
+	operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
+	operand_count = ret;
+
+	DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+			"opcode 0x%02X, %d operands",
+			avctp->cr ? "response" : "command",
+			avc->code, avc->subunit_type, avc->subunit_id,
+			avc->opcode, operand_count);
+
+	if (avctp->cr == AVCTP_RESPONSE)
+		return TRUE;
+
+	packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
+	avctp->cr = AVCTP_RESPONSE;
+
+	if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+		avc->code = AVC_CTYPE_NOT_IMPLEMENTED;
+		goto done;
+	}
+
+	if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+		avctp->ipid = 1;
+		avc->code = AVC_CTYPE_REJECTED;
+		goto done;
+	}
+
+	handler = find_handler(handlers, avc->opcode);
+	if (!handler) {
+		avc->code = AVC_CTYPE_REJECTED;
+		goto done;
+	}
+
+	code = avc->code;
+	subunit = avc->subunit_type;
+
+	packet_size += handler->cb(session, avctp->transaction, &code,
+					&subunit, operands, operand_count,
+					handler->user_data);
+
+	avc->code = code;
+	avc->subunit_type = subunit;
+
+done:
+	ret = write(sock, buf, packet_size);
+	if (ret != packet_size)
+		goto failed;
+
+	return TRUE;
+
+failed:
+	DBG("AVCTP session %p got disconnected", session);
+	avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+	return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+	struct uinput_dev dev;
+	int fd, err, i;
+
+	fd = open("/dev/uinput", O_RDWR);
+	if (fd < 0) {
+		fd = open("/dev/input/uinput", O_RDWR);
+		if (fd < 0) {
+			fd = open("/dev/misc/uinput", O_RDWR);
+			if (fd < 0) {
+				err = errno;
+				error("Can't open input device: %s (%d)",
+							strerror(err), err);
+				return -err;
+			}
+		}
+	}
+
+	memset(&dev, 0, sizeof(dev));
+	if (name)
+		strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
+
+	dev.id.bustype = BUS_BLUETOOTH;
+	dev.id.vendor  = 0x0000;
+	dev.id.product = 0x0000;
+	dev.id.version = 0x0000;
+
+	if (write(fd, &dev, sizeof(dev)) < 0) {
+		err = errno;
+		error("Can't write device information: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	ioctl(fd, UI_SET_EVBIT, EV_KEY);
+	ioctl(fd, UI_SET_EVBIT, EV_REL);
+	ioctl(fd, UI_SET_EVBIT, EV_REP);
+	ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+	for (i = 0; key_map[i].name != NULL; i++)
+		ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+	if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+		err = errno;
+		error("Can't create uinput device: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	return fd;
+}
+
+static void init_uinput(struct avctp *session)
+{
+	struct audio_device *dev;
+	char address[18], name[248 + 1];
+
+	dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+	device_get_name(dev->btd_dev, name, sizeof(name));
+	if (g_str_equal(name, "Nokia CK-20W")) {
+		session->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
+		session->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
+		session->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
+		session->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
+	}
+
+	ba2str(&session->dst, address);
+
+	session->uinput = uinput_create(address);
+	if (session->uinput < 0)
+		error("AVRCP: failed to init uinput for %s", address);
+	else
+		DBG("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+	struct avctp *session = data;
+	char address[18];
+	uint16_t imtu;
+	GError *gerr = NULL;
+
+	if (err) {
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		return;
+	}
+
+	bt_io_get(chan, BT_IO_L2CAP, &gerr,
+			BT_IO_OPT_DEST, &address,
+			BT_IO_OPT_IMTU, &imtu,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		return;
+	}
+
+	DBG("AVCTP: connected to %s", address);
+
+	if (!session->io)
+		session->io = g_io_channel_ref(chan);
+
+	init_uinput(session);
+
+	avctp_set_state(session, AVCTP_STATE_CONNECTED);
+	session->mtu = imtu;
+	session->io_id = g_io_add_watch(chan,
+				G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) session_cb, session);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct avctp *session = user_data;
+	GError *err = NULL;
+
+	if (session->io_id) {
+		g_source_remove(session->io_id);
+		session->io_id = 0;
+	}
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+		return;
+	}
+
+	if (!bt_io_accept(session->io, avctp_connect_cb, session,
+								NULL, &err)) {
+		error("bt_io_accept: %s", err->message);
+		g_error_free(err);
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+	}
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	for (; list; list = list->next) {
+		struct avctp_server *server = list->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+static struct avctp *find_session(GSList *list, const bdaddr_t *dst)
+{
+	for (; list != NULL; list = g_slist_next(list)) {
+		struct avctp *s = list->data;
+
+		if (bacmp(dst, &s->dst))
+			continue;
+
+		return s;
+	}
+
+	return NULL;
+}
+
+static struct avctp *avctp_get_internal(const bdaddr_t *src,
+							const bdaddr_t *dst)
+{
+	struct avctp_server *server;
+	struct avctp *session;
+
+	assert(src != NULL);
+	assert(dst != NULL);
+
+	server = find_server(servers, src);
+	if (server == NULL)
+		return NULL;
+
+	session = find_session(server->sessions, dst);
+	if (session)
+		return session;
+
+	session = g_new0(struct avctp, 1);
+
+	session->server = server;
+	bacpy(&session->dst, dst);
+	session->state = AVCTP_STATE_DISCONNECTED;
+
+	server->sessions = g_slist_append(server->sessions, session);
+
+	return session;
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+	struct avctp *session;
+	struct audio_device *dev;
+	char address[18];
+	bdaddr_t src, dst;
+	GError *err = NULL;
+
+	bt_io_get(chan, BT_IO_L2CAP, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, address,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		return;
+	}
+
+	DBG("AVCTP: incoming connect from %s", address);
+
+	session = avctp_get_internal(&src, &dst);
+	if (!session)
+		goto drop;
+
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (!dev) {
+		dev = manager_get_device(&src, &dst, TRUE);
+		if (!dev) {
+			error("Unable to get audio device object for %s",
+					address);
+			goto drop;
+		}
+		btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+	}
+
+	if (session->io) {
+		error("Refusing unexpected connect from %s", address);
+		goto drop;
+	}
+
+	avctp_set_state(session, AVCTP_STATE_CONNECTING);
+	session->io = g_io_channel_ref(chan);
+
+	if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+						auth_cb, session) < 0)
+		goto drop;
+
+	session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+							session_cb, session);
+	return;
+
+drop:
+	if (!session || !session->io)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+	if (session)
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
+
+static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, src,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+static unsigned int passthrough_id = 0;
+static unsigned int unit_id = 0;
+static unsigned int subunit_id = 0;
+
+int avctp_register(const bdaddr_t *src, gboolean master)
+{
+	struct avctp_server *server;
+
+	server = g_new0(struct avctp_server, 1);
+	if (!server)
+		return -ENOMEM;
+
+	server->io = avctp_server_socket(src, master);
+	if (!server->io) {
+		g_free(server);
+		return -1;
+	}
+
+	bacpy(&server->src, src);
+
+	servers = g_slist_append(servers, server);
+
+	if (!passthrough_id)
+		passthrough_id = avctp_register_pdu_handler(AVC_OP_PASSTHROUGH,
+					handle_panel_passthrough, NULL);
+
+	if (!unit_id)
+		unit_id = avctp_register_pdu_handler(AVC_OP_UNITINFO, handle_unit_info,
+									NULL);
+
+	if (!subunit_id)
+		subunit_id = avctp_register_pdu_handler(AVC_OP_SUBUNITINFO,
+						handle_subunit_info, NULL);
+
+	return 0;
+}
+
+void avctp_unregister(const bdaddr_t *src)
+{
+	struct avctp_server *server;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	while (server->sessions)
+		avctp_disconnected(server->sessions->data);
+
+	servers = g_slist_remove(servers, server);
+
+	g_io_channel_shutdown(server->io, TRUE, NULL);
+	g_io_channel_unref(server->io);
+	g_free(server);
+
+	if (servers)
+		return;
+
+	if (passthrough_id)
+		avctp_unregister_pdu_handler(passthrough_id);
+
+	if (unit_id)
+		avctp_unregister_pdu_handler(unit_id);
+
+	if (subunit_id)
+		avctp_unregister_pdu_handler(unit_id);
+}
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op)
+{
+	unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
+	struct avctp_header *avctp = (void *) buf;
+	struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+	uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+	int sk;
+	static uint8_t transaction = 0;
+
+	if (session->state != AVCTP_STATE_CONNECTED)
+		return -ENOTCONN;
+
+	memset(buf, 0, sizeof(buf));
+
+	avctp->transaction = transaction++;
+	avctp->packet_type = AVCTP_PACKET_SINGLE;
+	avctp->cr = AVCTP_COMMAND;
+	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+	avc->code = AVC_CTYPE_CONTROL;
+	avc->subunit_type = AVC_SUBUNIT_PANEL;
+	avc->opcode = AVC_OP_PASSTHROUGH;
+
+	operands[0] = op & 0x7f;
+	operands[1] = 0;
+
+	sk = g_io_channel_unix_get_fd(session->io);
+
+	if (write(sk, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	/* Button release */
+	avctp->transaction = transaction++;
+	operands[0] |= 0x80;
+
+	if (write(sk, buf, sizeof(buf)) < 0)
+		return -errno;
+
+	return 0;
+}
+
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+				uint8_t code, uint8_t subunit,
+				uint8_t *operands, size_t operand_count)
+{
+	uint8_t *buf;
+	struct avctp_header *avctp;
+	struct avc_header *avc;
+	uint8_t *pdu;
+	int sk, err;
+	uint16_t size;
+
+	if (session->state != AVCTP_STATE_CONNECTED)
+		return -ENOTCONN;
+
+	sk = g_io_channel_unix_get_fd(session->io);
+	size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + operand_count;
+	buf = g_malloc0(size);
+
+	avctp = (void *) buf;
+	avc = (void *) &buf[AVCTP_HEADER_LENGTH];
+	pdu = (void *) &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
+
+	avctp->transaction = transaction;
+	avctp->packet_type = AVCTP_PACKET_SINGLE;
+	avctp->cr = AVCTP_RESPONSE;
+	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+	avc->code = code;
+	avc->subunit_type = subunit;
+	avc->opcode = AVC_OP_VENDORDEP;
+
+	memcpy(pdu, operands, operand_count);
+
+	err = write(sk, buf, size);
+	if (err < 0) {
+		g_free(buf);
+		return -errno;
+	}
+
+	g_free(buf);
+	return 0;
+}
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
+{
+	struct avctp_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct avctp_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	callbacks = g_slist_append(callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			callbacks = g_slist_remove(callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+							void *user_data)
+{
+	struct avctp_pdu_handler *handler;
+	static unsigned int id = 0;
+
+	handler = find_handler(handlers, opcode);
+	if (handler)
+		return 0;
+
+	handler = g_new(struct avctp_pdu_handler, 1);
+	handler->opcode = opcode;
+	handler->cb = cb;
+	handler->user_data = user_data;
+	handler->id = ++id;
+
+	handlers = g_slist_append(handlers, handler);
+
+	return handler->id;
+}
+
+gboolean avctp_unregister_pdu_handler(unsigned int id)
+{
+	GSList *l;
+
+	for (l = handlers; l != NULL; l = l->next) {
+		struct avctp_pdu_handler *handler = l->data;
+
+		if (handler->id == id) {
+			handlers = g_slist_remove(handlers, handler);
+			g_free(handler);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct avctp *session;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	session = avctp_get_internal(src, dst);
+	if (!session)
+		return NULL;
+
+	if (session->state > AVCTP_STATE_DISCONNECTED)
+		return session;
+
+	avctp_set_state(session, AVCTP_STATE_CONNECTING);
+
+	io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, session, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+				BT_IO_OPT_DEST_BDADDR, &session->dst,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_INVALID);
+	if (err) {
+		avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		g_error_free(err);
+		return NULL;
+	}
+
+	session->io = io;
+
+	return session;
+}
+
+void avctp_disconnect(struct avctp *session)
+{
+	if (session->io)
+		return;
+
+	avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
+}
diff --git a/audio/avctp.h b/audio/avctp.h
new file mode 100644
index 0000000..157ecc6
--- /dev/null
+++ b/audio/avctp.h
@@ -0,0 +1,95 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2010  Nokia Corporation
+ *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@xxxxxxxxxxxx>
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define AVCTP_PSM 23
+
+/* ctype entries */
+#define AVC_CTYPE_CONTROL		0x0
+#define AVC_CTYPE_STATUS		0x1
+#define AVC_CTYPE_NOTIFY		0x3
+#define AVC_CTYPE_NOT_IMPLEMENTED	0x8
+#define AVC_CTYPE_ACCEPTED		0x9
+#define AVC_CTYPE_REJECTED		0xA
+#define AVC_CTYPE_STABLE		0xC
+#define AVC_CTYPE_CHANGED		0xD
+#define AVC_CTYPE_INTERIM		0xF
+
+/* opcodes */
+#define AVC_OP_VENDORDEP		0x00
+#define AVC_OP_UNITINFO			0x30
+#define AVC_OP_SUBUNITINFO		0x31
+#define AVC_OP_PASSTHROUGH		0x7c
+
+/* subunits of interest */
+#define AVC_SUBUNIT_PANEL		0x09
+
+/* operands in passthrough commands */
+#define VOL_UP_OP			0x41
+#define VOL_DOWN_OP			0x42
+#define MUTE_OP				0x43
+#define PLAY_OP				0x44
+#define STAVC_OP_OP			0x45
+#define PAUSE_OP			0x46
+#define RECORD_OP			0x47
+#define REWIND_OP			0x48
+#define FAST_FORWARD_OP			0x49
+#define EJECT_OP			0x4a
+#define FORWARD_OP			0x4b
+#define BACKWARD_OP			0x4c
+
+struct avctp;
+
+typedef enum {
+	AVCTP_STATE_DISCONNECTED = 0,
+	AVCTP_STATE_CONNECTING,
+	AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+				avctp_state_t old_state,
+				avctp_state_t new_state,
+				void *user_data);
+
+typedef size_t (*avctp_pdu_cb) (struct avctp *session, uint8_t transaction,
+					uint8_t *code, uint8_t *subunit,
+					uint8_t *operands, size_t operand_count,
+					void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avctp_register(const bdaddr_t *src, gboolean master);
+void avctp_unregister(const bdaddr_t *src);
+
+struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst);
+void avctp_disconnect(struct avctp *session);
+
+unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
+							void *user_data);
+gboolean avctp_unregister_pdu_handler(unsigned int id);
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op);
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+				uint8_t code, uint8_t subunit,
+				uint8_t *operands, size_t operand_count);
diff --git a/audio/control.c b/audio/control.c
index cafb951..dceb004 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -36,7 +36,6 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <netinet/in.h>
 
 #include <bluetooth/bluetooth.h>
 #include <bluetooth/sdp.h>
@@ -48,64 +47,14 @@
 
 #include "log.h"
 #include "error.h"
-#include "uinput.h"
-#include "adapter.h"
-#include "../src/device.h"
 #include "device.h"
 #include "manager.h"
-#include "avdtp.h"
+#include "avctp.h"
 #include "control.h"
 #include "sdpd.h"
 #include "glib-helper.h"
-#include "btio.h"
 #include "dbus-common.h"
 
-#define AVCTP_PSM 23
-
-/* Message types */
-#define AVCTP_COMMAND		0
-#define AVCTP_RESPONSE		1
-
-/* Packet types */
-#define AVCTP_PACKET_SINGLE	0
-#define AVCTP_PACKET_START	1
-#define AVCTP_PACKET_CONTINUE	2
-#define AVCTP_PACKET_END	3
-
-/* ctype entries */
-#define CTYPE_CONTROL		0x0
-#define CTYPE_STATUS		0x1
-#define CTYPE_NOTIFY		0x3
-#define CTYPE_NOT_IMPLEMENTED	0x8
-#define CTYPE_ACCEPTED		0x9
-#define CTYPE_REJECTED		0xA
-#define CTYPE_STABLE		0xC
-#define CTYPE_CHANGED		0xD
-#define CTYPE_INTERIM		0xF
-
-/* opcodes */
-#define OP_VENDORDEP		0x00
-#define OP_UNITINFO		0x30
-#define OP_SUBUNITINFO		0x31
-#define OP_PASSTHROUGH		0x7c
-
-/* subunits of interest */
-#define SUBUNIT_PANEL		0x09
-
-/* operands in passthrough commands */
-#define VOL_UP_OP		0x41
-#define VOL_DOWN_OP		0x42
-#define MUTE_OP			0x43
-#define PLAY_OP			0x44
-#define STOP_OP			0x45
-#define PAUSE_OP		0x46
-#define RECORD_OP		0x47
-#define REWIND_OP		0x48
-#define FAST_FORWARD_OP		0x49
-#define EJECT_OP		0x4a
-#define FORWARD_OP		0x4b
-#define BACKWARD_OP		0x4c
-
 /* Company IDs for vendor dependent commands */
 #define IEEEID_BTSIG		0x001958
 
@@ -137,8 +86,6 @@
 #define CAP_COMPANY_ID		0x02
 #define CAP_EVENTS_SUPPORTED	0x03
 
-#define QUIRK_NO_RELEASE	1 << 0
-
 enum player_setting {
 	PLAYER_SETTING_EQUALIZER =	1,
 	PLAYER_SETTING_REPEAT =		2,
@@ -200,27 +147,10 @@ enum media_info_id {
 static DBusConnection *connection = NULL;
 
 static GSList *servers = NULL;
+static unsigned int avctp_id = 0;
 
 #if __BYTE_ORDER == __LITTLE_ENDIAN
 
-struct avctp_header {
-	uint8_t ipid:1;
-	uint8_t cr:1;
-	uint8_t packet_type:2;
-	uint8_t transaction:4;
-	uint16_t pid;
-} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
-
-struct avc_header {
-	uint8_t code:4;
-	uint8_t _hdr0:4;
-	uint8_t subunit_id:3;
-	uint8_t subunit_type:5;
-	uint8_t opcode;
-} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
-
 struct avrcp_header {
 	uint8_t company_id[3];
 	uint8_t pdu_id;
@@ -233,24 +163,6 @@ struct avrcp_header {
 
 #elif __BYTE_ORDER == __BIG_ENDIAN
 
-struct avctp_header {
-	uint8_t transaction:4;
-	uint8_t packet_type:2;
-	uint8_t cr:1;
-	uint8_t ipid:1;
-	uint16_t pid;
-} __attribute__ ((packed));
-#define AVCTP_HEADER_LENGTH 3
-
-struct avc_header {
-	uint8_t _hdr0:4;
-	uint8_t code:4;
-	uint8_t subunit_type:5;
-	uint8_t subunit_id:3;
-	uint8_t opcode;
-} __attribute__ ((packed));
-#define AVC_HEADER_LENGTH 3
-
 struct avrcp_header {
 	uint8_t company_id[3];
 	uint8_t pdu_id;
@@ -265,15 +177,8 @@ struct avrcp_header {
 #error "Unknown byte order"
 #endif
 
-struct avctp_state_callback {
-	avctp_state_cb cb;
-	void *user_data;
-	unsigned int id;
-};
-
-struct avctp_server {
+struct avrcp_server {
 	bdaddr_t src;
-	GIOChannel *io;
 	uint32_t tg_record_id;
 	uint32_t ct_record_id;
 };
@@ -295,53 +200,25 @@ struct media_player {
 
 	struct media_info mi;
 	GTimer *timer;
+	unsigned int handler;
 };
 
 struct control {
 	struct audio_device *dev;
 	struct media_player *mp;
-
-	avctp_state_t state;
-
-	int uinput;
-
-	GIOChannel *io;
-	guint io_id;
-
-	uint16_t mtu;
+	struct avctp *session;
 
 	gboolean target;
 
-	uint8_t key_quirks[256];
-
 	uint16_t registered_events;
 	uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
 };
 
-static struct {
-	const char *name;
-	uint8_t avrcp;
-	uint16_t uinput;
-} key_map[] = {
-	{ "PLAY",		PLAY_OP,		KEY_PLAYCD },
-	{ "STOP",		STOP_OP,		KEY_STOPCD },
-	{ "PAUSE",		PAUSE_OP,		KEY_PAUSECD },
-	{ "FORWARD",		FORWARD_OP,		KEY_NEXTSONG },
-	{ "BACKWARD",		BACKWARD_OP,		KEY_PREVIOUSSONG },
-	{ "REWIND",		REWIND_OP,		KEY_REWIND },
-	{ "FAST FORWARD",	FAST_FORWARD_OP,	KEY_FASTFORWARD },
-	{ NULL }
-};
-
 /* Company IDs supported by this device */
 static uint32_t company_ids[] = {
 	IEEEID_BTSIG,
 };
 
-static GSList *avctp_callbacks = NULL;
-
-static void auth_cb(DBusError *derr, void *user_data);
-
 static sdp_record_t *avrcp_ct_record(void)
 {
 	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -470,76 +347,6 @@ static sdp_record_t *avrcp_tg_record(void)
 	return record;
 }
 
-static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
-{
-	struct uinput_event event;
-
-	memset(&event, 0, sizeof(event));
-	event.type	= type;
-	event.code	= code;
-	event.value	= value;
-
-	return write(fd, &event, sizeof(event));
-}
-
-static void send_key(int fd, uint16_t key, int pressed)
-{
-	if (fd < 0)
-		return;
-
-	send_event(fd, EV_KEY, key, pressed);
-	send_event(fd, EV_SYN, SYN_REPORT, 0);
-}
-
-static void handle_panel_passthrough(struct control *control,
-					const unsigned char *operands,
-					int operand_count)
-{
-	const char *status;
-	int pressed, i;
-
-	if (operand_count == 0)
-		return;
-
-	if (operands[0] & 0x80) {
-		status = "released";
-		pressed = 0;
-	} else {
-		status = "pressed";
-		pressed = 1;
-	}
-
-	for (i = 0; key_map[i].name != NULL; i++) {
-		uint8_t key_quirks;
-
-		if ((operands[0] & 0x7F) != key_map[i].avrcp)
-			continue;
-
-		DBG("AVRCP: %s %s", key_map[i].name, status);
-
-		key_quirks = control->key_quirks[key_map[i].avrcp];
-
-		if (key_quirks & QUIRK_NO_RELEASE) {
-			if (!pressed) {
-				DBG("AVRCP: Ignoring release");
-				break;
-			}
-
-			DBG("AVRCP: treating key press as press + release");
-			send_key(control->uinput, key_map[i].uinput, 1);
-			send_key(control->uinput, key_map[i].uinput, 0);
-			break;
-		}
-
-		send_key(control->uinput, key_map[i].uinput, pressed);
-		break;
-	}
-
-	if (key_map[i].name == NULL)
-		DBG("AVRCP: unknown button 0x%02X %s",
-						operands[0] & 0x7F, status);
-}
-
 static unsigned int attr_get_max_val(uint8_t attr)
 {
 	switch (attr) {
@@ -719,18 +526,14 @@ static const char *battery_status_to_str(enum battery_status status)
 	return NULL;
 }
 
-static int avctp_send_event(struct control *control, uint8_t id, void *data)
+static int avrcp_send_event(struct control *control, uint8_t id, void *data)
 {
-	uint8_t buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
-					AVRCP_HEADER_LENGTH + 9];
-	struct avctp_header *avctp = (void *) buf;
-	struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
-	struct avrcp_header *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
-							AVC_HEADER_LENGTH];
-	int sk;
+	uint8_t buf[AVRCP_HEADER_LENGTH + 9];
+	struct avrcp_header *pdu = (void *) buf;
 	uint16_t size;
+	int err;
 
-	if (control->state != AVCTP_STATE_CONNECTED)
+	if (control->session)
 		return -ENOTCONN;
 
 	if (!(control->registered_events & (1 << id)))
@@ -738,15 +541,6 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)
 
 	memset(buf, 0, sizeof(buf));
 
-	avctp->transaction = control->transaction_events[id];
-	avctp->packet_type = AVCTP_PACKET_SINGLE;
-	avctp->cr = AVCTP_RESPONSE;
-	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
-
-	avc->code = CTYPE_CHANGED;
-	avc->subunit_type = SUBUNIT_PANEL;
-	avc->opcode = OP_VENDORDEP;
-
 	pdu->company_id[0] = IEEEID_BTSIG >> 16;
 	pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF;
 	pdu->company_id[2] = IEEEID_BTSIG & 0xFF;
@@ -780,13 +574,12 @@ static int avctp_send_event(struct control *control, uint8_t id, void *data)
 	}
 
 	pdu->params_len = htons(size);
-	size += AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH +
-					AVRCP_HEADER_LENGTH;
-
-	sk = g_io_channel_unix_get_fd(control->io);
 
-	if (write(sk, buf, size) < 0)
-		return -errno;
+	err = avctp_send_vendordep(control->session, control->transaction_events[id],
+					AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
+					buf, size);
+	if (err < 0)
+		return err;
 
 	/* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
 	control->registered_events ^= 1 << id;
@@ -833,7 +626,7 @@ static void mp_set_playback_status(struct control *control, uint8_t status,
 
 	mp->status = status;
 
-	avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
+	avrcp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
 								&status);
 }
 
@@ -983,7 +776,7 @@ static void mp_set_media_attributes(struct control *control,
 			mi->title, mi->artist, mi->album, mi->genre,
 			mi->ntracks, mi->track, mi->track_len);
 
-	avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
+	avrcp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
 }
 
 static uint8_t avrcp_handle_get_capabilities(struct control *control,
@@ -1009,21 +802,21 @@ static uint8_t avrcp_handle_get_capabilities(struct control *control,
 		pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
 		pdu->params[1] = G_N_ELEMENTS(company_ids);
 
-		return CTYPE_STABLE;
+		return AVC_CTYPE_STABLE;
 	case CAP_EVENTS_SUPPORTED:
 		pdu->params_len = htons(4);
 		pdu->params[1] = 2;
 		pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED;
 		pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
 
-		return CTYPE_STABLE;
+		return AVC_CTYPE_STABLE;
 	}
 
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
 
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_list_player_attributes(struct control *control,
@@ -1037,7 +830,7 @@ static uint8_t avrcp_handle_list_player_attributes(struct control *control,
 	if (len != 0) {
 		pdu->params_len = htons(1);
 		pdu->params[0] = E_INVALID_PARAM;
-		return CTYPE_REJECTED;
+		return AVC_CTYPE_REJECTED;
 	}
 
 	if (!mp)
@@ -1057,7 +850,7 @@ done:
 	pdu->params[0] = len;
 	pdu->params_len = htons(len + 1);
 
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 }
 
 static uint8_t avrcp_handle_list_player_values(struct control *control,
@@ -1083,12 +876,12 @@ static uint8_t avrcp_handle_list_player_values(struct control *control,
 	pdu->params[0] = len;
 	pdu->params_len = htons(len + 1);
 
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_get_element_attributes(struct control *control,
@@ -1153,11 +946,11 @@ done:
 	pdu->params[0] = len;
 	pdu->params_len = htons(pos);
 
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_get_current_player_value(struct control *control,
@@ -1212,7 +1005,7 @@ static uint8_t avrcp_handle_get_current_player_value(struct control *control,
 		pdu->params[0] = len;
 		pdu->params_len = htons(2 * len + 1);
 
-		return CTYPE_STABLE;
+		return AVC_CTYPE_STABLE;
 	}
 
 	error("No valid attributes in request");
@@ -1221,7 +1014,7 @@ err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
 
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_set_player_value(struct control *control,
@@ -1268,13 +1061,13 @@ static uint8_t avrcp_handle_set_player_value(struct control *control,
 	if (len) {
 		pdu->params_len = 0;
 
-		return CTYPE_STABLE;
+		return AVC_CTYPE_STABLE;
 	}
 
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_displayable_charset(struct control *control,
@@ -1286,7 +1079,7 @@ static uint8_t avrcp_handle_displayable_charset(struct control *control,
 	if (len < 3) {
 		pdu->params_len = htons(1);
 		pdu->params[0] = E_INVALID_PARAM;
-		return CTYPE_REJECTED;
+		return AVC_CTYPE_REJECTED;
 	}
 
 	/*
@@ -1294,7 +1087,7 @@ static uint8_t avrcp_handle_displayable_charset(struct control *control,
 	 * encoding since CT is obliged to support it.
 	 */
 	pdu->params_len = 0;
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 }
 
 static uint8_t avrcp_handle_ct_battery_status(struct control *control,
@@ -1316,12 +1109,12 @@ static uint8_t avrcp_handle_ct_battery_status(struct control *control,
 					DBUS_TYPE_STRING, &valstr);
 	pdu->params_len = 0;
 
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static uint8_t avrcp_handle_get_play_status(struct control *control,
@@ -1336,7 +1129,7 @@ static uint8_t avrcp_handle_get_play_status(struct control *control,
 	if (len != 0) {
 		pdu->params_len = htons(1);
 		pdu->params[0] = E_INVALID_PARAM;
-		return CTYPE_REJECTED;
+		return AVC_CTYPE_REJECTED;
 	}
 
 	if (control->mp) {
@@ -1356,7 +1149,7 @@ static uint8_t avrcp_handle_get_play_status(struct control *control,
 
 	pdu->params_len = htons(9);
 
-	return CTYPE_STABLE;
+	return AVC_CTYPE_STABLE;
 }
 
 static uint8_t avrcp_handle_register_notification(struct control *control,
@@ -1406,12 +1199,12 @@ static uint8_t avrcp_handle_register_notification(struct control *control,
 
 	pdu->params_len = htons(len);
 
-	return CTYPE_INTERIM;
+	return AVC_CTYPE_INTERIM;
 
 err:
 	pdu->params_len = htons(1);
 	pdu->params[0] = E_INVALID_PARAM;
-	return CTYPE_REJECTED;
+	return AVC_CTYPE_REJECTED;
 }
 
 static struct pdu_handler {
@@ -1421,51 +1214,54 @@ static struct pdu_handler {
 					struct avrcp_header *pdu,
 					uint8_t transaction);
 } handlers[] = {
-		{ AVRCP_GET_CAPABILITIES, CTYPE_STATUS,
+		{ AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
 					avrcp_handle_get_capabilities },
-		{ AVRCP_LIST_PLAYER_ATTRIBUTES, CTYPE_STATUS,
+		{ AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
 					avrcp_handle_list_player_attributes },
-		{ AVRCP_LIST_PLAYER_VALUES, CTYPE_STATUS,
+		{ AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
 					avrcp_handle_list_player_values },
-		{ AVRCP_GET_ELEMENT_ATTRIBUTES, CTYPE_STATUS,
+		{ AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
 					avrcp_handle_get_element_attributes },
-		{ AVRCP_GET_CURRENT_PLAYER_VALUE, CTYPE_STATUS,
+		{ AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
 					avrcp_handle_get_current_player_value },
-		{ AVRCP_SET_PLAYER_VALUE, CTYPE_CONTROL,
+		{ AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
 					avrcp_handle_set_player_value },
-		{ AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, CTYPE_STATUS,
+		{ AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
 					NULL },
-		{ AVRCP_GET_PLAYER_VALUE_TEXT, CTYPE_STATUS,
+		{ AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
 					NULL },
-		{ AVRCP_DISPLAYABLE_CHARSET, CTYPE_STATUS,
+		{ AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
 					avrcp_handle_displayable_charset },
-		{ AVRCP_CT_BATTERY_STATUS, CTYPE_STATUS,
+		{ AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
 					avrcp_handle_ct_battery_status },
-		{ AVRCP_GET_PLAY_STATUS, CTYPE_STATUS,
+		{ AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
 					avrcp_handle_get_play_status },
-		{ AVRCP_REGISTER_NOTIFICATION, CTYPE_NOTIFY,
+		{ AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
 					avrcp_handle_register_notification },
 		{ },
 };
 
 /* handle vendordep pdu inside an avctp packet */
-static int handle_vendordep_pdu(struct control *control,
-					struct avctp_header *avctp,
-					struct avc_header *avc,
-					int operand_count)
+static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
+					uint8_t *code, uint8_t *subunit,
+					uint8_t *operands, size_t operand_count,
+					void *user_data)
 {
+	struct control *control = user_data;
 	struct pdu_handler *handler;
-	struct avrcp_header *pdu = (void *) avc + AVC_HEADER_LENGTH;
+	struct avrcp_header *pdu = (void *) operands;
 	uint32_t company_id = (pdu->company_id[0] << 16) |
 				(pdu->company_id[1] << 8) |
 				(pdu->company_id[2]);
 
-	if (company_id != IEEEID_BTSIG ||
-				pdu->packet_type != AVCTP_PACKET_SINGLE) {
-		avc->code = CTYPE_NOT_IMPLEMENTED;
-		return AVC_HEADER_LENGTH;
+	if (company_id != IEEEID_BTSIG) {
+		*code = AVC_CTYPE_NOT_IMPLEMENTED;
+		return 0;
 	}
 
+	DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
+			pdu->pdu_id, company_id, pdu->params_len);
+
 	pdu->packet_type = 0;
 	pdu->rsvd = 0;
 
@@ -1477,7 +1273,7 @@ static int handle_vendordep_pdu(struct control *control,
 			break;
 	}
 
-	if (!handler || handler->code != avc->code) {
+	if (!handler || handler->code != *code) {
 		pdu->params[0] = E_INVALID_COMMAND;
 		goto err_metadata;
 	}
@@ -1487,64 +1283,31 @@ static int handle_vendordep_pdu(struct control *control,
 		goto err_metadata;
 	}
 
-	avc->code = handler->func(control, pdu, avctp->transaction);
+	*code = handler->func(control, pdu, transaction);
 
-	return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
-						ntohs(pdu->params_len);
+	return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
 
 err_metadata:
 	pdu->params_len = htons(1);
-	avc->code = CTYPE_REJECTED;
+	*code = AVC_CTYPE_REJECTED;
 
-	return AVC_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 1;
+	return AVRCP_HEADER_LENGTH + 1;
 }
 
-static void avctp_disconnected(struct audio_device *dev)
+static void state_changed(struct audio_device *dev, avctp_state_t old_state,
+				avctp_state_t new_state, void *user_data)
 {
 	struct control *control = dev->control;
-
-	if (!control)
-		return;
-
-	if (control->io) {
-		g_io_channel_shutdown(control->io, TRUE, NULL);
-		g_io_channel_unref(control->io);
-		control->io = NULL;
-	}
-
-	if (control->io_id) {
-		g_source_remove(control->io_id);
-		control->io_id = 0;
-
-		if (control->state == AVCTP_STATE_CONNECTING)
-			audio_device_cancel_authorization(dev, auth_cb,
-								control);
-	}
-
-	if (control->uinput >= 0) {
-		char address[18];
-
-		ba2str(&dev->dst, address);
-		DBG("AVRCP: closing uinput for %s", address);
-
-		ioctl(control->uinput, UI_DEV_DESTROY);
-		close(control->uinput);
-		control->uinput = -1;
-	}
-}
-
-static void avctp_set_state(struct control *control, avctp_state_t new_state)
-{
-	GSList *l;
-	struct audio_device *dev = control->dev;
-	avctp_state_t old_state = control->state;
 	gboolean value;
 
 	switch (new_state) {
 	case AVCTP_STATE_DISCONNECTED:
-		DBG("AVCTP Disconnected");
+		control->session = NULL;
 
-		avctp_disconnected(control->dev);
+		if (control->mp && control->mp->handler) {
+			avctp_unregister_pdu_handler(control->mp->handler);
+			control->mp->handler = 0;
+		}
 
 		if (old_state != AVCTP_STATE_CONNECTED)
 			break;
@@ -1557,221 +1320,32 @@ static void avctp_set_state(struct control *control, avctp_state_t new_state)
 					AUDIO_CONTROL_INTERFACE, "Connected",
 					DBUS_TYPE_BOOLEAN, &value);
 
-		if (!audio_device_is_active(dev, NULL))
-			audio_device_set_authorized(dev, FALSE);
-
 		break;
 	case AVCTP_STATE_CONNECTING:
-		DBG("AVCTP Connecting");
+		if (control->session)
+			break;
+
+		control->session = avctp_connect(&dev->src, &dev->dst);
+		if (!control->mp)
+			break;
+
+		control->mp->handler = avctp_register_pdu_handler(
+							AVC_OP_VENDORDEP,
+							handle_vendordep_pdu,
+							control);
 		break;
 	case AVCTP_STATE_CONNECTED:
-		DBG("AVCTP Connected");
 		value = TRUE;
-		g_dbus_emit_signal(control->dev->conn, control->dev->path,
+		g_dbus_emit_signal(dev->conn, dev->path,
 				AUDIO_CONTROL_INTERFACE, "Connected",
 				DBUS_TYPE_INVALID);
-		emit_property_changed(control->dev->conn, control->dev->path,
+		emit_property_changed(dev->conn, dev->path,
 				AUDIO_CONTROL_INTERFACE, "Connected",
 				DBUS_TYPE_BOOLEAN, &value);
 		break;
 	default:
-		error("Invalid AVCTP state %d", new_state);
 		return;
 	}
-
-	control->state = new_state;
-
-	for (l = avctp_callbacks; l != NULL; l = l->next) {
-		struct avctp_state_callback *cb = l->data;
-		cb->cb(control->dev, old_state, new_state, cb->user_data);
-	}
-}
-
-static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
-				gpointer data)
-{
-	struct control *control = data;
-	unsigned char buf[1024], *operands;
-	struct avctp_header *avctp;
-	struct avc_header *avc;
-	int ret, packet_size, operand_count, sock;
-
-	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
-		goto failed;
-
-	sock = g_io_channel_unix_get_fd(control->io);
-
-	ret = read(sock, buf, sizeof(buf));
-	if (ret <= 0)
-		goto failed;
-
-	DBG("Got %d bytes of data for AVCTP session %p", ret, control);
-
-	if ((unsigned int) ret < sizeof(struct avctp_header)) {
-		error("Too small AVCTP packet");
-		goto failed;
-	}
-
-	packet_size = ret;
-
-	avctp = (struct avctp_header *) buf;
-
-	DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
-			"PID 0x%04X",
-			avctp->transaction, avctp->packet_type,
-			avctp->cr, avctp->ipid, ntohs(avctp->pid));
-
-	ret -= sizeof(struct avctp_header);
-	if ((unsigned int) ret < sizeof(struct avc_header)) {
-		error("Too small AVRCP packet");
-		goto failed;
-	}
-
-	avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
-
-	ret -= sizeof(struct avc_header);
-
-	operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
-	operand_count = ret;
-
-	DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
-			"opcode 0x%02X, %d operands",
-			avctp->cr ? "response" : "command",
-			avc->code, avc->subunit_type, avc->subunit_id,
-			avc->opcode, operand_count);
-
-	if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
-		avctp->cr = AVCTP_RESPONSE;
-		avc->code = CTYPE_NOT_IMPLEMENTED;
-	} else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
-		avctp->ipid = 1;
-		avctp->cr = AVCTP_RESPONSE;
-		packet_size = sizeof(*avctp);
-	} else if (avctp->cr == AVCTP_COMMAND &&
-			avc->code == CTYPE_CONTROL &&
-			avc->subunit_type == SUBUNIT_PANEL &&
-			avc->opcode == OP_PASSTHROUGH) {
-		handle_panel_passthrough(control, operands, operand_count);
-		avctp->cr = AVCTP_RESPONSE;
-		avc->code = CTYPE_ACCEPTED;
-	} else if (avctp->cr == AVCTP_COMMAND &&
-			avc->code == CTYPE_STATUS &&
-			(avc->opcode == OP_UNITINFO
-			|| avc->opcode == OP_SUBUNITINFO)) {
-		avctp->cr = AVCTP_RESPONSE;
-		avc->code = CTYPE_STABLE;
-		/* The first operand should be 0x07 for the UNITINFO response.
-		 * Neither AVRCP (section 22.1, page 117) nor AVC Digital
-		 * Interface Command Set (section 9.2.1, page 45) specs
-		 * explain this value but both use it */
-		if (operand_count >= 1 && avc->opcode == OP_UNITINFO)
-			operands[0] = 0x07;
-		if (operand_count >= 2)
-			operands[1] = SUBUNIT_PANEL << 3;
-		DBG("reply to %s", avc->opcode == OP_UNITINFO ?
-				"OP_UNITINFO" : "OP_SUBUNITINFO");
-	} else if (avctp->cr == AVCTP_COMMAND &&
-			avc->opcode == OP_VENDORDEP) {
-		int r_size;
-		operand_count -= 3;
-		avctp->cr = AVCTP_RESPONSE;
-		r_size = handle_vendordep_pdu(control, avctp, avc,
-								operand_count);
-		packet_size = AVCTP_HEADER_LENGTH + r_size;
-	} else {
-		avctp->cr = AVCTP_RESPONSE;
-		avc->code = CTYPE_REJECTED;
-	}
-
-	ret = write(sock, buf, packet_size);
-	if (ret != packet_size)
-		goto failed;
-
-	return TRUE;
-
-failed:
-	DBG("AVCTP session %p got disconnected", control);
-	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-	return FALSE;
-}
-
-static int uinput_create(char *name)
-{
-	struct uinput_dev dev;
-	int fd, err, i;
-
-	fd = open("/dev/uinput", O_RDWR);
-	if (fd < 0) {
-		fd = open("/dev/input/uinput", O_RDWR);
-		if (fd < 0) {
-			fd = open("/dev/misc/uinput", O_RDWR);
-			if (fd < 0) {
-				err = errno;
-				error("Can't open input device: %s (%d)",
-							strerror(err), err);
-				return -err;
-			}
-		}
-	}
-
-	memset(&dev, 0, sizeof(dev));
-	if (name)
-		strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
-
-	dev.id.bustype = BUS_BLUETOOTH;
-	dev.id.vendor  = 0x0000;
-	dev.id.product = 0x0000;
-	dev.id.version = 0x0000;
-
-	if (write(fd, &dev, sizeof(dev)) < 0) {
-		err = errno;
-		error("Can't write device information: %s (%d)",
-						strerror(err), err);
-		close(fd);
-		errno = err;
-		return -err;
-	}
-
-	ioctl(fd, UI_SET_EVBIT, EV_KEY);
-	ioctl(fd, UI_SET_EVBIT, EV_REL);
-	ioctl(fd, UI_SET_EVBIT, EV_REP);
-	ioctl(fd, UI_SET_EVBIT, EV_SYN);
-
-	for (i = 0; key_map[i].name != NULL; i++)
-		ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
-
-	if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
-		err = errno;
-		error("Can't create uinput device: %s (%d)",
-						strerror(err), err);
-		close(fd);
-		errno = err;
-		return -err;
-	}
-
-	return fd;
-}
-
-static void init_uinput(struct control *control)
-{
-	struct audio_device *dev = control->dev;
-	char address[18], name[248 + 1];
-
-	device_get_name(dev->btd_dev, name, sizeof(name));
-	if (g_str_equal(name, "Nokia CK-20W")) {
-		control->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
-		control->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
-		control->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
-		control->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
-	}
-
-	ba2str(&dev->dst, address);
-
-	control->uinput = uinput_create(address);
-	if (control->uinput < 0)
-		error("AVRCP: failed to init uinput for %s", address);
-	else
-		DBG("AVRCP: uinput initialized for %s", address);
 }
 
 static void media_info_init(struct media_info *mi)
@@ -1787,169 +1361,16 @@ static void media_info_init(struct media_info *mi)
 	mi->elapsed = 0xFFFFFFFF;
 }
 
-static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
-{
-	struct control *control = data;
-	char address[18];
-	uint16_t imtu;
-	GError *gerr = NULL;
-
-	if (err) {
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-		error("%s", err->message);
-		return;
-	}
-
-	bt_io_get(chan, BT_IO_L2CAP, &gerr,
-			BT_IO_OPT_DEST, &address,
-			BT_IO_OPT_IMTU, &imtu,
-			BT_IO_OPT_INVALID);
-	if (gerr) {
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-		error("%s", gerr->message);
-		g_error_free(gerr);
-		return;
-	}
-
-	DBG("AVCTP: connected to %s", address);
-
-	if (!control->io)
-		control->io = g_io_channel_ref(chan);
-
-	init_uinput(control);
-
-	avctp_set_state(control, AVCTP_STATE_CONNECTED);
-	control->mtu = imtu;
-	control->io_id = g_io_add_watch(chan,
-				G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
-				(GIOFunc) control_cb, control);
-}
-
-static void auth_cb(DBusError *derr, void *user_data)
-{
-	struct control *control = user_data;
-	GError *err = NULL;
-
-	if (control->io_id) {
-		g_source_remove(control->io_id);
-		control->io_id = 0;
-	}
-
-	if (derr && dbus_error_is_set(derr)) {
-		error("Access denied: %s", derr->message);
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-		return;
-	}
-
-	if (!bt_io_accept(control->io, avctp_connect_cb, control,
-								NULL, &err)) {
-		error("bt_io_accept: %s", err->message);
-		g_error_free(err);
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-	}
-}
-
-static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
-{
-	struct control *control = NULL;
-	struct audio_device *dev;
-	char address[18];
-	bdaddr_t src, dst;
-	GError *err = NULL;
-
-	bt_io_get(chan, BT_IO_L2CAP, &err,
-			BT_IO_OPT_SOURCE_BDADDR, &src,
-			BT_IO_OPT_DEST_BDADDR, &dst,
-			BT_IO_OPT_DEST, address,
-			BT_IO_OPT_INVALID);
-	if (err) {
-		error("%s", err->message);
-		g_error_free(err);
-		g_io_channel_shutdown(chan, TRUE, NULL);
-		return;
-	}
-
-	dev = manager_get_device(&src, &dst, TRUE);
-	if (!dev) {
-		error("Unable to get audio device object for %s", address);
-		goto drop;
-	}
-
-	if (!dev->control) {
-		btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
-		if (!dev->control)
-			goto drop;
-	}
-
-	control = dev->control;
-
-	if (control->io) {
-		error("Refusing unexpected connect from %s", address);
-		goto drop;
-	}
-
-	avctp_set_state(control, AVCTP_STATE_CONNECTING);
-	control->io = g_io_channel_ref(chan);
-
-	if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
-						auth_cb, dev->control) < 0)
-		goto drop;
-
-	control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
-							control_cb, control);
-	return;
-
-drop:
-	if (!control || !control->io)
-		g_io_channel_shutdown(chan, TRUE, NULL);
-	if (control)
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-}
-
-static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
-{
-	GError *err = NULL;
-	GIOChannel *io;
-
-	io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
-				NULL, &err,
-				BT_IO_OPT_SOURCE_BDADDR, src,
-				BT_IO_OPT_PSM, AVCTP_PSM,
-				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
-				BT_IO_OPT_MASTER, master,
-				BT_IO_OPT_INVALID);
-	if (!io) {
-		error("%s", err->message);
-		g_error_free(err);
-	}
-
-	return io;
-}
-
 gboolean avrcp_connect(struct audio_device *dev)
 {
 	struct control *control = dev->control;
-	GError *err = NULL;
-	GIOChannel *io;
 
-	if (control->state > AVCTP_STATE_DISCONNECTED)
+	if (control->session)
 		return TRUE;
 
-	avctp_set_state(control, AVCTP_STATE_CONNECTING);
-
-	io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err,
-				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
-				BT_IO_OPT_DEST_BDADDR, &dev->dst,
-				BT_IO_OPT_PSM, AVCTP_PSM,
-				BT_IO_OPT_INVALID);
-	if (err) {
-		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
-		error("%s", err->message);
-		g_error_free(err);
+	control->session = avctp_connect(&dev->src, &dev->dst);
+	if (!control->session)
 		return FALSE;
-	}
-
-	control->io = io;
 
 	return TRUE;
 }
@@ -1958,10 +1379,10 @@ void avrcp_disconnect(struct audio_device *dev)
 {
 	struct control *control = dev->control;
 
-	if (!(control && control->io))
+	if (!(control && control->session))
 		return;
 
-	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+	avctp_disconnect(control->session);
 }
 
 int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
@@ -1969,7 +1390,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
 	sdp_record_t *record;
 	gboolean tmp, master = TRUE;
 	GError *err = NULL;
-	struct avctp_server *server;
+	struct avrcp_server *server;
 
 	if (config) {
 		tmp = g_key_file_get_boolean(config, "General",
@@ -1981,7 +1402,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
 			master = tmp;
 	}
 
-	server = g_new0(struct avctp_server, 1);
+	server = g_new0(struct avrcp_server, 1);
 	if (!server)
 		return -ENOMEM;
 
@@ -2018,8 +1439,7 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
 	}
 	server->ct_record_id = record->handle;
 
-	server->io = avctp_server_socket(src, master);
-	if (!server->io) {
+	if (avctp_register(src, master) < 0) {
 		remove_record_from_server(server->ct_record_id);
 		remove_record_from_server(server->tg_record_id);
 		g_free(server);
@@ -2033,10 +1453,10 @@ int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
 	return 0;
 }
 
-static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
 {
 	for (; list; list = list->next) {
-		struct avctp_server *server = list->data;
+		struct avrcp_server *server = list->data;
 
 		if (bacmp(&server->src, src) == 0)
 			return server;
@@ -2047,7 +1467,7 @@ static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
 
 void avrcp_unregister(const bdaddr_t *src)
 {
-	struct avctp_server *server;
+	struct avrcp_server *server;
 
 	server = find_server(servers, src);
 	if (!server)
@@ -2058,13 +1478,15 @@ void avrcp_unregister(const bdaddr_t *src)
 	remove_record_from_server(server->ct_record_id);
 	remove_record_from_server(server->tg_record_id);
 
-	g_io_channel_shutdown(server->io, TRUE, NULL);
-	g_io_channel_unref(server->io);
+	avctp_unregister(&server->src);
 	g_free(server);
 
 	if (servers)
 		return;
 
+	if (avctp_id)
+		avctp_remove_state_cb(avctp_id);
+
 	dbus_connection_unref(connection);
 	connection = NULL;
 }
@@ -2082,7 +1504,7 @@ static DBusMessage *control_is_connected(DBusConnection *conn,
 	if (!reply)
 		return NULL;
 
-	connected = (control->state == AVCTP_STATE_CONNECTED);
+	connected = (control->session != NULL);
 
 	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
 					DBUS_TYPE_INVALID);
@@ -2090,42 +1512,6 @@ static DBusMessage *control_is_connected(DBusConnection *conn,
 	return reply;
 }
 
-static int avctp_send_passthrough(struct control *control, uint8_t op)
-{
-	unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
-	struct avctp_header *avctp = (void *) buf;
-	struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
-	uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
-	int sk = g_io_channel_unix_get_fd(control->io);
-	static uint8_t transaction = 0;
-
-	memset(buf, 0, sizeof(buf));
-
-	avctp->transaction = transaction++;
-	avctp->packet_type = AVCTP_PACKET_SINGLE;
-	avctp->cr = AVCTP_COMMAND;
-	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
-
-	avc->code = CTYPE_CONTROL;
-	avc->subunit_type = SUBUNIT_PANEL;
-	avc->opcode = OP_PASSTHROUGH;
-
-	operands[0] = op & 0x7f;
-	operands[1] = 0;
-
-	if (write(sk, buf, sizeof(buf)) < 0)
-		return -errno;
-
-	/* Button release */
-	avctp->transaction = transaction++;
-	operands[0] |= 0x80;
-
-	if (write(sk, buf, sizeof(buf)) < 0)
-		return -errno;
-
-	return 0;
-}
-
 static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
 								void *data)
 {
@@ -2133,13 +1519,13 @@ static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
 	struct control *control = device->control;
 	int err;
 
-	if (control->state != AVCTP_STATE_CONNECTED)
+	if (!control->session)
 		return btd_error_not_connected(msg);
 
 	if (!control->target)
 		return btd_error_not_supported(msg);
 
-	err = avctp_send_passthrough(control, VOL_UP_OP);
+	err = avctp_send_passthrough(control->session, VOL_UP_OP);
 	if (err < 0)
 		return btd_error_failed(msg, strerror(-err));
 
@@ -2153,13 +1539,13 @@ static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
 	struct control *control = device->control;
 	int err;
 
-	if (control->state != AVCTP_STATE_CONNECTED)
+	if (!control->session)
 		return btd_error_not_connected(msg);
 
 	if (!control->target)
 		return btd_error_not_supported(msg);
 
-	err = avctp_send_passthrough(control, VOL_DOWN_OP);
+	err = avctp_send_passthrough(control->session, VOL_DOWN_OP);
 	if (err < 0)
 		return btd_error_failed(msg, strerror(-err));
 
@@ -2187,7 +1573,7 @@ static DBusMessage *control_get_properties(DBusConnection *conn,
 			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
 
 	/* Connected */
-	value = (device->control->state == AVCTP_STATE_CONNECTED);
+	value = (device->control->session != NULL);
 	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
 
 	dbus_message_iter_close_container(&iter, &dict);
@@ -2404,8 +1790,8 @@ static void path_unregister(void *data)
 	DBG("Unregistered interface %s on path %s",
 		AUDIO_CONTROL_INTERFACE, dev->path);
 
-	if (control->state != AVCTP_STATE_DISCONNECTED)
-		avctp_disconnected(dev);
+	if (control->session)
+		avctp_disconnect(control->session);
 
 	g_free(control);
 	dev->control = NULL;
@@ -2420,6 +1806,9 @@ static void mp_path_unregister(void *data)
 	DBG("Unregistered interface %s on path %s",
 		MEDIA_PLAYER_INTERFACE, dev->path);
 
+	if (mp->handler)
+		avctp_unregister_pdu_handler(mp->handler);
+
 	g_timer_destroy(mp->timer);
 	g_free(mp);
 	control->mp = NULL;
@@ -2494,11 +1883,12 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16,
 
 	control = g_new0(struct control, 1);
 	control->dev = dev;
-	control->state = AVCTP_STATE_DISCONNECTED;
-	control->uinput = -1;
 
 	control_update(control, uuid16, media_player);
 
+	if (!avctp_id)
+		avctp_id = avctp_add_state_cb(state_changed, NULL);
+
 	return control;
 }
 
@@ -2506,39 +1896,8 @@ gboolean control_is_active(struct audio_device *dev)
 {
 	struct control *control = dev->control;
 
-	if (control && control->state != AVCTP_STATE_DISCONNECTED)
+	if (control && control->session)
 		return TRUE;
 
 	return FALSE;
 }
-
-unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
-{
-	struct avctp_state_callback *state_cb;
-	static unsigned int id = 0;
-
-	state_cb = g_new(struct avctp_state_callback, 1);
-	state_cb->cb = cb;
-	state_cb->user_data = user_data;
-	state_cb->id = ++id;
-
-	avctp_callbacks = g_slist_append(avctp_callbacks, state_cb);
-
-	return state_cb->id;
-}
-
-gboolean avctp_remove_state_cb(unsigned int id)
-{
-	GSList *l;
-
-	for (l = avctp_callbacks; l != NULL; l = l->next) {
-		struct avctp_state_callback *cb = l->data;
-		if (cb && cb->id == id) {
-			avctp_callbacks = g_slist_remove(avctp_callbacks, cb);
-			g_free(cb);
-			return TRUE;
-		}
-	}
-
-	return FALSE;
-}
diff --git a/audio/control.h b/audio/control.h
index 77e7595..f5cfef2 100644
--- a/audio/control.h
+++ b/audio/control.h
@@ -25,20 +25,6 @@
 #define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
 #define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"
 
-typedef enum {
-	AVCTP_STATE_DISCONNECTED = 0,
-	AVCTP_STATE_CONNECTING,
-	AVCTP_STATE_CONNECTED
-} avctp_state_t;
-
-typedef void (*avctp_state_cb) (struct audio_device *dev,
-				avctp_state_t old_state,
-				avctp_state_t new_state,
-				void *user_data);
-
-unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
-gboolean avctp_remove_state_cb(unsigned int id);
-
 int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
 void avrcp_unregister(const bdaddr_t *src);
 
diff --git a/audio/device.c b/audio/device.c
index ea268bc..16f0701 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -52,6 +52,7 @@
 #include "unix.h"
 #include "avdtp.h"
 #include "control.h"
+#include "avctp.h"
 #include "headset.h"
 #include "gateway.h"
 #include "sink.h"
-- 
1.7.6.1

--
To unsubscribe from this list: send the line "unsubscribe linux-bluetooth" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Bluez Devel]     [Linux Wireless Networking]     [Linux Wireless Personal Area Networking]     [Linux ATH6KL]     [Linux USB Devel]     [Linux Media Drivers]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Big List of Linux Books]

  Powered by Linux