mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2025-01-26 22:33:29 +08:00
Rework interfacing with the avdtp state machine
This commit is contained in:
parent
3863eba35c
commit
cdd9e2e17a
464
audio/a2dp.c
464
audio/a2dp.c
@ -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;
|
||||
}
|
||||
|
16
audio/a2dp.h
16
audio/a2dp.h
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
327
audio/sink.c
327
audio/sink.c
@ -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;
|
||||
|
@ -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);
|
||||
|
215
audio/unix.c
215
audio/unix.c
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user