summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2007-10-23 07:54:36 +0000
committerJohan Hedberg <johan.hedberg@nokia.com>2007-10-23 07:54:36 +0000
commit0ad3f2251089e00a57b6aa6def396e24f30ab1e4 (patch)
tree378fa05c2215381f82271ebec4f45762ec971665
parentb636ccf2c9c5f5c7419c49c556b118930012d2e7 (diff)
Integrate AVRCP further with the rest of the audio service
-rw-r--r--audio/avdtp.c21
-rw-r--r--audio/control.c277
-rw-r--r--audio/control.h11
-rw-r--r--audio/device.c9
-rw-r--r--audio/manager.c37
5 files changed, 342 insertions, 13 deletions
diff --git a/audio/avdtp.c b/audio/avdtp.c
index 8e2094bf..222c1dea 100644
--- a/audio/avdtp.c
+++ b/audio/avdtp.c
@@ -43,6 +43,7 @@
#include "device.h"
#include "manager.h"
+#include "control.h"
#include "avdtp.h"
#include <bluetooth/l2cap.h>
@@ -731,6 +732,13 @@ static void release_stream(struct avdtp_stream *stream, struct avdtp *session)
static void connection_lost(struct avdtp *session, int err)
{
+ struct device *dev;
+
+ dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE,
+ FALSE);
+ if (dev)
+ avrcp_disconnect(dev);
+
if (session->state == AVDTP_SESSION_STATE_CONNECTED) {
char address[18];
@@ -1550,6 +1558,8 @@ static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond,
}
if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+ struct device *dev;
+
session->mtu = l2o.imtu;
session->buf = g_malloc0(session->mtu);
session->state = AVDTP_SESSION_STATE_CONNECTED;
@@ -1557,6 +1567,11 @@ static gboolean l2cap_connect_cb(GIOChannel *chan, GIOCondition cond,
G_IO_IN | G_IO_ERR | G_IO_HUP
| G_IO_NVAL,
(GIOFunc) session_cb, session);
+
+ dev = manager_find_device(&session->dst,
+ AUDIO_CONTROL_INTERFACE, FALSE);
+ if (dev)
+ avrcp_connect(dev);
}
else if (session->pending_open)
handle_transport_connect(session, sk, l2o.imtu);
@@ -2682,6 +2697,7 @@ static void auth_cb(DBusPendingCall *call, void *data)
struct avdtp *session = data;
DBusMessage *reply = dbus_pending_call_steal_reply(call);
DBusError err;
+ struct device *dev;
dbus_pending_call_unref(session->pending_auth);
session->pending_auth = NULL;
@@ -2712,6 +2728,11 @@ static void auth_cb(DBusPendingCall *call, void *data)
session->state = AVDTP_SESSION_STATE_CONNECTED;
+ dev = manager_find_device(&session->dst, AUDIO_CONTROL_INTERFACE,
+ FALSE);
+ if (dev)
+ avrcp_connect(dev);
+
g_source_remove(session->io);
io = g_io_channel_unix_new(session->sock);
diff --git a/audio/control.c b/audio/control.c
index c61a79dd..83f48df4 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -41,6 +41,8 @@
#include <bluetooth/sdp_lib.h>
#include <bluetooth/l2cap.h>
+#include "dbus.h"
+#include "dbus-helper.h"
#include "logging.h"
#include "device.h"
#include "manager.h"
@@ -58,6 +60,12 @@ static GIOChannel *avctp_server = NULL;
static GSList *sessions = NULL;
+typedef enum {
+ AVCTP_STATE_DISCONNECTED = 0,
+ AVCTP_STATE_CONNECTING,
+ AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
#if __BYTE_ORDER == __LITTLE_ENDIAN
struct avctp_header {
@@ -99,6 +107,10 @@ struct avrcp_header {
#endif
struct avctp {
+ struct device *dev;
+
+ avctp_state_t state;
+
bdaddr_t src;
bdaddr_t dst;
@@ -111,6 +123,11 @@ struct avctp {
DBusPendingCall *pending_auth;
};
+struct control {
+ struct avctp *session;
+};
+
+
static int avrcp_ct_record(sdp_buf_t *buf)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
@@ -314,10 +331,25 @@ static struct avctp *find_session(bdaddr_t *src, bdaddr_t *dst)
static void avctp_unref(struct avctp *session)
{
sessions = g_slist_remove(sessions, session);
+
+ if (session->pending_auth) {
+ manager_cancel_authorize(&session->dst, AVRCP_TARGET_UUID,
+ NULL);
+ dbus_pending_call_cancel(session->pending_auth);
+ dbus_pending_call_unref(session->pending_auth);
+ }
+
+ if (session->state == AVCTP_STATE_CONNECTED)
+ dbus_connection_emit_signal(session->dev->conn,
+ session->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
if (session->sock >= 0)
close(session->sock);
if (session->io)
g_source_remove(session->io);
+ session->dev->control->session = NULL;
g_free(session);
}
@@ -427,6 +459,16 @@ static void auth_cb(DBusPendingCall *call, void *data)
return;
}
+ session->state = AVCTP_STATE_CONNECTED;
+
+ session->dev = manager_device_connected(&session->dst,
+ AVRCP_TARGET_UUID);
+ session->dev->control->session = session;
+
+ dbus_connection_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
g_source_remove(session->io);
io = g_io_channel_unix_new(session->sock);
@@ -498,6 +540,8 @@ static gboolean avctp_server_cb(GIOChannel *chan, GIOCondition cond, void *data)
return TRUE;
}
+ session->state = AVCTP_STATE_CONNECTING;
+
if (avdtp_is_connected(&src, &dst))
goto proceed;
@@ -513,15 +557,176 @@ proceed:
session->sock = cli_sk;
io = g_io_channel_unix_new(session->sock);
- if (!session->pending_auth)
+ if (!session->pending_auth) {
+ session->state = AVCTP_STATE_CONNECTED;
+ session->dev = manager_device_connected(&dst,
+ AVRCP_TARGET_UUID);
+ session->dev->control->session = session;
flags |= G_IO_IN;
+ dbus_connection_emit_signal(session->dev->conn,
+ session->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ }
+
session->io = g_io_add_watch(io, flags, (GIOFunc) session_cb, session);
g_io_channel_unref(io);
return TRUE;
}
-int control_init(DBusConnection *conn)
+static gboolean avctp_connect_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ struct l2cap_options l2o;
+ socklen_t len;
+ int ret, err, sk;
+ char address[18];
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ ba2str(&session->dst, address);
+
+ len = sizeof(ret);
+ if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) {
+ err = errno;
+ error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err);
+ goto failed;
+ }
+
+ if (ret != 0) {
+ err = ret;
+ error("AVCTP connect(%s): %s (%d)", address, strerror(err),
+ err);
+ goto failed;
+ }
+
+ if (cond & G_IO_HUP) {
+ err = EIO;
+ goto failed;
+ }
+
+ debug("AVCTP: connected to %s", address);
+
+ memset(&l2o, 0, sizeof(l2o));
+ len = sizeof(l2o);
+ if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &l2o,
+ &len) < 0) {
+ err = errno;
+ error("getsockopt(L2CAP_OPTIONS): %s (%d)", strerror(err),
+ err);
+ goto failed;
+ }
+
+ dbus_connection_emit_signal(session->dev->conn, session->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_INVALID);
+
+ session->state = AVCTP_STATE_CONNECTED;
+
+ session->mtu = l2o.imtu;
+ session->io = g_io_add_watch(chan,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) session_cb, session);
+ return FALSE;
+
+failed:
+ close(sk);
+
+ avctp_unref(session);
+
+ return FALSE;
+}
+
+gboolean avrcp_connect(struct device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session;
+ struct sockaddr_l2 l2a;
+ GIOChannel *io;
+ int sk;
+
+ if (control->session)
+ return TRUE;
+
+ session = avctp_get(&dev->src, &dev->dst);
+ session->dev = dev;
+
+ memset(&l2a, 0, sizeof(l2a));
+ l2a.l2_family = AF_BLUETOOTH;
+ bacpy(&l2a.l2_bdaddr, &dev->src);
+
+ sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (sk < 0) {
+ error("Cannot create L2CAP socket. %s(%d)", strerror(errno),
+ errno);
+ goto failed;
+ }
+
+ if (bind(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) {
+ error("Bind failed. %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ memset(&l2a, 0, sizeof(l2a));
+ l2a.l2_family = AF_BLUETOOTH;
+ bacpy(&l2a.l2_bdaddr, &dev->dst);
+ l2a.l2_psm = htobs(AVCTP_PSM);
+
+ if (set_nonblocking(sk) < 0) {
+ error("Set non blocking: %s (%d)", strerror(errno), errno);
+ goto failed;
+ }
+
+ io = g_io_channel_unix_new(sk);
+ g_io_channel_set_close_on_unref(io, FALSE);
+ session->sock = sk;
+
+ if (connect(sk, (struct sockaddr *) &l2a, sizeof(l2a)) < 0) {
+ if (!(errno == EAGAIN || errno == EINPROGRESS)) {
+ error("Connect failed. %s(%d)", strerror(errno),
+ errno);
+ g_io_channel_close(io);
+ g_io_channel_unref(io);
+ goto failed;
+ }
+
+ session->state = AVCTP_STATE_CONNECTING;
+
+ g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ (GIOFunc) avctp_connect_cb, session);
+ } else
+ avctp_connect_cb(io, G_IO_OUT, session);
+
+ g_io_channel_unref(io);
+
+ control->session = session;
+
+ return TRUE;
+
+failed:
+ avctp_unref(session);
+ return FALSE;
+}
+
+void avrcp_disconnect(struct device *dev)
+{
+ struct control *control = dev->control;
+ struct avctp *session = control->session;
+
+ if (!session)
+ return;
+
+ avctp_unref(session);
+ control->session = NULL;
+}
+
+int avrcp_init(DBusConnection *conn)
{
sdp_buf_t buf;
@@ -566,7 +771,7 @@ int control_init(DBusConnection *conn)
return 0;
}
-void control_exit(void)
+void avrcp_exit(void)
{
if (!avctp_server)
return;
@@ -585,3 +790,69 @@ void control_exit(void)
connection = NULL;
}
+static DBusHandlerResult control_is_connected(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct device *device = data;
+ struct control *control = device->control;
+ DBusMessage *reply;
+ dbus_bool_t connected;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ connected = (control->session != NULL);
+
+ dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+ DBUS_TYPE_INVALID);
+
+ send_message_and_unref(conn, reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusMethodVTable control_methods[] = {
+ { "IsConnected", control_is_connected, "", "b" },
+ { NULL, NULL, NULL, NULL }
+};
+
+static DBusSignalVTable control_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { NULL, NULL }
+};
+
+struct control *control_init(struct device *dev)
+{
+ if (!dbus_connection_register_interface(dev->conn, dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ control_methods,
+ control_signals, NULL))
+ return NULL;
+
+ return g_new0(struct control, 1);
+}
+
+void control_free(struct device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session)
+ avctp_unref(control->session);
+
+ g_free(control);
+ dev->control = NULL;
+}
+
+gboolean control_is_active(struct device *dev)
+{
+ struct control *control = dev->control;
+
+ if (control->session &&
+ control->session->state != AVCTP_STATE_DISCONNECTED)
+ return TRUE;
+
+ return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
index 79d66ec7..aa183e02 100644
--- a/audio/control.h
+++ b/audio/control.h
@@ -23,5 +23,12 @@
#define AUDIO_CONTROL_INTERFACE "org.bluez.audio.Control"
-int control_init(DBusConnection *conn);
-void control_exit(void);
+int avrcp_init(DBusConnection *conn);
+void avrcp_exit(void);
+
+gboolean avrcp_connect(struct device *dev);
+void avrcp_disconnect(struct device *dev);
+
+struct control *control_init(struct device *dev);
+void control_free(struct device *dev);
+gboolean control_is_active(struct device *dev);
diff --git a/audio/device.c b/audio/device.c
index 9b76e019..a2047b9e 100644
--- a/audio/device.c
+++ b/audio/device.c
@@ -49,6 +49,7 @@
#include "ipc.h"
#include "device.h"
#include "avdtp.h"
+#include "control.h"
#include "headset.h"
#include "sink.h"
@@ -185,6 +186,9 @@ static void device_free(struct device *dev)
if (dev->sink)
sink_free(dev);
+ if (dev->control)
+ control_free(dev);
+
if (dev->conn)
dbus_connection_unref(dev->conn);
@@ -486,6 +490,8 @@ uint8_t device_get_state(struct device *dev)
hs_state = headset_get_state(dev);
return hs_to_ipc_state(hs_state);
}
+ else if (dev->control && control_is_active(dev))
+ return STATE_CONNECTED;
return STATE_DISCONNECTED;
}
@@ -509,6 +515,9 @@ gboolean device_is_connected(struct device *dev, const char *interface)
else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&
headset_is_active(dev))
return TRUE;
+ else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->headset &&
+ control_is_active(dev))
+ return TRUE;
return FALSE;
}
diff --git a/audio/manager.c b/audio/manager.c
index 2b628544..74588c78 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -279,9 +279,17 @@ static void handle_record(sdp_record_t *record, struct device *device)
break;
case AV_REMOTE_SVCLASS_ID:
debug("Found AV Remote");
+ if (device->control == NULL)
+ device->control = control_init(device);
+ if (device->sink && sink_is_active(device))
+ avrcp_connect(device);
break;
case AV_REMOTE_TARGET_SVCLASS_ID:
debug("Found AV Target");
+ if (device->control == NULL)
+ device->control = control_init(device);
+ if (device->sink && sink_is_active(device))
+ avrcp_connect(device);
break;
default:
debug("Unrecognized UUID: 0x%04X", uuid16);
@@ -639,8 +647,7 @@ struct device *manager_device_connected(bdaddr_t *bda, const char *uuid)
return NULL;
headset = TRUE;
- }
- else if (!strcmp(uuid, A2DP_SOURCE_UUID)) {
+ } else if (!strcmp(uuid, A2DP_SOURCE_UUID)) {
if (device->sink)
return device;
@@ -648,6 +655,14 @@ struct device *manager_device_connected(bdaddr_t *bda, const char *uuid)
if (!device->sink)
return NULL;
+ } else if (!strcmp(uuid, AVRCP_TARGET_UUID)) {
+ if (device->control)
+ return device;
+
+ device->control = control_init(device);
+
+ if (!device->control)
+ return NULL;
} else
return NULL;
@@ -1050,10 +1065,12 @@ static void parse_stored_devices(char *key, char *value, void *data)
/* Change storage to source adapter */
bacpy(&device->store, src);
- if (strstr(value, "headset"))
+ if (enabled->headset && strstr(value, "headset"))
device->headset = headset_init(device, NULL, 0);
- if (strstr(value, "sink"))
+ if (enabled->sink && strstr(value, "sink"))
device->sink = sink_init(device);
+ if (enabled->control && strstr(value, "control"))
+ device->control = control_init(device);
add_device(device, FALSE);
}
@@ -1621,7 +1638,7 @@ int audio_init(DBusConnection *conn, struct enabled_interfaces *enable,
if (a2dp_init(conn, sources, sinks) < 0)
goto failed;
- if (enable->control && control_init(conn) < 0)
+ if (enable->control && avrcp_init(conn) < 0)
goto failed;
if (!dbus_connection_register_interface(conn, AUDIO_MANAGER_PATH,
@@ -1761,15 +1778,19 @@ struct device *manager_find_device(bdaddr_t *bda, const char *interface,
continue;
if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface)
- && !dev->headset)
+ && !dev->headset)
continue;
if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface)
- && !dev->sink)
+ && !dev->sink)
continue;
if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface)
- && !dev->source)
+ && !dev->source)
+ continue;
+
+ if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface)
+ && !dev->control)
continue;
if (connected && !device_is_connected(dev, interface))