Rework interfacing with the avdtp state machine

This commit is contained in:
Johan Hedberg 2007-08-16 15:42:10 +00:00
parent 3863eba35c
commit cdd9e2e17a
11 changed files with 739 additions and 368 deletions

View File

@ -42,16 +42,67 @@
#include "sink.h"
#include "a2dp.h"
#ifndef MIN
# define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif
#ifndef MAX
# define MAX(x, y) ((x) > (y) ? (x) : (y))
#endif
struct a2dp_sep {
struct avdtp_local_sep *sep;
struct avdtp_stream *stream;
struct device *used_by;
uint32_t record_id;
gboolean start_requested;
gboolean suspending;
gboolean starting;
};
struct a2dp_stream_cb {
a2dp_stream_cb_t cb;
void *user_data;
int id;
};
struct a2dp_stream_setup {
struct avdtp *session;
struct device *dev;
struct avdtp_stream *stream;
gboolean start;
gboolean canceled;
GSList *cb;
};
static DBusConnection *connection = NULL;
static uint32_t sink_record_id = 0;
static uint32_t source_record_id = 0;
static struct a2dp_sep sink = { NULL, NULL, 0 };
static struct a2dp_sep source = { NULL, NULL, 0 };
static struct avdtp_local_sep *sink_sep = NULL;
static struct avdtp_local_sep *source_sep = NULL;
static struct a2dp_stream_setup *setup = NULL;
static struct avdtp *start_session = NULL;
static struct avdtp_stream *start_stream = NULL;
static void stream_setup_free(struct a2dp_stream_setup *s)
{
if (s->session)
avdtp_unref(s->session);
g_slist_foreach(s->cb, (GFunc) g_free, NULL);
g_slist_free(s->cb);
g_free(s);
setup = NULL;
}
static void setup_callback(struct a2dp_stream_cb *cb,
struct a2dp_stream_setup *s)
{
cb->cb(s->session, s->dev, s->stream, cb->user_data);
}
static void finalize_stream_setup(struct a2dp_stream_setup *s)
{
g_slist_foreach(setup->cb, (GFunc) setup_callback, setup);
stream_setup_free(setup);
}
static gboolean setconf_ind(struct avdtp *session,
struct avdtp_local_sep *sep,
@ -62,7 +113,7 @@ static gboolean setconf_ind(struct avdtp *session,
struct device *dev;
bdaddr_t addr;
if (sep == sink_sep) {
if (sep == sink.sep) {
debug("SBC Sink: Set_Configuration_Ind");
return TRUE;
}
@ -78,6 +129,8 @@ static gboolean setconf_ind(struct avdtp *session,
return FALSE;
}
source.stream = stream;
sink_new_stream(dev, session, stream);
return TRUE;
@ -89,7 +142,7 @@ static gboolean getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_service_capability *media_transport, *media_codec;
struct sbc_codec_cap sbc_cap;
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Get_Capability_Ind");
else
debug("SBC Source: Get_Capability_Ind");
@ -140,16 +193,33 @@ static gboolean getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
int err;
if (sep == sink.sep) {
debug("SBC Sink: Set_Configuration_Cfm");
else
debug("SBC Source: Set_Configuration_Cfm");
return;
}
debug("SBC Source: Set_Configuration_Cfm");
source.stream = stream;
if (!setup)
return;
err = avdtp_open(session, stream);
if (err < 0) {
error("Error on avdtp_open %s (%d)", strerror(-err),
-err);
setup->stream = FALSE;
finalize_stream_setup(setup);
}
}
static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Get_Configuration_Ind");
else
debug("SBC Source: Get_Configuration_Ind");
@ -159,7 +229,7 @@ static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Set_Configuration_Cfm");
else
debug("SBC Source: Set_Configuration_Cfm");
@ -168,7 +238,7 @@ static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Open_Ind");
else
debug("SBC Source: Open_Ind");
@ -178,28 +248,35 @@ static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
int err;
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Open_Cfm");
else
debug("SBC Source: Open_Cfm");
if (session != start_session || stream != start_stream)
if (!setup)
return;
start_session = NULL;
start_stream = NULL;
if (setup->canceled) {
avdtp_close(session, stream);
stream_setup_free(setup);
return;
}
err = avdtp_start(session, stream);
if (err < 0)
error("Error on avdtp_start %s (%d)", strerror(-err), err);
if (setup->start) {
if (avdtp_start(session, stream) == 0)
return;
error("avdtp_start failed");
setup->stream = NULL;
}
finalize_stream_setup(setup);
}
static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Start_Ind");
else
debug("SBC Source: Start_Ind");
@ -213,16 +290,27 @@ static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Start_Cfm");
else
debug("SBC Source: Start_Cfm");
if (!setup)
return;
if (setup->canceled) {
avdtp_close(session, stream);
stream_setup_free(setup);
return;
}
finalize_stream_setup(setup);
}
static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Suspend_Ind");
else
debug("SBC Source: Suspend_Ind");
@ -232,45 +320,68 @@ static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
if (sep == sink.sep) {
debug("SBC Sink: Suspend_Cfm");
else
debug("SBC Source: Suspend_Cfm");
return;
}
debug("SBC Source: Suspend_Cfm");
source.suspending = FALSE;
if (source.start_requested) {
avdtp_start(session, stream);
source.start_requested = FALSE;
}
}
static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep) {
debug("SBC Sink: Close_Ind");
else
debug("SBC Source: Close_Ind");
return TRUE;
}
debug("SBC Source: Close_Ind");
source.stream = NULL;
return TRUE;
}
static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
if (sep == sink.sep) {
debug("SBC Sink: Close_Cfm");
else
debug("SBC Source: Close_Cfm");
return;
}
debug("SBC Source: Close_Cfm");
source.stream = NULL;
}
static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream, uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep) {
debug("SBC Sink: Abort_Ind");
else
debug("SBC Source: Abort_Ind");
return TRUE;
}
debug("SBC Source: Abort_Ind");
source.stream = NULL;
return TRUE;
}
static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
struct avdtp_stream *stream)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: Abort_Cfm");
else
debug("SBC Source: Abort_Cfm");
@ -279,7 +390,7 @@ static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
uint8_t *err)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: ReConfigure_Ind");
else
debug("SBC Source: ReConfigure_Ind");
@ -288,7 +399,7 @@ static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep)
{
if (sep == sink_sep)
if (sep == sink.sep)
debug("SBC Sink: ReConfigure_Cfm");
else
debug("SBC Source: ReConfigure_Cfm");
@ -400,10 +511,10 @@ int a2dp_init(DBusConnection *conn, gboolean enable_sink, gboolean enable_source
avdtp_init();
if (enable_sink) {
source_sep = avdtp_register_sep(AVDTP_SEP_TYPE_SOURCE,
AVDTP_MEDIA_TYPE_AUDIO,
&ind, &cfm);
if (source_sep == NULL)
source.sep = avdtp_register_sep(AVDTP_SEP_TYPE_SOURCE,
AVDTP_MEDIA_TYPE_AUDIO,
&ind, &cfm);
if (source.sep == NULL)
return -1;
if (a2dp_source_record(&buf) < 0) {
@ -411,19 +522,19 @@ int a2dp_init(DBusConnection *conn, gboolean enable_sink, gboolean enable_source
return -1;
}
source_record_id = add_service_record(conn, &buf);
source.record_id = add_service_record(conn, &buf);
free(buf.data);
if (!source_record_id) {
if (!source.record_id) {
error("Unable to register A2DP Source service record");
return -1;
}
}
if (enable_source) {
sink_sep = avdtp_register_sep(AVDTP_SEP_TYPE_SINK,
sink.sep = avdtp_register_sep(AVDTP_SEP_TYPE_SINK,
AVDTP_MEDIA_TYPE_AUDIO,
&ind, &cfm);
if (sink_sep == NULL)
if (sink.sep == NULL)
return -1;
if (a2dp_sink_record(&buf) < 0) {
@ -431,9 +542,9 @@ int a2dp_init(DBusConnection *conn, gboolean enable_sink, gboolean enable_source
return -1;
}
sink_record_id = add_service_record(conn, &buf);
sink.record_id = add_service_record(conn, &buf);
free(buf.data);
if (!sink_record_id) {
if (!sink.record_id) {
error("Unable to register A2DP Sink service record");
return -1;
}
@ -444,20 +555,24 @@ int a2dp_init(DBusConnection *conn, gboolean enable_sink, gboolean enable_source
void a2dp_exit()
{
if (sink_sep)
avdtp_unregister_sep(sink_sep);
if (source_sep)
avdtp_unregister_sep(source_sep);
if (source_record_id) {
remove_service_record(connection, source_record_id);
source_record_id = 0;
if (sink.sep) {
avdtp_unregister_sep(sink.sep);
sink.sep = NULL;
}
if (sink_record_id) {
remove_service_record(connection, sink_record_id);
sink_record_id = 0;
if (source.sep) {
avdtp_unregister_sep(source.sep);
source.sep = NULL;
}
if (source.record_id) {
remove_service_record(connection, source.record_id);
source.record_id = 0;
}
if (sink.record_id) {
remove_service_record(connection, sink.record_id);
sink.record_id = 0;
}
dbus_connection_unref(connection);
@ -561,7 +676,7 @@ static gboolean select_sbc_params(struct sbc_codec_cap *cap,
else if (supported->allocation_method & A2DP_ALLOCATION_SNR)
cap->allocation_method = A2DP_ALLOCATION_SNR;
min_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
min_bitpool = MAX(default_bitpool(cap->frequency, cap->channel_mode),
supported->min_bitpool);
max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode),
supported->max_bitpool);
@ -685,10 +800,223 @@ gboolean a2dp_get_config(struct avdtp_stream *stream,
return TRUE;
}
void a2dp_start_stream_when_opened(struct avdtp *session,
struct avdtp_stream *stream)
static void discovery_complete(struct avdtp *session, GSList *seps, int err,
void *user_data)
{
start_session = session;
start_stream = stream;
struct avdtp_local_sep *lsep;
struct avdtp_remote_sep *rsep;
GSList *caps = NULL;
if (err < 0) {
error("Discovery failed: %s (%d)", strerror(-err), -err);
setup->stream = NULL;
finalize_stream_setup(setup);
return;
}
debug("Discovery complete");
if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO,
A2DP_CODEC_SBC, &lsep, &rsep) < 0) {
error("No matching ACP and INT SEPs found");
finalize_stream_setup(setup);
return;
}
if (!a2dp_select_capabilities(rsep, &caps)) {
error("Unable to select remote SEP capabilities");
finalize_stream_setup(setup);
return;
}
err = avdtp_set_configuration(session, rsep, lsep, caps,
&setup->stream);
if (err < 0) {
error("avdtp_set_configuration: %s", strerror(-err));
finalize_stream_setup(setup);
return;
}
/* Notify sink.c of the new stream */
sink_new_stream(setup->dev, session, setup->stream);
}
gboolean a2dp_source_cancel_stream(int id)
{
struct a2dp_stream_cb *cb_data;
GSList *l;
if (!setup)
return FALSE;
for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
struct a2dp_stream_cb *cb = l->data;
if (cb->id == id) {
cb_data = cb;
break;
}
}
if (!cb_data)
return FALSE;
setup->cb = g_slist_remove(setup->cb, cb_data);
if (!setup->cb)
setup->canceled = TRUE;
return TRUE;
}
int a2dp_source_request_stream(struct avdtp *session, struct device *dev,
gboolean start, a2dp_stream_cb_t cb,
void *user_data)
{
struct a2dp_stream_cb *cb_data;
static unsigned int cb_id = 0;
cb_data = g_new(struct a2dp_stream_cb, 1);
cb_data->cb = cb;
cb_data->user_data = user_data;
cb_data->id = cb_id++;
if (setup) {
setup->canceled = FALSE;
setup->cb = g_slist_append(setup->cb, cb_data);
if (start)
setup->start = TRUE;
return cb_data->id;
}
setup = g_new0(struct a2dp_stream_setup, 1);
setup->session = avdtp_ref(session);
setup->dev = dev;
setup->cb = g_slist_append(setup->cb, cb_data);
setup->start = start;
setup->stream = source.stream;
switch (avdtp_sep_get_state(source.sep)) {
case AVDTP_STATE_IDLE:
if (avdtp_discover(session, discovery_complete, setup) < 0)
goto failed;
break;
case AVDTP_STATE_OPEN:
if (!start) {
g_idle_add((GSourceFunc) finalize_stream_setup, setup);
break;
}
if (source.starting)
break;
if (avdtp_start(session, source.stream) < 0)
goto failed;
break;
case AVDTP_STATE_STREAMING:
if (!start || !source.suspending) {
g_idle_add((GSourceFunc) finalize_stream_setup, setup);
return cb_data->id;
}
source.start_requested = TRUE;
break;
default:
goto failed;
}
return cb_data->id;
failed:
stream_setup_free(setup);
cb_id--;
return -1;
}
gboolean a2dp_source_lock(struct device *dev, struct avdtp *session)
{
if (source.used_by)
return FALSE;
source.used_by = dev;
return TRUE;
}
gboolean a2dp_source_unlock(struct device *dev, struct avdtp *session)
{
avdtp_state_t state;
if (!source.sep)
return FALSE;
if (source.used_by != dev)
return FALSE;
state = avdtp_sep_get_state(source.sep);
source.used_by = NULL;
if (!source.stream || state == AVDTP_STATE_IDLE)
return TRUE;
switch (state) {
case AVDTP_STATE_OPEN:
/* Set timer here */
break;
case AVDTP_STATE_STREAMING:
if (avdtp_suspend(session, source.stream) == 0)
source.suspending = TRUE;
break;
default:
break;
}
return TRUE;
}
gboolean a2dp_source_suspend(struct device *dev, struct avdtp *session)
{
avdtp_state_t state;
if (!source.sep)
return FALSE;
if (source.used_by != dev)
return FALSE;
state = avdtp_sep_get_state(source.sep);
if (!source.stream || state != AVDTP_STATE_STREAMING)
return TRUE;
if (avdtp_suspend(session, source.stream) == 0) {
source.suspending = TRUE;
return TRUE;
}
return FALSE;
}
gboolean a2dp_source_start_stream(struct device *dev, struct avdtp *session)
{
avdtp_state_t state;
if (!source.sep)
return FALSE;
if (source.used_by != dev)
return FALSE;
state = avdtp_sep_get_state(source.sep);
if (state < AVDTP_STATE_OPEN) {
error("a2dp_source_start_stream: no stream open");
return FALSE;
}
if (state == AVDTP_STATE_STREAMING)
return TRUE;
if (avdtp_start(session, source.stream) < 0)
return FALSE;
return TRUE;
}

View File

@ -58,6 +58,10 @@ struct sbc_codec_cap {
uint8_t max_bitpool;
} __attribute__ ((packed));
typedef void (*a2dp_stream_cb_t) (struct avdtp *session, struct device *dev,
struct avdtp_stream *stream,
void *user_data);
int a2dp_init(DBusConnection *conn, gboolean enable_sink,
gboolean enable_source);
void a2dp_exit(void);
@ -67,5 +71,13 @@ gboolean a2dp_select_capabilities(struct avdtp_remote_sep *rsep, GSList **caps);
gboolean a2dp_get_config(struct avdtp_stream *stream,
struct ipc_data_cfg **cfg, int *fd);
void a2dp_start_stream_when_opened(struct avdtp *session,
struct avdtp_stream *stream);
int a2dp_source_request_stream(struct avdtp *session, struct device *dev,
gboolean start, a2dp_stream_cb_t cb,
void *user_data);
gboolean a2dp_source_cancel_stream(int id);
gboolean a2dp_source_lock(struct device *dev, struct avdtp *session);
gboolean a2dp_source_unlock(struct device *dev, struct avdtp *session);
gboolean a2dp_source_suspend(struct device *dev, struct avdtp *session);
gboolean a2dp_source_start_stream(struct device *dev, struct avdtp *session);

View File

@ -1957,6 +1957,11 @@ struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst)
return avdtp_ref(session);
}
gboolean avdtp_is_connected(bdaddr_t *src, bdaddr_t *dst)
{
return find_session(src, dst) == NULL ? FALSE : TRUE;
}
gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
uint16_t *mtu, GSList **caps)
{
@ -2489,6 +2494,11 @@ const char *avdtp_strerror(struct avdtp_error *err)
}
}
avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep)
{
return sep->state;
}
void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst)
{
if (src)

View File

@ -155,6 +155,8 @@ struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst);
void avdtp_unref(struct avdtp *session);
struct avdtp *avdtp_ref(struct avdtp *session);
gboolean avdtp_is_connected(bdaddr_t *src, bdaddr_t *dst);
struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
void *data, int size);
@ -196,6 +198,8 @@ int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media,
int avdtp_unregister_sep(struct avdtp_local_sep *sep);
avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep);
const char *avdtp_strerror(struct avdtp_error *err);
void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst);

