diff options
author | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-16 15:42:10 +0000 |
---|---|---|
committer | Johan Hedberg <johan.hedberg@nokia.com> | 2007-08-16 15:42:10 +0000 |
commit | cdd9e2e17ad674e5fc1a5ed19643880ef61d28c7 (patch) | |
tree | 5292da714c803e54ad8e199f3a06d6db9d9d42f2 /audio/a2dp.c | |
parent | 3863eba35ca65bb4ea17d3763ae27afafdf0f4c2 (diff) |
Rework interfacing with the avdtp state machine
Diffstat (limited to 'audio/a2dp.c')
-rw-r--r-- | audio/a2dp.c | 460 |
1 files changed, 394 insertions, 66 deletions
diff --git a/audio/a2dp.c b/audio/a2dp.c index b8431bc6..5302fb2d 100644 --- a/audio/a2dp.c +++ b/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; + } + + if (setup->start) { + if (avdtp_start(session, stream) == 0) + return; - err = avdtp_start(session, stream); - if (err < 0) - error("Error on avdtp_start %s (%d)", strerror(-err), err); + 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 (sink.sep) { + avdtp_unregister_sep(sink.sep); + sink.sep = NULL; + } - if (source_sep) - avdtp_unregister_sep(source_sep); + 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 (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; + 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; } |