diff options
Diffstat (limited to 'audio')
-rw-r--r-- | audio/a2dp.c | 460 | ||||
-rw-r--r-- | audio/a2dp.h | 16 | ||||
-rw-r--r-- | audio/avdtp.c | 10 | ||||
-rw-r--r-- | audio/avdtp.h | 4 | ||||
-rw-r--r-- | audio/device.c | 25 | ||||
-rw-r--r-- | audio/device.h | 5 | ||||
-rw-r--r-- | audio/headset.c | 33 | ||||
-rw-r--r-- | audio/headset.h | 5 | ||||
-rw-r--r-- | audio/sink.c | 325 | ||||
-rw-r--r-- | audio/sink.h | 3 | ||||
-rw-r--r-- | audio/unix.c | 215 |
11 files changed, 736 insertions, 365 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; } diff --git a/audio/a2dp.h b/audio/a2dp.h index 23416988..3617afc4 100644 --- a/audio/a2dp.h +++ b/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); + diff --git a/audio/avdtp.c b/audio/avdtp.c index eb00f8bb..7194177f 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -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) diff --git a/audio/avdtp.h b/audio/avdtp.h index f7ca6308..0043c480 100644 --- a/audio/avdtp.h +++ b/audio/avdtp.h @@ -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); diff --git a/audio/device.c b/audio/device.c index fe3af8cb..2a6498ee 100644 --- a/audio/device.c +++ b/audio/device.c @@ -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) { diff --git a/audio/device.h b/audio/device.h index d9a42ca1..a527c096 100644 --- a/audio/device.h +++ b/audio/device.h @@ -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); diff --git a/audio/headset.c b/audio/headset.c index b0e67ba2..056f9e18 100644 --- a/audio/headset.c +++ b/audio/headset.c @@ -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; +} diff --git a/audio/headset.h b/audio/headset.h index 2a65d136..29e2e496 100644 --- a/audio/headset.h +++ b/audio/headset.h @@ -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); diff --git a/audio/sink.c b/audio/sink.c index fe8b6393..0f69bbf6 100644 --- a/audio/sink.c +++ b/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; - - if (err < 0) { - error("Discovery failed"); - err_str = strerror(-err); - goto failed; - } - - debug("Discovery complete"); - - if (avdtp_get_seps(session, AVDTP_SEP_TYPE_SINK, AVDTP_MEDIA_TYPE_AUDIO, - A2DP_CODEC_SBC, &lsep, &rsep) < 0) { - err_str = "No matching ACP and INT SEPs found"; - goto failed; - } + struct pending_request *pending; - if (!a2dp_select_capabilities(rsep, &caps)) { - err_str = "Unable to select remote SEP capabilities"; - goto failed; - } + pending = sink->connect; + sink->connect = NULL; - 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; + if (stream) { + DBusMessage *reply; + reply = dbus_message_new_method_return(pending->msg); + send_message_and_unref(pending->conn, reply); } - - 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; diff --git a/audio/sink.h b/audio/sink.h index 7894a36c..309379b1 100644 --- a/audio/sink.h +++ b/audio/sink.h @@ -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); diff --git a/audio/unix.c b/audio/unix.c index 52b27809..10856b93 100644 --- a/audio/unix.c +++ b/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 |