View File

@ -328,21 +328,7 @@ void device_finish_sdp_transaction(struct device *dev)
dbus_message_unref(reply);
}
int device_get_config(struct device *dev, int sock, struct ipc_packet *req,
int pkt_len, struct ipc_data_cfg **rsp, int *fd)
{
if (dev->sink && sink_is_active(dev))
return sink_get_config(dev, sock, req, pkt_len, rsp, fd);
else if (dev->headset && headset_is_active(dev))
return headset_get_config(dev, sock, req, pkt_len, rsp, fd);
else if (dev->sink)
return sink_get_config(dev, sock, req, pkt_len, rsp, fd);
else if (dev->headset)
return headset_get_config(dev, sock, req, pkt_len, rsp, fd);
return -EINVAL;
}
#if 0
static avdtp_state_t ipc_to_avdtp_state(uint8_t ipc_state)
{
switch (ipc_state) {
@ -379,14 +365,7 @@ static headset_state_t ipc_to_hs_state(uint8_t ipc_state)
return HEADSET_STATE_DISCONNECTED;
}
}
void device_set_state(struct device *dev, uint8_t state)
{
if (dev->sink && sink_is_active(dev))
sink_set_state(dev, ipc_to_avdtp_state(state));
else if (dev->headset && headset_is_active(dev))
headset_set_state(dev, ipc_to_hs_state(state));
}
#endif
static uint8_t avdtp_to_ipc_state(avdtp_state_t state)
{

View File

@ -75,9 +75,4 @@ int device_remove_stored(struct device *dev);
void device_finish_sdp_transaction(struct device *device);
int device_get_config(struct device *dev, int sock, struct ipc_packet *req,
int pkt_len, struct ipc_data_cfg **rsp, int *fd);
void device_set_state(struct device *dev, uint8_t state);
uint8_t device_get_state(struct device *dev);

View File

@ -100,6 +100,8 @@ struct headset {
int sp_gain;
int mic_gain;
gboolean locked;
};
static int rfcomm_connect(struct device *device, struct pending_connect *c);
@ -1570,3 +1572,34 @@ gboolean headset_is_active(struct device *dev)
return FALSE;
}
gboolean headset_lock(struct device *dev, void *data)
{
struct headset *hs = dev->headset;
if (hs->locked)
return FALSE;
hs->locked = TRUE;
return TRUE;
}
gboolean headset_unlock(struct device *dev, void *data)
{
struct headset *hs = dev->headset;
hs->locked = FALSE;
return TRUE;
}
gboolean headset_suspend(struct device *dev, void *data)
{
return TRUE;
}
gboolean headset_play(struct device *dev, void *data)
{
return TRUE;
}

