summaryrefslogtreecommitdiffstats
path: root/audio/sink.c
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@nokia.com>2007-08-11 11:05:24 +0000
committerJohan Hedberg <johan.hedberg@nokia.com>2007-08-11 11:05:24 +0000
commit6763ebb3c231740c66a235f94d56e8d8cc213d90 (patch)
tree527ad7a778289b70ac64b2d4e49512eae6d634e2 /audio/sink.c
parent46e860574f3d6d70d961e38270522764191cea20 (diff)
Integrate A2DP work from Johan's and Luiz's GIT trees
Diffstat (limited to 'audio/sink.c')
-rw-r--r--audio/sink.c429
1 files changed, 429 insertions, 0 deletions
diff --git a/audio/sink.c b/audio/sink.c
index 436dfbce..f7e32647 100644
--- a/audio/sink.c
+++ b/audio/sink.c
@@ -25,4 +25,433 @@
#include <config.h>
#endif
+#include <errno.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "dbus.h"
+#include "dbus-helper.h"
+#include "logging.h"
+
#include "avdtp.h"
+#include "device.h"
+#include "a2dp.h"
+#include "error.h"
+#include "unix.h"
+
+struct pending_connect {
+ DBusMessage *msg;
+ struct ipc_packet *pkt;
+ int pkt_len;
+ int sock;
+};
+
+struct sink {
+ struct avdtp *session;
+ struct avdtp_stream *stream;
+ uint8_t state;
+ struct pending_connect *c;
+ DBusConnection *conn;
+};
+
+static void pending_connect_free(struct pending_connect *c)
+{
+ if (c->pkt)
+ g_free(c->pkt);
+ if (c->msg)
+ dbus_message_unref(c->msg);
+ g_free(c);
+}
+
+void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err, void *user_data)
+{
+ struct device *dev = user_data;
+ struct sink *sink = dev->sink;
+ struct pending_connect *c = NULL;
+ DBusMessage *reply;
+ int cmd_err;
+
+ if (err)
+ goto failed;
+
+ switch (new_state) {
+ case AVDTP_STATE_IDLE:
+ dbus_connection_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Disconnected",
+ DBUS_TYPE_INVALID);
+ if (sink->session) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ }
+ c = sink->c;
+ break;
+ case AVDTP_STATE_CONFIGURED:
+ cmd_err = avdtp_open(sink->session, stream);
+ if (cmd_err < 0) {
+ error("Error on avdtp_open %s (%d)", strerror(-cmd_err),
+ cmd_err);
+ goto failed;
+ }
+ break;
+ case AVDTP_STATE_OPEN:
+ if (old_state == AVDTP_STATE_CONFIGURED)
+ dbus_connection_emit_signal(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ "Connected",
+ DBUS_TYPE_INVALID);
+ if (sink->c && sink->c->pkt) {
+ cmd_err = avdtp_start(sink->session, stream);
+ if (cmd_err < 0) {
+ error("Error on avdtp_start %s (%d)",
+ strerror(-cmd_err), cmd_err);
+ goto failed;
+ }
+ }
+ else
+ c = sink->c;
+ break;
+ case AVDTP_STATE_STREAMING:
+ c = sink->c;
+ break;
+ case AVDTP_STATE_CLOSING:
+ break;
+ case AVDTP_STATE_ABORTING:
+ break;
+ }
+
+ sink->state = new_state;
+
+ if (c) {
+ if (c->msg) {
+ reply = dbus_message_new_method_return(c->msg);
+ send_message_and_unref(dev->conn, reply);
+ }
+ if (c->pkt) {
+ struct ipc_data_cfg *rsp;
+ int ret;
+
+ ret = sink_get_config(dev, c->sock, c->pkt,
+ c->pkt_len, &rsp);
+ if (ret == 0) {
+ unix_send_cfg(c->sock, rsp);
+ g_free(rsp);
+ }
+ else
+ unix_send_cfg(c->sock, NULL);
+ }
+
+ pending_connect_free(c);
+ sink->c = NULL;
+ }
+
+ return;
+
+failed:
+ if (sink->c) {
+ if (sink->c->msg && err)
+ err_failed(dev->conn, sink->c->msg,
+ avdtp_strerror(err));
+
+ pending_connect_free(sink->c);
+ sink->c = NULL;
+ }
+
+ if (new_state == AVDTP_STATE_IDLE) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ }
+}
+
+
+
+static void discovery_complete(struct avdtp *session, GSList *seps, int err,
+ void *user_data)
+{
+ struct device *dev = user_data;
+ struct sink *sink = dev->sink;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_remote_sep *rsep;
+ GSList *caps = NULL;
+ const char *err_str = NULL;
+
+ if (err < 0) {
+ error("Discovery failed");
+ err_str = strerror(-err);
+ goto failed;
+ }
+
+ debug("Discovery complete");
+
+ if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO,
+ A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
+ err_str = "No matching ACP and INT SEPs found";
+ goto failed;
+ }
+
+ if (!a2dp_select_capabilities(rsep, &caps)) {
+ err_str = "Unable to select remote SEP capabilities";
+ goto failed;
+ }
+
+ err = avdtp_set_configuration(session, rsep, lsep, caps,
+ &sink->stream);
+ if (err < 0) {
+ error("avdtp_set_configuration: %s", strerror(-err));
+ err_str = "Unable to set configuration";
+ goto failed;
+ }
+
+ avdtp_stream_set_cb(session, sink->stream, stream_state_changed, dev);
+
+ return;
+
+failed:
+ error("%s", err_str);
+ if (sink->c) {
+ if (sink->c->msg)
+ err_failed(dev->conn, sink->c->msg, err_str);
+ pending_connect_free(sink->c);
+ sink->c = NULL;
+ }
+ if (sink->session) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ }
+}
+
+static DBusHandlerResult sink_connect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *dev = data;
+ struct sink *sink = dev->sink;
+ struct pending_connect *c;
+ int err;
+
+ if (!sink->session)
+ sink->session = avdtp_get(&dev->src, &dev->dst);
+
+ if (sink->c)
+ return err_connect_failed(conn, msg, "Connect in progress");
+
+ if (sink->state >= AVDTP_STATE_OPEN)
+ return err_already_connected(conn, msg);
+
+ c = g_new0(struct pending_connect, 1);
+ c->msg = dbus_message_ref(msg);
+ sink->c = c;
+
+ err = avdtp_discover(sink->session, discovery_complete, data);
+ if (err < 0) {
+ dbus_message_unref(c->msg);
+ pending_connect_free(c);
+ sink->c = NULL;
+ return err_connect_failed(conn, msg, strerror(err));
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult sink_disconnect(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct device *device = data;
+ struct sink *sink = device->sink;
+ struct pending_connect *c;
+ int err;
+
+ if (!sink->session)
+ return err_not_connected(conn, msg);
+
+ if (sink->c)
+ return err_failed(conn, msg, strerror(EBUSY));
+
+ if (sink->state < AVDTP_STATE_OPEN) {
+ avdtp_unref(sink->session);
+ sink->session = NULL;
+ } else {
+ err = avdtp_close(sink->session, sink->stream);
+ if (err < 0)
+ return err_failed(conn, msg, strerror(-err));
+ }
+
+ c = g_new0(struct pending_connect, 1);
+ c->msg = dbus_message_ref(msg);
+ sink->c = c;
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult sink_is_connected(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct device *device = data;
+ struct sink *sink = device->sink;
+ DBusMessage *reply;
+ dbus_bool_t connected;
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ connected = (sink->state != AVDTP_STATE_IDLE);
+
+ 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 sink_methods[] = {
+ { "Connect", sink_connect, "", "" },
+ { "Disconnect", sink_disconnect, "", "" },
+ { "IsConnected", sink_is_connected, "", "b" },
+ { NULL, NULL, NULL, NULL }
+};
+
+static DBusSignalVTable sink_signals[] = {
+ { "Connected", "" },
+ { "Disconnected", "" },
+ { NULL, NULL }
+};
+
+struct sink *sink_init(void *device)
+{
+ struct device *dev = device;
+
+ if (!dbus_connection_register_interface(dev->conn, dev->path,
+ AUDIO_SINK_INTERFACE,
+ sink_methods,
+ sink_signals, NULL))
+ return NULL;
+
+ return g_new0(struct sink, 1);
+}
+
+void sink_free(void *device)
+{
+ struct device *dev = device;
+ struct sink *sink = dev->sink;
+
+ if (sink->session)
+ avdtp_unref(sink->session);
+
+ if (sink->c)
+ pending_connect_free(sink->c);
+
+ g_free(sink);
+ dev->sink = NULL;
+}
+
+int sink_get_config(void *device, int sock, struct ipc_packet *req,
+ int pkt_len, struct ipc_data_cfg **rsp)
+{
+ struct device *dev = device;
+ struct sink *sink = dev->sink;
+ int err = EINVAL;
+ struct pending_connect *c = NULL;
+
+ if (sink->state == AVDTP_STATE_STREAMING)
+ goto proceed;
+
+ if (!sink->session)
+ sink->session = avdtp_get(&dev->src, &dev->dst);
+
+ c = g_new0(struct pending_connect, 1);
+ c->sock = sock;
+ c->pkt = g_malloc(pkt_len);
+ memcpy(c->pkt, req, pkt_len);
+ sink->c = c;
+
+ if (sink->state == AVDTP_STATE_IDLE)
+ err = avdtp_discover(sink->session, discovery_complete, device);
+ else if (sink->state < AVDTP_STATE_STREAMING)
+ err = avdtp_start(sink->session, sink->stream);
+ else
+ goto error;
+
+ if (err < 0)
+ goto error;
+
+ return 1;
+
+proceed:
+ if (!a2dp_get_config(sink->stream, rsp))
+ goto error;
+
+ return 0;
+
+error:
+ if (c)
+ pending_connect_free(c);
+ return -err;
+}
+
+gboolean sink_is_active(void *device)
+{
+ struct device *dev = device;
+ struct sink *sink = dev->sink;
+
+ if (sink->session)
+ return TRUE;
+
+ return FALSE;
+}
+
+void sink_set_state(void *device, avdtp_state_t state)
+{
+ struct device *dev = device;
+ struct sink *sink = dev->sink;
+ int err = 0;
+
+ if (sink->state == state)
+ return;
+
+ if (!sink->session || !sink->stream)
+ goto failed;
+
+ switch (sink->state) {
+ case AVDTP_STATE_OPEN:
+ if (state == AVDTP_STATE_STREAMING) {
+ err = avdtp_start(sink->session, sink->stream);
+ if (err == 0)
+ return;
+ }
+ else if (state == AVDTP_STATE_IDLE) {
+ err = avdtp_close(sink->session, sink->stream);
+ if (err == 0)
+ return;
+ }
+ break;
+ case AVDTP_STATE_STREAMING:
+ if (state == AVDTP_STATE_OPEN) {
+ err = avdtp_suspend(sink->session, sink->stream);
+ if (err == 0)
+ return;
+ }
+ else if (state == AVDTP_STATE_IDLE) {
+ err = avdtp_close(sink->session, sink->stream);
+ if (err == 0)
+ return;
+ }
+ break;
+ default:
+ goto failed;
+ }
+failed:
+ error("%s: Error changing states", dev->path);
+}
+
+avdtp_state_t sink_get_state(void *device)
+{
+ struct device *dev = device;
+ struct sink *sink = dev->sink;
+
+ return sink->state;
+}