View File

@ -68,3 +68,8 @@ void headset_set_state(struct device *dev, headset_state_t state);
int headset_get_channel(struct device *dev);
gboolean headset_is_active(struct device *dev);
gboolean headset_lock(struct device *dev, void *data);
gboolean headset_unlock(struct device *dev, void *data);
gboolean headset_suspend(struct device *dev, void *data);
gboolean headset_play(struct device *dev, void *data);

View File

@ -46,29 +46,29 @@
#include "sink.h"
struct pending_request {
DBusConnection *conn;
DBusMessage *msg;
struct ipc_packet *pkt;
int pkt_len;
int sock;
int id;
};
struct sink {
struct avdtp *session;
struct avdtp_stream *stream;
uint8_t state;
struct pending_request *c;
struct pending_request *connect;
struct pending_request *disconnect;
DBusConnection *conn;
gboolean initiator;
gboolean suspending;
};
static void pending_connect_free(struct pending_request *c)
static void pending_request_free(struct pending_request *pending)
{
if (c->pkt)
g_free(c->pkt);
if (c->msg)
dbus_message_unref(c->msg);
g_free(c);
if (pending->conn)
dbus_connection_unref(pending->conn);
if (pending->msg)
dbus_message_unref(pending->msg);
g_free(pending);
}
void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state,
@ -77,12 +77,9 @@ void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state,
{
struct device *dev = user_data;
struct sink *sink = dev->sink;
struct pending_request *c = NULL;
DBusMessage *reply;
int cmd_err;
if (err)
goto failed;
return;
switch (new_state) {
case AVDTP_STATE_IDLE:
@ -90,152 +87,64 @@ void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state,
AUDIO_SINK_INTERFACE,
"Disconnected",
DBUS_TYPE_INVALID);
if (sink->disconnect) {
DBusMessage *reply;
struct pending_request *p;
p = sink->disconnect;
sink->disconnect = NULL;
reply = dbus_message_new_method_return(p->msg);
send_message_and_unref(p->conn, reply);
pending_request_free(p);
}
if (sink->session) {
avdtp_unref(sink->session);
sink->session = NULL;
}
sink->stream = NULL;
c = sink->c;
break;
case AVDTP_STATE_CONFIGURED:
if (!sink->initiator)
break;
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;
}
if (sink->c && sink->c->pkt)
a2dp_start_stream_when_opened(sink->session, stream);
break;
case AVDTP_STATE_OPEN:
sink->suspending = FALSE;
if (old_state == AVDTP_STATE_CONFIGURED)
dbus_connection_emit_signal(dev->conn, dev->path,
AUDIO_SINK_INTERFACE,
"Connected",
DBUS_TYPE_INVALID);
if (!sink->initiator)
break;
if (!(sink->c && sink->c->pkt))
c = sink->c;
break;
case AVDTP_STATE_CONFIGURED:
case AVDTP_STATE_STREAMING:
c = sink->c;
break;
case AVDTP_STATE_CLOSING:
break;
case AVDTP_STATE_ABORTING:
default:
break;
}
sink->state = new_state;
if (c) {
sink->c = NULL;
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, fd;
ret = sink_get_config(dev, c->sock, c->pkt,
c->pkt_len, &rsp, &fd);
if (ret == 0) {
unix_send_cfg(c->sock, rsp, fd);
g_free(rsp);
}
else
unix_send_cfg(c->sock, NULL, -1);
}
pending_connect_free(c);
}
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;
sink->stream = NULL;
}
}
static void discovery_complete(struct avdtp *session, GSList *seps, int err,
void *user_data)
static void stream_setup_complete(struct avdtp *session, struct device *dev,
struct avdtp_stream *stream,
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;
struct pending_request *pending;
if (err < 0) {
error("Discovery failed");
err_str = strerror(-err);
goto failed;
pending = sink->connect;
sink->connect = NULL;
if (stream) {
DBusMessage *reply;
reply = dbus_message_new_method_return(pending->msg);
send_message_and_unref(pending->conn, reply);
}
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;
}
sink->initiator = TRUE;
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) {
else {
err_failed(pending->conn, pending->msg, "Stream setup failed");
avdtp_unref(sink->session);
sink->session = NULL;
}
pending_request_free(pending);
}
static DBusHandlerResult sink_connect(DBusConnection *conn,
@ -243,31 +152,36 @@ static DBusHandlerResult sink_connect(DBusConnection *conn,
{
struct device *dev = data;
struct sink *sink = dev->sink;
struct pending_request *c;
int err;
struct pending_request *pending;
int id;
if (!sink->session)
sink->session = avdtp_get(&dev->src, &dev->dst);
if (sink->c)
if (sink->connect || sink->disconnect)
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_request, 1);
c->msg = dbus_message_ref(msg);
sink->c = c;
pending = g_new0(struct pending_request, 1);
pending->conn = dbus_connection_ref(conn);
pending->msg = dbus_message_ref(msg);
sink->connect = pending;
err = avdtp_discover(sink->session, discovery_complete, data);
if (err < 0) {
pending_connect_free(c);
sink->c = NULL;
id = a2dp_source_request_stream(sink->session, dev, FALSE,
stream_setup_complete, pending);
if (id < 0) {
pending_request_free(pending);
sink->connect = NULL;
avdtp_unref(sink->session);
sink->session = NULL;
return err_connect_failed(conn, msg, strerror(err));
return err_connect_failed(conn, msg,
"Failed to request a stream");
}
pending->id = id;
return DBUS_HANDLER_RESULT_HANDLED;
}
@ -276,27 +190,32 @@ static DBusHandlerResult sink_disconnect(DBusConnection *conn,
{
struct device *device = data;
struct sink *sink = device->sink;
struct pending_request *c;
struct pending_request *pending;
int err;
if (!sink->session)
return err_not_connected(conn, msg);
if (sink->c)
if (sink->connect || sink->disconnect)
return err_failed(conn, msg, strerror(EBUSY));
if (sink->state < AVDTP_STATE_OPEN) {
DBusMessage *reply = dbus_message_new_method_return(msg);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
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));
return send_message_and_unref(conn, reply);
}
c = g_new0(struct pending_request, 1);
c->msg = dbus_message_ref(msg);
sink->c = c;
err = avdtp_close(sink->session, sink->stream);
if (err < 0)
return err_failed(conn, msg, strerror(-err));
pending = g_new0(struct pending_request, 1);
pending->conn = dbus_connection_ref(conn);
pending->msg = dbus_message_ref(msg);
sink->disconnect = pending;
return DBUS_HANDLER_RESULT_HANDLED;
}
@ -355,66 +274,16 @@ void sink_free(struct device *dev)
if (sink->session)
avdtp_unref(sink->session);
if (sink->c)
pending_connect_free(sink->c);
if (sink->connect)
pending_request_free(sink->connect);
if (sink->disconnect)
pending_request_free(sink->disconnect);
g_free(sink);
dev->sink = NULL;
}
int sink_get_config(struct device *dev, int sock, struct ipc_packet *req,
int pkt_len, struct ipc_data_cfg **rsp, int *fd)
{
struct sink *sink = dev->sink;
int err;
struct pending_request *c = NULL;
if (!sink->suspending && sink->state == AVDTP_STATE_STREAMING)
goto proceed;
if (sink->c) {
error("sink_get_config: another request already in progress");
return -EBUSY;
}
if (!sink->session)
sink->session = avdtp_get(&dev->src, &dev->dst);
c = g_new0(struct pending_request, 1);
c->sock = sock;
c->pkt = g_malloc(pkt_len);
memcpy(c->pkt, req, pkt_len);
if (sink->state == AVDTP_STATE_IDLE)
err = avdtp_discover(sink->session, discovery_complete, dev);
else if (sink->state < AVDTP_STATE_STREAMING)
err = avdtp_start(sink->session, sink->stream);
else if (sink->suspending)
err = 0;
else
err = -EINVAL;
if (err < 0)
goto failed;
sink->c = c;
return 1;
proceed:
if (!a2dp_get_config(sink->stream, rsp, fd)) {
err = -EINVAL;
goto failed;
}
return 0;
failed:
if (c)
pending_connect_free(c);
return -err;
}
gboolean sink_is_active(struct device *dev)
{
struct sink *sink = dev->sink;
@ -425,52 +294,6 @@ gboolean sink_is_active(struct device *dev)
return FALSE;
}
void sink_set_state(struct device *dev, avdtp_state_t state)
{
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) {
sink->suspending = TRUE;
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(struct device *dev)
{
struct sink *sink = dev->sink;

View File

@ -25,10 +25,7 @@
struct sink *sink_init(struct device *dev);
void sink_free(struct device *dev);
int sink_get_config(struct device *dev, int sock, struct ipc_packet *req,
int pkt_len, struct ipc_data_cfg **rsp, int *fd);
gboolean sink_is_active(struct device *dev);
void sink_set_state(struct device *dev, avdtp_state_t state);
avdtp_state_t sink_get_state(struct device *dev);
gboolean sink_new_stream(struct device *dev, struct avdtp *session,
struct avdtp_stream *stream);

View File

@ -43,21 +43,51 @@
#include "ipc.h"
#include "device.h"
#include "manager.h"
#include "avdtp.h"
#include "a2dp.h"
#include "headset.h"
#include "sink.h"
#include "unix.h"
typedef enum {
TYPE_NONE,
TYPE_HEADSET,
TYPE_SINK,
TYPE_SOURCE
} service_type_t;
typedef void (*notify_cb_t) (struct device *dev, void *data);
struct unix_client {
struct device *dev;
service_type_t type;
union {
struct avdtp *session;
void *data;
} data;
int sock;
int req_id;
notify_cb_t disconnect;
notify_cb_t suspend;
notify_cb_t play;
};
static GSList *clients = NULL;
static int unix_sock = -1;
static int unix_send_state(int sock, struct ipc_packet *pkt);
static void client_free(struct unix_client *client)
{
switch (client->type) {
case TYPE_SINK:
case TYPE_SOURCE:
if (client->data.session)
avdtp_unref(client->data.session);
break;
default:
break;
}
if (client->sock >= 0)
close(client->sock);
g_free(client);
@ -96,12 +126,116 @@ static int unix_sendmsg_fd(int sock, int fd, struct ipc_packet *pkt)
return sendmsg(sock, &msgh, MSG_NOSIGNAL);
}
static service_type_t select_service(struct device *dev)
{
if (dev->sink && avdtp_is_connected(&dev->src, &dev->dst))
return TYPE_SINK;
else if (dev->headset && headset_is_active(dev))
return TYPE_HEADSET;
else if (dev->sink)
return TYPE_SINK;
else if (dev->headset)
return TYPE_HEADSET;
else
return TYPE_NONE;
}
static void a2dp_setup_complete(struct avdtp *session, struct device *dev,
struct avdtp_stream *stream,
void *user_data)
{
struct unix_client *client = user_data;
char buf[sizeof(struct ipc_data_cfg) + sizeof(struct ipc_codec_sbc)];
struct ipc_data_cfg *cfg = (void *) buf;
struct avdtp_service_capability *cap;
struct avdtp_media_codec_capability *codec_cap;
struct sbc_codec_cap *sbc_cap;
struct ipc_codec_sbc *sbc = (void *) cfg->data;
int fd;
GSList *caps;
if (!stream)
goto failed;
if (!avdtp_stream_get_transport(stream, &fd, &cfg->pkt_len, &caps)) {
error("Unable to get stream transport");
goto failed;
}
for (codec_cap = NULL; caps; caps = g_slist_next(caps)) {
cap = caps->data;
if (cap->category == AVDTP_MEDIA_CODEC) {
codec_cap = (void *) cap->data;
break;
}
}
if (codec_cap == NULL ||
codec_cap->media_codec_type != A2DP_CODEC_SBC) {
error("Unable to find matching codec capability");
goto failed;
}
cfg->fd_opt = CFG_FD_OPT_WRITE;
sbc_cap = (void *) codec_cap;
cfg->channels = sbc_cap->channel_mode == A2DP_CHANNEL_MODE_MONO ?
1 : 2;
cfg->channel_mode = sbc_cap->channel_mode;
cfg->sample_size = 2;
switch (sbc_cap->frequency) {
case A2DP_SAMPLING_FREQ_16000:
cfg->rate = 16000;
break;
case A2DP_SAMPLING_FREQ_32000:
cfg->rate = 32000;
break;
case A2DP_SAMPLING_FREQ_44100:
cfg->rate = 44100;
break;
case A2DP_SAMPLING_FREQ_48000:
cfg->rate = 48000;
break;
}
cfg->codec = CFG_CODEC_SBC;
sbc->allocation = sbc_cap->allocation_method == A2DP_ALLOCATION_SNR ?
0x01 : 0x00;
sbc->subbands = sbc_cap->subbands == A2DP_SUBBANDS_4 ? 4 : 8;
switch (sbc_cap->block_length) {
case A2DP_BLOCK_LENGTH_4:
sbc->blocks = 4;
break;
case A2DP_BLOCK_LENGTH_8:
sbc->blocks = 8;
break;
case A2DP_BLOCK_LENGTH_12:
sbc->blocks = 12;
break;
case A2DP_BLOCK_LENGTH_16:
sbc->blocks = 16;
break;
}
sbc->bitpool = sbc_cap->max_bitpool;
unix_send_cfg(client->sock, cfg, fd);
return;
failed:
unix_send_cfg(client->sock, NULL, -1);
a2dp_source_unlock(dev, session);
}
static void cfg_event(struct unix_client *client, struct ipc_packet *pkt,
int len)
{
struct ipc_data_cfg *rsp;
struct device *dev;
int ret, fd;
int ret, fd, id;
dev = manager_get_connected_device();
if (dev)
@ -112,19 +246,50 @@ static void cfg_event(struct unix_client *client, struct ipc_packet *pkt,
goto failed;
proceed:
ret = device_get_config(dev, client->sock, pkt, len, &rsp, &fd);
if (ret < 0)
client->type = select_service(dev);
switch (client->type) {
case TYPE_SINK:
if (!client->data.session)
client->data.session = avdtp_get(&dev->src, &dev->dst);
if (!a2dp_source_lock(dev, client->data.session)) {
error("Unable to lock A2DP source SEP");
goto failed;
}
id = a2dp_source_request_stream(client->data.session, dev,
TRUE, a2dp_setup_complete,
client);
if (id < 0) {
error("request_stream failed");
goto failed;
}
client->req_id = id;
client->disconnect = (notify_cb_t) a2dp_source_unlock;
client->suspend = (notify_cb_t) a2dp_source_suspend;
client->play = (notify_cb_t) a2dp_source_start_stream;
break;
case TYPE_HEADSET:
if (!headset_lock(dev, client->data.data)) {
error("Unable to lock headset");
goto failed;
}
ret = headset_get_config(dev, client->sock, pkt, len, &rsp,
&fd);
client->disconnect = (notify_cb_t) headset_unlock;
client->suspend = (notify_cb_t) headset_suspend;
client->play = (notify_cb_t) headset_play;
break;
default:
error("No known services for device");
goto failed;
}
client->dev = dev;
/* Connecting in progress */
if (ret == 1)
return;
unix_send_cfg(client->sock, rsp, fd);
g_free(rsp);
return;
failed:
@ -139,6 +304,7 @@ static void ctl_event(struct unix_client *client, struct ipc_packet *pkt,
static void state_event(struct unix_client *client, struct ipc_packet *pkt,
int len)
{
#if 0
struct ipc_data_state *state = (struct ipc_data_state *) pkt->data;
struct device *dev = client->dev;
@ -148,6 +314,7 @@ static void state_event(struct unix_client *client, struct ipc_packet *pkt,
state->state = device_get_state(dev);
unix_send_state(client->sock, pkt);
#endif
}
static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
@ -156,14 +323,30 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
struct ipc_packet *pkt = (void *) buf;
struct unix_client *client = data;
int len, len_check;
void *cb_data;
if (cond & G_IO_NVAL)
return FALSE;
switch (client->type) {
case TYPE_HEADSET:
cb_data = client->data.data;
break;
case TYPE_SINK:
case TYPE_SOURCE:
cb_data = client->data.session;
break;
default:
cb_data = NULL;
break;
}
if (cond & (G_IO_HUP | G_IO_ERR)) {
debug("Unix client disconnected");
if (client->dev)
device_set_state(client->dev, STATE_CONNECTED);
if (client->disconnect)
client->disconnect(client->dev, cb_data);
if (client->type == TYPE_SINK && client->req_id >= 0)
a2dp_source_cancel_stream(client->req_id);
goto failed;
}
@ -339,6 +522,7 @@ int unix_send_cfg(int sock, struct ipc_data_cfg *cfg, int fd)
return 0;
}
#if 0
static int unix_send_state(int sock, struct ipc_packet *pkt)
{
struct ipc_data_state *state = (struct ipc_data_state *) pkt->data;
@ -359,3 +543,4 @@ static int unix_send_state(int sock, struct ipc_packet *pkt)
return 0;
}
#endif