From 2934e194f3ffe754e18477113c870a7b98f88454 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 3 Dec 2007 22:41:29 +0000 Subject: Handle new ipc messages properly and adapt the plugins. --- audio/a2dp.c | 712 ++++++++++++++++++++++---------------------------- audio/a2dp.h | 26 +- audio/avdtp.c | 37 ++- audio/avdtp.h | 2 + audio/gsta2dpsink.c | 73 ++++-- audio/ipc.h | 8 +- audio/pcm_bluetooth.c | 154 ++++++++++- audio/sink.c | 212 +++++++++++++-- audio/unix.c | 581 +++++++++++++++++++++++++++++++--------- 9 files changed, 1225 insertions(+), 580 deletions(-) diff --git a/audio/a2dp.c b/audio/a2dp.c index 83441617..bf644359 100644 --- a/audio/a2dp.c +++ b/audio/a2dp.c @@ -43,12 +43,10 @@ #include "sink.h" #include "a2dp.h" -#define MAX_BITPOOL 64 -#define MIN_BITPOOL 2 - /* The duration that streams without users are allowed to stay in * STREAMING state. */ #define SUSPEND_TIMEOUT 5000 +#define RECONFIGURE_TIMEOUT 500 #ifndef MIN # define MIN(x, y) ((x) < (y) ? (x) : (y)) @@ -69,20 +67,23 @@ struct a2dp_sep { gboolean starting; }; -struct a2dp_stream_cb { - a2dp_stream_cb_t cb; +struct a2dp_setup_cb { + a2dp_config_cb_t config_cb; + a2dp_stream_cb_t resume_cb; + a2dp_stream_cb_t suspend_cb; void *user_data; int id; }; -struct a2dp_stream_setup { +struct a2dp_setup { struct avdtp *session; struct a2dp_sep *sep; struct avdtp_stream *stream; - struct avdtp_service_capability *media_codec; - gboolean start; + GSList *client_caps; + gboolean reconfigure; gboolean canceled; GSList *cb; + int ref; }; static DBusConnection *connection = NULL; @@ -94,9 +95,20 @@ static uint32_t source_record_id = 0; static uint32_t sink_record_id = 0; static GSList *setups = NULL; +static unsigned int cb_id = 0; + +static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) +{ + setup->ref++; + + debug("setup_ref(%p): ref=%d", setup, setup->ref); + + return setup; +} -static void stream_setup_free(struct a2dp_stream_setup *s) +static void setup_free(struct a2dp_setup *s) { + debug("setup_free(%p)", s); setups = g_slist_remove(setups, s); if (s->session) avdtp_unref(s->session); @@ -105,6 +117,16 @@ static void stream_setup_free(struct a2dp_stream_setup *s) g_free(s); } +static void setup_unref(struct a2dp_setup *setup) +{ + setup->ref--; + + debug("setup_unref(%p): ref=%d", setup, setup->ref); + + if (setup->ref <= 0) + setup_free(setup); +} + static struct device *a2dp_get_dev(struct avdtp *session) { bdaddr_t addr; @@ -114,35 +136,97 @@ static struct device *a2dp_get_dev(struct avdtp *session) return manager_device_connected(&addr, A2DP_SOURCE_UUID); } -static gboolean finalize_stream_setup(struct a2dp_stream_setup *s, struct avdtp_error *err) +static gboolean finalize_config(struct a2dp_setup *s, struct avdtp_error *err) { GSList *l; + setup_ref(s); for (l = s->cb; l != NULL; l = l->next) { - struct a2dp_stream_cb *cb = l->data; - - cb->cb(s->session, s->sep, s->stream, cb->user_data, err); + struct a2dp_setup_cb *cb = l->data; + + if (cb->config_cb) { + cb->config_cb(s->session, s->sep, s->stream, err, + cb->user_data); + cb->config_cb = NULL; + setup_unref(s); + } } - stream_setup_free(s); + setup_unref(s); return FALSE; } -static gboolean finalize_stream_setup_errno(struct a2dp_stream_setup *s, int err) +static gboolean finalize_config_errno(struct a2dp_setup *s, int err) { struct avdtp_error avdtp_err; avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); - return finalize_stream_setup(s, err ? &avdtp_err : NULL); + return finalize_config(s, err ? &avdtp_err : NULL); } -static struct a2dp_stream_setup *find_setup_by_session(struct avdtp *session) +static gboolean finalize_resume(struct a2dp_setup *s, struct avdtp_error *err) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->resume_cb) { + cb->resume_cb(s->session, err, cb->user_data); + cb->resume_cb = NULL; + setup_unref(s); + } + } + + setup_unref(s); + return FALSE; +} + +static gboolean finalize_resume_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); + + return finalize_resume(s, err ? &avdtp_err : NULL); +} + +static gboolean finalize_suspend(struct a2dp_setup *s, struct avdtp_error *err) +{ + GSList *l; + + setup_ref(s); + for (l = s->cb; l != NULL; l = l->next) { + struct a2dp_setup_cb *cb = l->data; + + if (cb->suspend_cb) { + cb->suspend_cb(s->session, err, cb->user_data); + cb->suspend_cb = NULL; + setup_unref(s); + } + } + + setup_unref(s); + return FALSE; +} + +static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err) +{ + struct avdtp_error avdtp_err; + + avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err); + + return finalize_suspend(s, err ? &avdtp_err : NULL); +} + +static struct a2dp_setup *find_setup_by_session(struct avdtp *session) { GSList *l; for (l = setups; l != NULL; l = l->next) { - struct a2dp_stream_setup *setup = l->data; + struct a2dp_setup *setup = l->data; if (setup->session == session) return setup; @@ -151,12 +235,12 @@ static struct a2dp_stream_setup *find_setup_by_session(struct avdtp *session) return NULL; } -static struct a2dp_stream_setup *find_setup_by_dev(struct device *dev) +static struct a2dp_setup *find_setup_by_dev(struct device *dev) { GSList *l; for (l = setups; l != NULL; l = l->next) { - struct a2dp_stream_setup *setup = l->data; + struct a2dp_setup *setup = l->data; struct device *setup_dev = a2dp_get_dev(setup->session); if (setup_dev == dev) @@ -186,197 +270,9 @@ static void stream_state_changed(struct avdtp_stream *stream, avdtp_unref(sep->session); sep->session = NULL; } - + sep->stream = NULL; -} - -static uint8_t default_bitpool(uint8_t freq, uint8_t mode) -{ - switch (freq) { - case A2DP_SAMPLING_FREQ_16000: - case A2DP_SAMPLING_FREQ_32000: - return 53; - case A2DP_SAMPLING_FREQ_44100: - switch (mode) { - case A2DP_CHANNEL_MODE_MONO: - case A2DP_CHANNEL_MODE_DUAL_CHANNEL: - return 31; - case A2DP_CHANNEL_MODE_STEREO: - case A2DP_CHANNEL_MODE_JOINT_STEREO: - return 53; - default: - error("Invalid channel mode %u", mode); - return 53; - } - case A2DP_SAMPLING_FREQ_48000: - switch (mode) { - case A2DP_CHANNEL_MODE_MONO: - case A2DP_CHANNEL_MODE_DUAL_CHANNEL: - return 29; - case A2DP_CHANNEL_MODE_STEREO: - case A2DP_CHANNEL_MODE_JOINT_STEREO: - return 51; - default: - error("Invalid channel mode %u", mode); - return 51; - } - default: - error("Invalid sampling freq %u", freq); - return 53; - } -} - -static gboolean select_sbc_params(struct sbc_codec_cap *cap, - struct sbc_codec_cap *supported) -{ - uint max_bitpool, min_bitpool; - - memset(cap, 0, sizeof(struct sbc_codec_cap)); - - cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; - cap->cap.media_codec_type = A2DP_CODEC_SBC; - - if (supported->frequency & A2DP_SAMPLING_FREQ_44100) - cap->frequency = A2DP_SAMPLING_FREQ_44100; - else if (supported->frequency & A2DP_SAMPLING_FREQ_48000) - cap->frequency = A2DP_SAMPLING_FREQ_48000; - else if (supported->frequency & A2DP_SAMPLING_FREQ_32000) - cap->frequency = A2DP_SAMPLING_FREQ_32000; - else if (supported->frequency & A2DP_SAMPLING_FREQ_16000) - cap->frequency = A2DP_SAMPLING_FREQ_16000; - else { - error("No supported frequencies"); - return FALSE; - } - - if (supported->channel_mode & A2DP_CHANNEL_MODE_JOINT_STEREO) - cap->channel_mode = A2DP_CHANNEL_MODE_JOINT_STEREO; - else if (supported->channel_mode & A2DP_CHANNEL_MODE_STEREO) - cap->channel_mode = A2DP_CHANNEL_MODE_STEREO; - else if (supported->channel_mode & A2DP_CHANNEL_MODE_DUAL_CHANNEL) - cap->channel_mode = A2DP_CHANNEL_MODE_DUAL_CHANNEL; - else if (supported->channel_mode & A2DP_CHANNEL_MODE_MONO) - cap->channel_mode = A2DP_CHANNEL_MODE_MONO; - else { - error("No supported channel modes"); - return FALSE; - } - - if (supported->block_length & A2DP_BLOCK_LENGTH_16) - cap->block_length = A2DP_BLOCK_LENGTH_16; - else if (supported->block_length & A2DP_BLOCK_LENGTH_12) - cap->block_length = A2DP_BLOCK_LENGTH_12; - else if (supported->block_length & A2DP_BLOCK_LENGTH_8) - cap->block_length = A2DP_BLOCK_LENGTH_8; - else if (supported->block_length & A2DP_BLOCK_LENGTH_4) - cap->block_length = A2DP_BLOCK_LENGTH_4; - else { - error("No supported block lengths"); - return FALSE; - } - - if (supported->subbands & A2DP_SUBBANDS_8) - cap->subbands = A2DP_SUBBANDS_8; - else if (supported->subbands & A2DP_SUBBANDS_4) - cap->subbands = A2DP_SUBBANDS_4; - else { - error("No supported subbands"); - return FALSE; - } - - if (supported->allocation_method & A2DP_ALLOCATION_LOUDNESS) - cap->allocation_method = A2DP_ALLOCATION_LOUDNESS; - else if (supported->allocation_method & A2DP_ALLOCATION_SNR) - cap->allocation_method = A2DP_ALLOCATION_SNR; - - min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); - max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), - supported->max_bitpool); - - cap->min_bitpool = min_bitpool; - cap->max_bitpool = max_bitpool; - - return TRUE; -} -static gboolean a2dp_select_capabilities(struct avdtp *session, - struct avdtp_remote_sep *rsep, - GSList **caps) -{ - struct avdtp_service_capability *media_transport, *media_codec; - struct sbc_codec_cap sbc_cap; - struct a2dp_stream_setup *setup; - - setup = find_setup_by_session(session); - if (!setup) - return FALSE; - - if (setup->media_codec) { - memcpy(&sbc_cap, setup->media_codec->data, sizeof(sbc_cap)); - } else { - media_codec = avdtp_get_codec(rsep); - if (!media_codec) - return FALSE; - - select_sbc_params(&sbc_cap, - (struct sbc_codec_cap *) media_codec->data); - } - - media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, - NULL, 0); - - *caps = g_slist_append(*caps, media_transport); - - media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, - sizeof(sbc_cap)); - - *caps = g_slist_append(*caps, media_codec); - - - return TRUE; -} - -static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, - void *user_data) -{ - struct avdtp_local_sep *lsep; - struct avdtp_remote_sep *rsep; - struct a2dp_stream_setup *setup; - GSList *caps = NULL; - int posix_err; - - setup = find_setup_by_session(session); - - if (!setup) - return; - - if (err || setup->canceled) { - setup->stream = NULL; - finalize_stream_setup(setup, err); - 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_errno(setup, -EINVAL); - return; - } - - if (!a2dp_select_capabilities(session, rsep, &caps)) { - error("Unable to select remote SEP capabilities"); - finalize_stream_setup_errno(setup, -EINVAL); - return; - } - - posix_err = avdtp_set_configuration(session, rsep, lsep, caps, - &setup->stream); - if (posix_err < 0) { - error("avdtp_set_configuration: %s", strerror(-posix_err)); - finalize_stream_setup_errno(setup, posix_err); - } } static gboolean setconf_ind(struct avdtp *session, @@ -490,7 +386,7 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; + struct a2dp_setup *setup; struct device *dev; int ret; @@ -503,7 +399,7 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, if (err) { if (setup) - finalize_stream_setup(setup, err); + finalize_config(setup, err); return; } @@ -523,7 +419,7 @@ static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, error("Error on avdtp_open %s (%d)", strerror(-ret), -ret); setup->stream = NULL; - finalize_stream_setup_errno(setup, ret); + finalize_config_errno(setup, ret); } } @@ -569,8 +465,7 @@ static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; - int posix_err; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: Open_Cfm"); @@ -584,28 +479,19 @@ static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, if (setup->canceled) { if (!err) avdtp_close(session, stream); - stream_setup_free(setup); + setup_unref(setup); return; } + if (setup->reconfigure) + setup->reconfigure = FALSE; + if (err) { setup->stream = NULL; - finalize_stream_setup(setup, err); - return; - } - - if (setup->start) { - posix_err = avdtp_start(session, stream); - if (posix_err == 0) - return; - - error("avdtp_start failed"); - setup->stream = NULL; + finalize_config(setup, err); } else - posix_err = 0; - - finalize_stream_setup_errno(setup, -posix_err); + finalize_config(setup, 0); } static gboolean suspend_timeout(struct a2dp_sep *sep) @@ -647,7 +533,7 @@ static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: Start_Cfm"); @@ -661,16 +547,16 @@ static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep, if (setup->canceled) { if (!err) avdtp_close(session, stream); - stream_setup_free(setup); + setup_unref(setup); return; } if (err) { setup->stream = NULL; - finalize_stream_setup(setup, err); + finalize_resume(setup, err); } else - finalize_stream_setup_errno(setup, 0); + finalize_resume_errno(setup, 0); } static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, @@ -699,8 +585,7 @@ static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; - int posix_err; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: Suspend_Cfm"); @@ -714,15 +599,11 @@ static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep, return; if (err) { - finalize_stream_setup(setup, err); - return; - } - - if (setup->start) { - posix_err = avdtp_start(session, stream); - if (posix_err < 0) - finalize_stream_setup_errno(setup, posix_err); + setup->stream = NULL; + finalize_suspend(setup, err); } + else + finalize_suspend_errno(setup, 0); } static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, @@ -739,13 +620,39 @@ static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, return TRUE; } +static gboolean reconfigure(gpointer data) +{ + struct a2dp_setup *setup = data; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + int posix_err; + + posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK, + AVDTP_MEDIA_TYPE_AUDIO, A2DP_CODEC_SBC, + &lsep, &rsep); + if (posix_err < 0) { + error("No matching ACP and INT SEPs found"); + finalize_config_errno(setup, posix_err); + } + + posix_err = avdtp_set_configuration(setup->session, rsep, lsep, + setup->client_caps, + &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", + strerror(-posix_err)); + finalize_config_errno(setup, posix_err); + } + + return FALSE; +} + static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, struct avdtp_stream *stream, struct avdtp_error *err, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; - int posix_err; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: Close_Cfm"); @@ -757,28 +664,18 @@ static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, return; if (setup->canceled) { - stream_setup_free(setup); + setup_unref(setup); return; } if (err) { setup->stream = NULL; - finalize_stream_setup(setup, err); + finalize_config(setup, err); return; } - if (setup->start) { - posix_err = avdtp_discover(session, discovery_complete, setup); - if (posix_err == 0) - return; - - error("avdtp_discover failed"); - setup->stream = NULL; - } - else - posix_err = 0; - - finalize_stream_setup_errno(setup, -posix_err); + if (setup->reconfigure) + g_timeout_add(RECONFIGURE_TIMEOUT, reconfigure, setup); } static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep, @@ -802,11 +699,18 @@ static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: Abort_Cfm"); else debug("SBC Source: Abort_Cfm"); + + setup = find_setup_by_session(session); + if (!setup) + return; + + setup_unref(setup); } static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep, @@ -826,8 +730,7 @@ static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, void *user_data) { struct a2dp_sep *a2dp_sep = user_data; - struct a2dp_stream_setup *setup; - int posix_err; + struct a2dp_setup *setup; if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) debug("SBC Sink: ReConfigure_Cfm"); @@ -841,28 +744,16 @@ static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, if (setup->canceled) { if (!err) avdtp_close(session, stream); - stream_setup_free(setup); + setup_unref(setup); return; } if (err) { setup->stream = NULL; - finalize_stream_setup(setup, err); - return; - } - - if (setup->start) { - posix_err = avdtp_start(session, stream); - if (posix_err == 0) - return; - - error("avdtp_start failed"); - setup->stream = NULL; + finalize_config(setup, err); } else - posix_err = 0; - - finalize_stream_setup_errno(setup, -posix_err); + finalize_config(setup, 0); } static struct avdtp_sep_cfm cfm = { @@ -1062,10 +953,10 @@ void a2dp_exit() dbus_connection_unref(connection); } -gboolean a2dp_source_cancel_stream(struct device *dev, unsigned int id) +gboolean a2dp_source_cancel(struct device *dev, unsigned int id) { - struct a2dp_stream_cb *cb_data; - struct a2dp_stream_setup *setup; + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; GSList *l; setup = find_setup_by_dev(dev); @@ -1073,7 +964,7 @@ gboolean a2dp_source_cancel_stream(struct device *dev, unsigned int id) return FALSE; for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) { - struct a2dp_stream_cb *cb = l->data; + struct a2dp_setup_cb *cb = l->data; if (cb->id == id) { cb_data = cb; @@ -1095,17 +986,16 @@ gboolean a2dp_source_cancel_stream(struct device *dev, unsigned int id) return TRUE; } -unsigned int a2dp_source_request_stream(struct avdtp *session, - gboolean start, - a2dp_stream_cb_t cb, - void *user_data, - struct avdtp_service_capability *media_codec) +unsigned int a2dp_source_config(struct avdtp *session, a2dp_config_cb_t cb, + GSList *caps, void *user_data) { - struct a2dp_stream_cb *cb_data; - static unsigned int cb_id = 0; + struct a2dp_setup_cb *cb_data; GSList *l; - struct a2dp_stream_setup *setup; + struct a2dp_setup *setup; struct a2dp_sep *sep = NULL; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + int posix_err; for (l = sources; l != NULL; l = l->next) { struct a2dp_sep *tmp = l->data; @@ -1120,91 +1010,173 @@ unsigned int a2dp_source_request_stream(struct avdtp *session, } if (!sep) { - error("a2dp_source_request_stream: no available SEP found"); + error("a2dp_source_cfg: no available SEP found"); return 0; } - setup = find_setup_by_session(session); - - debug("a2dp_source_request_stream: selected SEP %p", sep); + debug("a2dp_source_config: selected SEP %p", sep); - cb_data = g_new(struct a2dp_stream_cb, 1); - cb_data->cb = cb; + cb_data = g_new(struct a2dp_setup_cb, 1); + cb_data->config_cb = cb; cb_data->user_data = user_data; cb_data->id = ++cb_id; - if (setup) { - setup->canceled = FALSE; - setup->sep = sep; - setup->cb = g_slist_append(setup->cb, cb_data); - if (start) - setup->start = TRUE; - return cb_data->id; + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setups = g_slist_append(setups, setup); } - setup = g_new0(struct a2dp_stream_setup, 1); - setup->session = avdtp_ref(session); - setup->sep = sep; + setup_ref(setup); setup->cb = g_slist_append(setup->cb, cb_data); - setup->start = start; + setup->sep = sep; setup->stream = sep->stream; - setup->media_codec = media_codec; + setup->client_caps = caps; switch (avdtp_sep_get_state(sep->sep)) { case AVDTP_STATE_IDLE: - if (avdtp_discover(session, discovery_complete, setup) < 0) { - error("avdtp_discover failed"); + 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"); + goto failed; + } + + posix_err = avdtp_set_configuration(session, rsep, lsep, + caps, &setup->stream); + if (posix_err < 0) { + error("avdtp_set_configuration: %s", + strerror(-posix_err)); goto failed; } break; case AVDTP_STATE_OPEN: - if (!start) { - g_idle_add((GSourceFunc) finalize_stream_setup, setup); - break; - } - if (sep->starting) - break; - if (setup->media_codec) { - if (avdtp_stream_has_capability(setup->stream, - setup->media_codec)) { - if (avdtp_start(session, sep->stream) < 0) { - error("avdtp_start failed"); - goto failed; - } - } else { - if (avdtp_close(session, sep->stream) < 0) { - error("avdtp_close failed"); - goto failed; - } + case AVDTP_STATE_STREAMING: + if (avdtp_stream_has_capabilities(setup->stream, caps)) + g_idle_add((GSourceFunc) finalize_config, setup); + else if (!setup->reconfigure) { + setup->reconfigure = TRUE; + if (avdtp_close(session, sep->stream) < 0) { + error("avdtp_close failed"); + goto failed; } } - else if (avdtp_start(session, sep->stream) < 0) { + break; + default: + error("SEP in bad state for requesting a new stream"); + goto failed; + } + + return cb_data->id; + +failed: + setup_unref(setup); + cb_id--; + return 0; +} + +unsigned int a2dp_source_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + cb_data = g_new(struct a2dp_setup_cb, 1); + cb_data->resume_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->sep)) { + case AVDTP_STATE_IDLE: + goto failed; + break; + case AVDTP_STATE_OPEN: + if (avdtp_start(session, sep->stream) < 0) { error("avdtp_start failed"); goto failed; } break; case AVDTP_STATE_STREAMING: - if (!start || !sep->suspending) { - if (sep->suspend_timer) { - g_source_remove(sep->suspend_timer); - sep->suspend_timer = 0; - avdtp_unref(sep->session); - sep->session = NULL; - } - g_idle_add((GSourceFunc) finalize_stream_setup, setup); + if (!sep->suspending && sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + avdtp_unref(sep->session); + sep->session = NULL; } + g_idle_add((GSourceFunc) finalize_resume, setup); break; default: - error("SEP in bad state for requesting a new stream"); + error("SEP in bad state"); goto failed; } - setups = g_slist_append(setups, setup); + return cb_data->id; + +failed: + setup_unref(setup); + cb_id--; + return 0; +} + +unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data) +{ + struct a2dp_setup_cb *cb_data; + struct a2dp_setup *setup; + + cb_data = g_new(struct a2dp_setup_cb, 1); + cb_data->suspend_cb = cb; + cb_data->user_data = user_data; + cb_data->id = ++cb_id; + + setup = find_setup_by_session(session); + if (!setup) { + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); + setups = g_slist_append(setups, setup); + } + + setup_ref(setup); + setup->cb = g_slist_append(setup->cb, cb_data); + setup->sep = sep; + setup->stream = sep->stream; + + switch (avdtp_sep_get_state(sep->sep)) { + case AVDTP_STATE_IDLE: + error("a2dp_source_suspend: no stream to suspend"); + goto failed; + break; + case AVDTP_STATE_OPEN: + g_idle_add((GSourceFunc) finalize_suspend, setup); + break; + case AVDTP_STATE_STREAMING: + if (avdtp_start(session, sep->stream) < 0) { + error("avdtp_start failed"); + goto failed; + } + break; + default: + error("SEP in bad state for resume"); + goto failed; + } return cb_data->id; failed: - stream_setup_free(setup); + setup_unref(setup); cb_id--; return 0; } @@ -1248,67 +1220,3 @@ gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session) return TRUE; } -gboolean a2dp_source_suspend(struct device *dev, struct avdtp *session) -{ - avdtp_state_t state; - GSList *l; - struct a2dp_sep *sep = NULL; - - for (l = sources; l != NULL; l = l->next) { - struct a2dp_sep *tmp = l->data; - - if (tmp->session && tmp->session == session) { - sep = tmp; - break; - } - } - - if (!sep) - return FALSE; - - state = avdtp_sep_get_state(sep->sep); - - if (!sep->stream || state != AVDTP_STATE_STREAMING) - return TRUE; - - if (avdtp_suspend(session, sep->stream) == 0) { - sep->suspending = TRUE; - return TRUE; - } - - return FALSE; -} - -gboolean a2dp_source_start_stream(struct device *dev, struct avdtp *session) -{ - avdtp_state_t state; - GSList *l; - struct a2dp_sep *sep = NULL; - - for (l = sources; l != NULL; l = l->next) { - struct a2dp_sep *tmp = l->data; - - if (tmp->session && tmp->session == session) { - sep = tmp; - break; - } - } - - if (!sep) - return FALSE; - - state = avdtp_sep_get_state(sep->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, sep->stream) < 0) - return FALSE; - - return TRUE; -} diff --git a/audio/a2dp.h b/audio/a2dp.h index 6579f64c..8227296f 100644 --- a/audio/a2dp.h +++ b/audio/a2dp.h @@ -48,6 +48,9 @@ #define A2DP_ALLOCATION_SNR (1 << 1) #define A2DP_ALLOCATION_LOUDNESS 1 +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + #if __BYTE_ORDER == __LITTLE_ENDIAN struct sbc_codec_cap { @@ -80,21 +83,24 @@ struct sbc_codec_cap { struct a2dp_sep; -typedef void (*a2dp_stream_cb_t) (struct avdtp *session, struct a2dp_sep *sep, +typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, - void *user_data, - struct avdtp_error *err); + struct avdtp_error *err, + void *user_data); +typedef void (*a2dp_stream_cb_t) (struct avdtp *session, + struct avdtp_error *err, + void *user_data); int a2dp_init(DBusConnection *conn, int sources, int sinks); void a2dp_exit(void); -unsigned int a2dp_source_request_stream(struct avdtp *session, - gboolean start, a2dp_stream_cb_t cb, - void *user_data, - struct avdtp_service_capability *media_codec); -gboolean a2dp_source_cancel_stream(struct device *dev, unsigned int id); +unsigned int a2dp_source_config(struct avdtp *session, a2dp_config_cb_t cb, + GSList *caps, void *user_data); +unsigned int a2dp_source_resume(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +unsigned int a2dp_source_suspend(struct avdtp *session, struct a2dp_sep *sep, + a2dp_stream_cb_t cb, void *user_data); +gboolean a2dp_source_cancel(struct device *dev, unsigned int id); gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session); gboolean a2dp_sep_unlock(struct a2dp_sep *sep, 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 f68561e9..3acd4128 100644 --- a/audio/avdtp.c +++ b/audio/avdtp.c @@ -1825,16 +1825,20 @@ static gboolean avdtp_discover_resp(struct avdtp *session, resp->seps[i].media_type, resp->seps[i].inuse); /* Skip SEP's which are in use */ +/* if (resp->seps[i].inuse) continue; +*/ sep = find_remote_sep(session->seps, resp->seps[i].seid); if (!sep) { sep = g_new0(struct avdtp_remote_sep, 1); session->seps = g_slist_append(session->seps, sep); } +/* else if (sep && sep->stream) continue; +*/ sep->seid = resp->seps[i].seid; sep->type = resp->seps[i].type; @@ -2275,6 +2279,21 @@ gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, return FALSE; } +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps) +{ + GSList *l; + + for (l = caps; l; l = g_slist_next(l)) { + struct avdtp_service_capability *cap = l->data; + + if (!avdtp_stream_has_capability(stream, cap)) + return FALSE; + } + + return TRUE; +} + gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, uint16_t *imtu, uint16_t *omtu, GSList **caps) @@ -2331,6 +2350,9 @@ struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, { struct avdtp_service_capability *cap; + if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC) + return NULL; + cap = g_malloc(sizeof(struct avdtp_service_capability) + length); cap->category = category; cap->length = length; @@ -2457,6 +2479,18 @@ int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream) return send_request(session, FALSE, stream, &req, sizeof(req)); } +static void copy_capabilities(gpointer data, gpointer user_data) +{ + struct avdtp_service_capability *src_cap = data; + struct avdtp_service_capability *dst_cap; + GSList **l = user_data; + + dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data, + src_cap->length); + + *l = g_slist_append(*l, dst_cap); +} + int avdtp_set_configuration(struct avdtp *session, struct avdtp_remote_sep *rsep, struct avdtp_local_sep *lsep, @@ -2484,7 +2518,8 @@ int avdtp_set_configuration(struct avdtp *session, new_stream->session = session; new_stream->lsep = lsep; new_stream->rseid = rsep->seid; - new_stream->caps = caps; + + g_slist_foreach(caps, copy_capabilities, &new_stream->caps); /* Calculate total size of request */ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) { diff --git a/audio/avdtp.h b/audio/avdtp.h index 88684f28..241fa713 100644 --- a/audio/avdtp.h +++ b/audio/avdtp.h @@ -221,6 +221,8 @@ gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock, gboolean avdtp_stream_has_capability(struct avdtp_stream *stream, struct avdtp_service_capability *cap); +gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream, + GSList *caps); int avdtp_set_configuration(struct avdtp *session, struct avdtp_remote_sep *rsep, diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c index b24dc8b8..1b893cc1 100644 --- a/audio/gsta2dpsink.c +++ b/audio/gsta2dpsink.c @@ -45,6 +45,7 @@ GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug); #define GST_CAT_DEFAULT a2dp_sink_debug #define BUFFER_SIZE 2048 +#define TEMPLATE_MAX_BITPOOL_VALUE 64 #define GST_A2DP_SINK_MUTEX_LOCK(s) G_STMT_START { \ g_mutex_lock (s->sink_lock); \ @@ -56,8 +57,8 @@ GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug); struct bluetooth_data { - struct bt_getcapabilities_rsp cfg; /* Bluetooth device config */ - gint link_mtu; + struct bt_getcapabilities_rsp caps; /* Bluetooth device capabilities */ + struct bt_setconfiguration_rsp cfg; /* Bluetooth device configuration */ int samples; /* Number of encoded samples */ gchar buffer[BUFFER_SIZE]; /* Codec transfer buffer */ gsize count; /* Codec transfer buffer counter */ @@ -67,6 +68,7 @@ struct bluetooth_data { int frame_count; /* Current frames in buffer*/ }; + #define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0) #define IS_MPEG(n) (strcmp((n), "audio/mpeg") == 0) @@ -92,6 +94,7 @@ static GstStaticPadTemplate a2dp_sink_factory = "blocks = (int) { 4, 8, 12, 16 }, " "subbands = (int) { 4, 8 }, " "allocation = (string) { snr, loudness }," + /* FIXME use constant here */ "bitpool = (int) [ 2, 64 ]; " "audio/mpeg, " "mpegversion = (int) 1, " @@ -230,9 +233,10 @@ static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, GstCaps *caps, sbc_capabilities_t *pkt) { - sbc_capabilities_t *cfg = &sink->data->cfg.sbc_capabilities; + sbc_capabilities_t *cfg = &sink->data->caps.sbc_capabilities; const GValue *value = NULL; const char *pref, *name; + gint rate, blocks, subbands; GstStructure *structure = gst_caps_get_structure(caps, 0); name = gst_structure_get_name(structure); @@ -243,7 +247,19 @@ static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, } value = gst_structure_get_value(structure, "rate"); - cfg->frequency = g_value_get_int(value); + rate = g_value_get_int(value); + if (rate == 44100) + cfg->frequency = BT_A2DP_SAMPLING_FREQ_44100; + else if (rate == 48000) + cfg->frequency = BT_A2DP_SAMPLING_FREQ_48000; + else if (rate == 32000) + cfg->frequency = BT_A2DP_SAMPLING_FREQ_32000; + else if (rate == 16000) + cfg->frequency = BT_A2DP_SAMPLING_FREQ_16000; + else { + GST_ERROR_OBJECT(sink, "Invalid rate while setting caps"); + return FALSE; + } value = gst_structure_get_value(structure, "mode"); pref = g_value_get_string(value); @@ -276,13 +292,34 @@ static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, } value = gst_structure_get_value(structure, "subbands"); - cfg->subbands = g_value_get_int(value); + subbands = g_value_get_int(value); + if (subbands == 8) + cfg->subbands = BT_A2DP_SUBBANDS_8; + else if (subbands == 4) + cfg->subbands = BT_A2DP_SUBBANDS_4; + else { + GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands); + return FALSE; + } value = gst_structure_get_value(structure, "blocks"); - cfg->block_length = g_value_get_int(value); + blocks = g_value_get_int(value); + if (blocks == 16) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (blocks == 12) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (blocks == 8) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (blocks == 4) + cfg->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks); + return FALSE; + } /* FIXME min and max ??? */ value = gst_structure_get_value(structure, "bitpool"); + cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value); memcpy(pkt, cfg, sizeof(*pkt)); @@ -375,7 +412,7 @@ static gboolean server_callback(GIOChannel *chan, static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self) { - sbc_capabilities_t *sbc = &self->data->cfg.sbc_capabilities; + sbc_capabilities_t *sbc = &self->data->caps.sbc_capabilities; GstStructure *structure; GValue *value; GValue *list; @@ -519,7 +556,9 @@ static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self) /* bitpool */ value = g_value_init(value, GST_TYPE_INT_RANGE); - gst_value_set_int_range(value, sbc->min_bitpool, sbc->max_bitpool); + gst_value_set_int_range(value, + MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL_VALUE), + MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL_VALUE)); gst_structure_set_value(structure, "bitpool", value); /* channels */ @@ -528,6 +567,8 @@ static gboolean gst_a2dp_sink_update_caps(GstA2dpSink *self) g_free(value); + if (self->dev_caps != NULL) + gst_caps_unref(self->dev_caps); self->dev_caps = gst_caps_new_full(structure, NULL); tmp = gst_caps_to_string(self->dev_caps); @@ -549,8 +590,6 @@ static gboolean gst_a2dp_sink_get_capabilities(GstA2dpSink *self) req->h.msg_type = BT_GETCAPABILITIES_REQ; strncpy(req->device, self->device, 18); - req->transport = BT_CAPABILITIES_TRANSPORT_A2DP; - req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; io_error = gst_a2dp_sink_audioservice_send(self, &req->h); if (io_error != G_IO_ERROR_NONE) { GST_ERROR_OBJECT(self, "Error while asking device caps"); @@ -570,12 +609,7 @@ static gboolean gst_a2dp_sink_get_capabilities(GstA2dpSink *self) return FALSE; } - if (rsp->transport != BT_CAPABILITIES_TRANSPORT_A2DP) { - GST_ERROR_OBJECT(self, "Non a2dp answer from device"); - return FALSE; - } - - memcpy(&self->data->cfg, rsp, sizeof(*rsp)); + memcpy(&self->data->caps, rsp, sizeof(*rsp)); if (!gst_a2dp_sink_update_caps(self)) { GST_WARNING_OBJECT(self, "failed to update capabilities"); return FALSE; @@ -685,6 +719,7 @@ static gboolean gst_a2dp_sink_configure(GstA2dpSink *self, GstCaps *caps) memset (req, 0, sizeof(buf)); req->h.msg_type = BT_SETCONFIGURATION_REQ; + req->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; strncpy(req->device, self->device, 18); ret = gst_a2dp_sink_init_pkt_conf(self, caps, &req->sbc_capabilities); if (!ret) { @@ -716,6 +751,7 @@ static gboolean gst_a2dp_sink_configure(GstA2dpSink *self, GstCaps *caps) return FALSE; } + memcpy(&self->data->cfg, rsp, sizeof(*rsp)); GST_DEBUG_OBJECT(self, "configuration set"); return TRUE; @@ -801,7 +837,10 @@ static GstCaps* gst_a2dp_sink_get_caps(GstBaseSink *basesink) { GstA2dpSink *self = GST_A2DP_SINK(basesink); - return self->dev_caps ? gst_caps_ref(self->dev_caps): NULL; + if (self->dev_caps) + return gst_caps_ref(self->dev_caps); + + return gst_caps_copy(gst_pad_get_pad_template_caps(GST_BASE_SINK_PAD(self))); } static gboolean gst_a2dp_sink_set_caps(GstBaseSink *basesink, GstCaps *caps) diff --git a/audio/ipc.h b/audio/ipc.h index 0384cfd6..096c29f1 100644 --- a/audio/ipc.h +++ b/audio/ipc.h @@ -115,7 +115,6 @@ struct bt_getcapabilities_req { bt_audio_msg_header_t h; char device[18]; /* Address of the remote Device */ uint8_t transport; /* Requested transport */ - uint8_t access_mode; /* Requested access mode */ } __attribute__ ((packed)); /* BT_GETCAPABILITIES_RSP */ @@ -165,8 +164,6 @@ struct bt_getcapabilities_rsp { bt_audio_msg_header_t h; uint8_t posix_errno; uint8_t transport; /* Granted transport */ - uint8_t access_mode; /* Granted access mode */ - uint16_t link_mtu; /* Max length that transport supports */ sbc_capabilities_t sbc_capabilities; /* A2DP only */ mpeg_capabilities_t mpeg_capabilities; /* A2DP only */ uint16_t sampling_rate; /* SCO only */ @@ -176,6 +173,8 @@ struct bt_getcapabilities_rsp { struct bt_setconfiguration_req { bt_audio_msg_header_t h; char device[18]; /* Address of the remote Device */ + uint8_t transport; /* Requested transport */ + uint8_t access_mode; /* Requested access mode */ sbc_capabilities_t sbc_capabilities; /* A2DP only - only one of this field and next one must be filled */ mpeg_capabilities_t mpeg_capabilities; /* A2DP only */ @@ -184,6 +183,9 @@ struct bt_setconfiguration_req { /* BT_SETCONFIGURATION_RSP */ struct bt_setconfiguration_rsp { bt_audio_msg_header_t h; + uint8_t transport; /* Granted transport */ + uint8_t access_mode; /* Granted access mode */ + uint16_t link_mtu; /* Max length that transport supports */ uint8_t posix_errno; } __attribute__ ((packed)); diff --git a/audio/pcm_bluetooth.c b/audio/pcm_bluetooth.c index a5bfcdf9..a177274a 100644 --- a/audio/pcm_bluetooth.c +++ b/audio/pcm_bluetooth.c @@ -69,6 +69,17 @@ #define SCO_RXBUFS 0x04 #endif +#ifndef MIN +# define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +# define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2 + /* adapted from glibc sys/time.h timersub() macro */ #define priv_timespecsub(a, b, result) \ do { \ @@ -411,6 +422,120 @@ static int bluetooth_prepare(snd_pcm_ioplug_t *io) return write(data->pipefd[1], &c, 1); } +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case BT_A2DP_SAMPLING_FREQ_16000: + case BT_A2DP_SAMPLING_FREQ_32000: + return 53; + case BT_A2DP_SAMPLING_FREQ_44100: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + DBG("Invalid channel mode %u", mode); + return 53; + } + case BT_A2DP_SAMPLING_FREQ_48000: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + DBG("Invalid channel mode %u", mode); + return 51; + } + default: + DBG("Invalid sampling freq %u", freq); + return 53; + } +} + +static int select_sbc_params(sbc_capabilities_t *cap, unsigned int rate, + unsigned int channels) +{ + unsigned int max_bitpool, min_bitpool; + + switch (rate) { + case 48000: + cap->frequency = BT_A2DP_SAMPLING_FREQ_48000; + break; + case 44100: + cap->frequency = BT_A2DP_SAMPLING_FREQ_44100; + break; + case 32000: + cap->frequency = BT_A2DP_SAMPLING_FREQ_32000; + break; + case 16000: + cap->frequency = BT_A2DP_SAMPLING_FREQ_16000; + break; + default: + DBG("Rate %d not supported", rate); + return -1; + } + + if (channels == 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + } else { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (!cap->channel_mode) { + DBG("No supported channel modes"); + return -1; + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + DBG("No supported block lengths"); + return -1; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + DBG("No supported subbands"); + return -1; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, cap->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + cap->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return 0; +} + + static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params) { @@ -419,7 +544,8 @@ static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, char buf[BT_AUDIO_IPC_PACKET_SIZE]; struct bt_setconfiguration_req *setconf_req = (void*) buf; struct bt_setconfiguration_rsp *setconf_rsp = (void*) buf; - int err; + unsigned int rate, channels; + int err, dir; sbc_capabilities_t active_capabilities; DBG("Preparing with io->period_size=%lu io->buffer_size=%lu", @@ -428,11 +554,20 @@ static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, /* FIXME: this needs to be really implemented (take into account real asoundrc settings + ALSA hw settings ) once server side sends us more than one possible configuration */ + snd_pcm_hw_params_get_rate(params, &rate, &dir); + snd_pcm_hw_params_get_channels(params, &channels); + err = select_sbc_params(&a2dp->sbc_capabilities, rate, channels); + if (err < 0) + return err; + active_capabilities = a2dp->sbc_capabilities; memset(setconf_req, 0, BT_AUDIO_IPC_PACKET_SIZE); setconf_req->h.msg_type = BT_SETCONFIGURATION_REQ; setconf_req->sbc_capabilities = active_capabilities; + setconf_req->access_mode = (io->stream == SND_PCM_STREAM_PLAYBACK ? + BT_CAPABILITIES_ACCESS_MODE_WRITE : + BT_CAPABILITIES_ACCESS_MODE_READ); err = audioservice_send(data->server.fd, &setconf_req->h); if (err < 0) @@ -450,13 +585,16 @@ static int bluetooth_a2dp_hw_params(snd_pcm_ioplug_t *io, return -setconf_rsp->posix_errno; } + data->transport = setconf_rsp->transport; + data->link_mtu = setconf_rsp->link_mtu; + /* Setup SBC encoder now we agree on parameters */ if (a2dp->sbc_initialized) - sbc_finish(&a2dp->sbc); - - /* FIXME: init using flags? */ - sbc_init(&a2dp->sbc, 0); + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); a2dp->sbc_initialized = 1; + if (active_capabilities.frequency & BT_A2DP_SAMPLING_FREQ_16000) a2dp->sbc.rate = 16000; @@ -1327,11 +1465,11 @@ static int bluetooth_init(struct bluetooth_data *data, snd_pcm_stream_t stream, getcaps_req->transport = alsa_conf->transport; else getcaps_req->transport = BT_CAPABILITIES_TRANSPORT_ANY; - +/* getcaps_req->access_mode = (stream == SND_PCM_STREAM_PLAYBACK ? BT_CAPABILITIES_ACCESS_MODE_WRITE : BT_CAPABILITIES_ACCESS_MODE_READ); - +*/ err = audioservice_send(data->server.fd, &getcaps_req->h); if (err < 0) goto failed; @@ -1348,7 +1486,9 @@ static int bluetooth_init(struct bluetooth_data *data, snd_pcm_stream_t stream, } data->transport = getcaps_rsp->transport; +/* data->link_mtu = getcaps_rsp->link_mtu; +*/ if (getcaps_rsp->transport == BT_CAPABILITIES_TRANSPORT_A2DP) data->a2dp.sbc_capabilities = getcaps_rsp->sbc_capabilities; diff --git a/audio/sink.c b/audio/sink.c index b1fc5b2b..259abf8f 100644 --- a/audio/sink.c +++ b/audio/sink.c @@ -159,7 +159,7 @@ static gboolean stream_setup_retry(gpointer user_data) static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, - void *user_data, struct avdtp_error *err) + struct avdtp_error *err, void *user_data) { struct sink *sink = user_data; struct pending_request *pending; @@ -190,23 +190,212 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, } } +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) +{ + switch (freq) { + case A2DP_SAMPLING_FREQ_16000: + case A2DP_SAMPLING_FREQ_32000: + return 53; + case A2DP_SAMPLING_FREQ_44100: + switch (mode) { + case A2DP_CHANNEL_MODE_MONO: + case A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case A2DP_CHANNEL_MODE_STEREO: + case A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + error("Invalid channel mode %u", mode); + return 53; + } + case A2DP_SAMPLING_FREQ_48000: + switch (mode) { + case A2DP_CHANNEL_MODE_MONO: + case A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case A2DP_CHANNEL_MODE_STEREO: + case A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + error("Invalid channel mode %u", mode); + return 51; + } + default: + error("Invalid sampling freq %u", freq); + return 53; + } +} + +static gboolean select_sbc_params(struct sbc_codec_cap *cap, + struct sbc_codec_cap *supported) +{ + unsigned int max_bitpool, min_bitpool; + + memset(cap, 0, sizeof(struct sbc_codec_cap)); + + cap->cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->cap.media_codec_type = A2DP_CODEC_SBC; + + if (supported->frequency & A2DP_SAMPLING_FREQ_44100) + cap->frequency = A2DP_SAMPLING_FREQ_44100; + else if (supported->frequency & A2DP_SAMPLING_FREQ_48000) + cap->frequency = A2DP_SAMPLING_FREQ_48000; + else if (supported->frequency & A2DP_SAMPLING_FREQ_32000) + cap->frequency = A2DP_SAMPLING_FREQ_32000; + else if (supported->frequency & A2DP_SAMPLING_FREQ_16000) + cap->frequency = A2DP_SAMPLING_FREQ_16000; + else { + error("No supported frequencies"); + return FALSE; + } + + if (supported->channel_mode & A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = A2DP_CHANNEL_MODE_STEREO; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = A2DP_CHANNEL_MODE_DUAL_CHANNEL; + else if (supported->channel_mode & A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = A2DP_CHANNEL_MODE_MONO; + else { + error("No supported channel modes"); + return FALSE; + } + + if (supported->block_length & A2DP_BLOCK_LENGTH_16) + cap->block_length = A2DP_BLOCK_LENGTH_16; + else if (supported->block_length & A2DP_BLOCK_LENGTH_12) + cap->block_length = A2DP_BLOCK_LENGTH_12; + else if (supported->block_length & A2DP_BLOCK_LENGTH_8) + cap->block_length = A2DP_BLOCK_LENGTH_8; + else if (supported->block_length & A2DP_BLOCK_LENGTH_4) + cap->block_length = A2DP_BLOCK_LENGTH_4; + else { + error("No supported block lengths"); + return FALSE; + } + + if (supported->subbands & A2DP_SUBBANDS_8) + cap->subbands = A2DP_SUBBANDS_8; + else if (supported->subbands & A2DP_SUBBANDS_4) + cap->subbands = A2DP_SUBBANDS_4; + else { + error("No supported subbands"); + return FALSE; + } + + if (supported->allocation_method & A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = A2DP_ALLOCATION_LOUDNESS; + else if (supported->allocation_method & A2DP_ALLOCATION_SNR) + cap->allocation_method = A2DP_ALLOCATION_SNR; + + min_bitpool = MAX(MIN_BITPOOL, supported->min_bitpool); + max_bitpool = MIN(default_bitpool(cap->frequency, cap->channel_mode), + supported->max_bitpool); + + cap->min_bitpool = min_bitpool; + cap->max_bitpool = max_bitpool; + + return TRUE; +} + +static gboolean select_capabilities(struct avdtp *session, + struct avdtp_remote_sep *rsep, + GSList **caps) +{ + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + + media_codec = avdtp_get_codec(rsep); + if (!media_codec) + return FALSE; + + select_sbc_params(&sbc_cap, (struct sbc_codec_cap *) media_codec->data); + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + *caps = g_slist_append(*caps, media_transport); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + *caps = g_slist_append(*caps, media_codec); + + + return TRUE; +} + +static void discovery_complete(struct avdtp *session, GSList *seps, struct avdtp_error *err, + void *user_data) +{ + struct sink *sink = user_data; + struct pending_request *pending; + struct avdtp_local_sep *lsep; + struct avdtp_remote_sep *rsep; + GSList *caps = NULL; + int id; + + pending = sink->connect; + + if (err) { + avdtp_unref(sink->session); + sink->session = NULL; + if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO + && avdtp_error_posix_errno(err) != EHOSTDOWN) { + debug("connect:connect XCASE detected"); + g_timeout_add(STREAM_SETUP_RETRY_TIMER, + stream_setup_retry, sink); + } else + goto failed; + 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"); + goto failed; + } + + if (!select_capabilities(session, rsep, &caps)) { + error("Unable to select remote SEP capabilities"); + goto failed; + } + + id = a2dp_source_config(sink->session, stream_setup_complete, + caps, sink); + if (id == 0) + goto failed; + + pending->id = id; + return; + +failed: + pending_request_free(pending); + sink->connect = NULL; + avdtp_unref(sink->session); + sink->session = NULL; + error_failed(pending->conn, pending->msg, "Stream setup failed"); +} + static DBusHandlerResult sink_connect(DBusConnection *conn, DBusMessage *msg, void *data) { struct device *dev = data; struct sink *sink = dev->sink; struct pending_request *pending; - unsigned int id; if (!sink->session) sink->session = avdtp_get(&dev->src, &dev->dst); if (!sink->session) - return error_failed(conn, msg, - "Unable to get a session"); + return error_failed(conn, msg, "Unable to get a session"); if (sink->connect || sink->disconnect) - return error_in_progress(conn, msg, "Device connection already in progress"); + return error_in_progress(conn, msg, "Device connection" + "already in progress"); if (sink->state >= AVDTP_STATE_OPEN) return error_already_connected(conn, msg); @@ -216,21 +405,10 @@ static DBusHandlerResult sink_connect(DBusConnection *conn, pending->msg = dbus_message_ref(msg); sink->connect = pending; - id = a2dp_source_request_stream(sink->session, FALSE, - stream_setup_complete, sink, - NULL); - if (id == 0) { - pending_request_free(pending); - sink->connect = NULL; - avdtp_unref(sink->session); - sink->session = NULL; - return error_failed(conn, msg, "Failed to request a stream"); - } + avdtp_discover(sink->session, discovery_complete, sink); debug("stream creation in progress"); - pending->id = id; - return DBUS_HANDLER_RESULT_HANDLED; } diff --git a/audio/unix.c b/audio/unix.c index ea96bd4f..c321b99b 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -71,7 +71,7 @@ struct headset_data { struct unix_client { struct device *dev; - struct avdtp_service_capability *media_codec; + GSList *caps; service_type_t type; char *interface; union { @@ -83,18 +83,13 @@ struct unix_client { int data_fd; /* To be deleted once two phase configuration is fully implemented */ unsigned int req_id; unsigned int cb_id; - gboolean (*cancel_stream) (struct device *dev, unsigned int id); + gboolean (*cancel) (struct device *dev, unsigned int id); }; static GSList *clients = NULL; static int unix_sock = -1; -static void unix_ipc_sendmsg(struct unix_client *client, - const bt_audio_msg_header_t *msg); - -static void send_getcapabilities_rsp_error(struct unix_client *client, int err); - static void client_free(struct unix_client *client) { struct a2dp_data *a2dp; @@ -118,8 +113,10 @@ static void client_free(struct unix_client *client) if (client->sock >= 0) close(client->sock); - if (client->media_codec) - g_free(client->media_codec); + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } g_free(client->interface); g_free(client); @@ -152,6 +149,27 @@ static int unix_sendmsg_fd(int sock, int fd) return sendmsg(sock, &msgh, MSG_NOSIGNAL); } +static void unix_ipc_sendmsg(struct unix_client *client, + const bt_audio_msg_header_t *msg) +{ + info("Audio API: sending %s", bt_audio_strmsg(msg->msg_type)); + + if (send(client->sock, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) < 0) + error("Error %s(%d)", strerror(errno), errno); +} + +static void unix_ipc_error(struct unix_client *client, int type, int err) +{ + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_getcapabilities_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = type; + rsp->posix_errno = err; + + unix_ipc_sendmsg(client, &rsp->h); +} + static service_type_t select_service(struct device *dev, const char *interface) { if (!interface) { @@ -198,8 +216,8 @@ static void stream_state_changed(struct avdtp_stream *stream, break; } } - -static void headset_setup_complete(struct device *dev, void *user_data) +/* +static void headset_discovery_complete(struct device *dev, void *user_data) { struct unix_client *client = user_data; char buf[BT_AUDIO_IPC_PACKET_SIZE]; @@ -209,7 +227,7 @@ static void headset_setup_complete(struct device *dev, void *user_data) client->req_id = 0; if (!dev) { - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); client->dev = NULL; return; } @@ -231,7 +249,7 @@ static void headset_setup_complete(struct device *dev, void *user_data) if (!headset_lock(dev, hs->lock)) { error("Unable to lock headset"); - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); client->dev = NULL; return; } @@ -240,25 +258,131 @@ static void headset_setup_complete(struct device *dev, void *user_data) rsp->h.msg_type = BT_GETCAPABILITIES_RSP; rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO; - rsp->access_mode = client->access_mode; - rsp->link_mtu = 48; rsp->sampling_rate = 8000; client->data_fd = headset_get_sco_fd(dev); unix_ipc_sendmsg(client, &rsp->h); } +*/ +static void headset_setup_complete(struct device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_setconfiguration_rsp *rsp = (void *) buf; + struct headset_data *hs = &client->d.hs; -static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, - struct avdtp_stream *stream, - void *user_data, struct avdtp_error *err) + client->req_id = 0; + + if (!dev) { + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + client->dev = NULL; + return; + } + + switch (client->access_mode) { + case BT_CAPABILITIES_ACCESS_MODE_READ: + hs->lock = HEADSET_LOCK_READ; + break; + case BT_CAPABILITIES_ACCESS_MODE_WRITE: + hs->lock = HEADSET_LOCK_WRITE; + break; + case BT_CAPABILITIES_ACCESS_MODE_READWRITE: + hs->lock = HEADSET_LOCK_READ | HEADSET_LOCK_WRITE; + break; + default: + hs->lock = 0; + break; + } + + if (!headset_lock(dev, hs->lock)) { + error("Unable to lock headset"); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + client->dev = NULL; + return; + } + + memset(buf, 0, sizeof(buf)); + + rsp->h.msg_type = BT_SETCONFIGURATION_RSP; + rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO; + rsp->access_mode = client->access_mode; + + client->data_fd = headset_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void a2dp_discovery_complete(struct avdtp *session, GSList *seps, + struct avdtp_error *err, + void *user_data) { struct unix_client *client = user_data; char buf[BT_AUDIO_IPC_PACKET_SIZE]; struct bt_getcapabilities_rsp *rsp = (void *) buf; - struct avdtp_service_capability *cap; - struct avdtp_media_codec_capability *codec_cap; - struct sbc_codec_cap *sbc_cap; + struct a2dp_data *a2dp = &client->d.a2dp; + struct sbc_codec_cap *sbc_cap = NULL; + GSList *l; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + client->req_id = 0; + + rsp->h.msg_type = BT_GETCAPABILITIES_RSP; + rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + + for (l = seps; l; l = g_slist_next(l)) { + struct avdtp_remote_sep *rsep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap; + + cap = avdtp_get_codec(rsep); + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec_cap = (void *) cap->data; + + if (codec_cap->media_codec_type == A2DP_CODEC_SBC && !sbc_cap) + sbc_cap = (void *) codec_cap; + + } + + /* endianess prevent direct cast */ + if (sbc_cap) { + rsp->sbc_capabilities.channel_mode = sbc_cap->channel_mode; + rsp->sbc_capabilities.frequency = sbc_cap->frequency; + rsp->sbc_capabilities.allocation_method = sbc_cap->allocation_method; + rsp->sbc_capabilities.subbands = sbc_cap->subbands; + rsp->sbc_capabilities.block_length = sbc_cap->block_length; + rsp->sbc_capabilities.min_bitpool = sbc_cap->min_bitpool; + rsp->sbc_capabilities.max_bitpool = sbc_cap->max_bitpool; + } + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_setconfiguration_rsp *rsp = (void *) buf; struct a2dp_data *a2dp = &client->d.a2dp; uint16_t imtu, omtu; GSList *caps; @@ -282,40 +406,13 @@ static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, 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; - } - - rsp->h.msg_type = BT_GETCAPABILITIES_RSP; + rsp->h.msg_type = BT_SETCONFIGURATION_RSP; rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP; client->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; rsp->access_mode = client->access_mode; /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */ rsp->link_mtu = omtu; - sbc_cap = (void *) codec_cap; - - /* assignations below are ok as soon as newipc.h and a2dp.h are kept */ - /* in sync. However it is not possible to cast a struct to another */ - /* dues to endianess issues */ - rsp->sbc_capabilities.channel_mode = sbc_cap->channel_mode; - rsp->sbc_capabilities.frequency = sbc_cap->frequency; - rsp->sbc_capabilities.allocation_method = sbc_cap->allocation_method; - rsp->sbc_capabilities.subbands = sbc_cap->subbands; - rsp->sbc_capabilities.block_length = sbc_cap->block_length; - rsp->sbc_capabilities.min_bitpool = sbc_cap->min_bitpool; - rsp->sbc_capabilities.max_bitpool = sbc_cap->max_bitpool; - unix_ipc_sendmsg(client, &rsp->h); client->cb_id = avdtp_stream_add_cb(session, stream, @@ -324,12 +421,86 @@ static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, return; failed: - error("stream setup failed"); + error("setup failed"); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_streamstart_rsp *rsp = (void *) buf; + struct bt_datafd_ind *ind = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = BT_STREAMSTART_RSP; + rsp->posix_errno = 0; + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.msg_type = BT_STREAMFD_IND; + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("resume failed"); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + unix_ipc_error(client, BT_STREAMSTART_REQ, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_suspend_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_streamstart_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = BT_STREAMSTOP_RSP; + rsp->posix_errno = 0; + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + if (a2dp->sep) { a2dp_sep_unlock(a2dp->sep, a2dp->session); a2dp->sep = NULL; } - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_STREAMSTOP_REQ, EIO); avdtp_unref(a2dp->session); @@ -337,10 +508,11 @@ failed: a2dp->stream = NULL; } -static void create_stream(struct device *dev, struct unix_client *client) +static void start_discovery(struct device *dev, struct unix_client *client) { struct a2dp_data *a2dp; unsigned int id; + int err = 0; client->type = select_service(dev, client->interface); @@ -356,18 +528,55 @@ static void create_stream(struct device *dev, struct unix_client *client) goto failed; } - /* FIXME: The provided media_codec breaks bitpool - selection. So disable it. This needs fixing */ - id = a2dp_source_request_stream(a2dp->session, - TRUE, a2dp_setup_complete, - client, - NULL/*client->media_codec*/); - client->cancel_stream = a2dp_source_cancel_stream; + err = avdtp_discover(a2dp->session, a2dp_discovery_complete, + client); + if (err) + goto failed; break; case TYPE_HEADSET: id = headset_request_stream(dev, headset_setup_complete, client); - client->cancel_stream = headset_cancel_stream; + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, err ? : EIO); +} + +static void start_config(struct device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + unsigned int id; + + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + id = a2dp_source_config(a2dp->session, a2dp_config_complete, + client->caps, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; break; default: @@ -376,7 +585,7 @@ static void create_stream(struct device *dev, struct unix_client *client) } if (id == 0) { - error("request_stream failed"); + error("config failed"); goto failed; } @@ -386,37 +595,117 @@ static void create_stream(struct device *dev, struct unix_client *client) return; failed: - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO); } -static void create_cb(struct device *dev, void *user_data) +static void start_resume(struct device *dev, struct unix_client *client) { - struct unix_client *client = user_data; + struct a2dp_data *a2dp; + unsigned int id; - if (!dev) - send_getcapabilities_rsp_error(client, EIO); - else - create_stream(dev, client); + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_source_resume(a2dp->session, a2dp->sep, + a2dp_resume_complete, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("resume failed"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_STREAMSTART_RSP, EIO); } -static void unix_ipc_sendmsg(struct unix_client *client, - const bt_audio_msg_header_t *msg) +static void start_suspend(struct device *dev, struct unix_client *client) { - info("Audio API: sending %s", bt_audio_strmsg(msg->msg_type)); - if (send(client->sock, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) < 0) - error("Error %s(%d)", strerror(errno), errno); + struct a2dp_data *a2dp; + unsigned int id; + + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_source_suspend(a2dp->session, a2dp->sep, + a2dp_suspend_complete, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("suspend failed"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_STREAMSTOP_RSP, EIO); } -static void send_getcapabilities_rsp_error(struct unix_client *client, int err) +static void create_cb(struct device *dev, void *user_data) { - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_getcapabilities_rsp *rsp = (void *) buf; - - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_GETCAPABILITIES_RSP; - rsp->posix_errno = err; + struct unix_client *client = user_data; - unix_ipc_sendmsg(client, &rsp->h); + if (!dev) + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + else + start_discovery(dev, client); } static void handle_getcapabilities_req(struct unix_client *client, @@ -427,13 +716,6 @@ static void handle_getcapabilities_req(struct unix_client *client, str2ba(req->device, &bdaddr); - if (!req->access_mode) { - send_getcapabilities_rsp_error(client, EINVAL); - return; - } - - client->access_mode = req->access_mode; - if (client->interface) { g_free(client->interface); client->interface = NULL; @@ -444,8 +726,6 @@ static void handle_getcapabilities_req(struct unix_client *client, else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) client->interface = g_strdup(AUDIO_SINK_INTERFACE); - client->media_codec = 0; - if (!manager_find_device(&bdaddr, NULL, FALSE)) { if (!bacmp(&bdaddr, BDADDR_ANY)) goto failed; @@ -461,63 +741,118 @@ static void handle_getcapabilities_req(struct unix_client *client, if (!dev) goto failed; - create_stream(dev, client); + start_discovery(dev, client); return; failed: - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); } static void handle_setconfiguration_req(struct unix_client *client, struct bt_setconfiguration_req *req) { - /* FIXME: for now we just blindly assume that we receive is the - only valid configuration sent.*/ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_setconfiguration_rsp *rsp = (void *) buf; + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + struct device *dev; + bdaddr_t bdaddr; + int err = 0; - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_SETCONFIGURATION_RSP; - rsp->posix_errno = 0; + if (!req->access_mode) { + err = EINVAL; + goto failed; + } - unix_ipc_sendmsg(client, &rsp->h); + str2ba(req->device, &bdaddr); + + if (client->interface) { + g_free(client->interface); + client->interface = NULL; + } + + if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + + if (!manager_find_device(&bdaddr, NULL, FALSE)) { + if (!bacmp(&bdaddr, BDADDR_ANY)) + goto failed; + if (!manager_create_device(&bdaddr, create_cb, client)) + goto failed; + return; + } + + dev = manager_find_device(&bdaddr, client->interface, TRUE); + if (!dev) + dev = manager_find_device(&bdaddr, client->interface, FALSE); + + if (!dev) + goto failed; + + client->access_mode = req->access_mode; + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + client->caps = g_slist_append(client->caps, media_transport); + + memset(&sbc_cap, 0, sizeof(sbc_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + sbc_cap.channel_mode = req->sbc_capabilities.channel_mode; + sbc_cap.frequency = req->sbc_capabilities.frequency; + sbc_cap.allocation_method = req->sbc_capabilities.allocation_method; + sbc_cap.subbands = req->sbc_capabilities.subbands; + sbc_cap.block_length = req->sbc_capabilities.block_length ; + sbc_cap.min_bitpool = req->sbc_capabilities.min_bitpool; + sbc_cap.max_bitpool = req->sbc_capabilities.max_bitpool; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + client->caps = g_slist_append(client->caps, media_codec); + + start_config(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, err ? : EIO); } static void handle_streamstart_req(struct unix_client *client, struct bt_streamstart_req *req) { - /* FIXME : to be really implemented */ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_streamstart_rsp *rsp = (void *) buf; - struct bt_datafd_ind *ind = (void *) buf; - - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_STREAMSTART_RSP; - rsp->posix_errno = 0; - unix_ipc_sendmsg(client, &rsp->h); + if (!client->dev) + goto failed; - memset(buf, 0, sizeof(buf)); - ind->h.msg_type = BT_STREAMFD_IND; - unix_ipc_sendmsg(client, &ind->h); + start_resume(client->dev, client); - if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) - error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + return; +failed: + unix_ipc_error(client, BT_STREAMSTART_REQ, EIO); } static void handle_streamstop_req(struct unix_client *client, struct bt_streamstop_req *req) { - /* FIXME : to be implemented */ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_streamstop_rsp *rsp = (void *) buf; + if (!client->dev) + goto failed; - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_STREAMSTOP_RSP; - rsp->posix_errno = 0; + start_suspend(client->dev, client); - unix_ipc_sendmsg(client, &rsp->h); + return; + +failed: + unix_ipc_error(client, BT_STREAMSTOP_REQ, EIO); } static void handle_control_req(struct unix_client *client, @@ -565,8 +900,8 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data) break; } - if (client->cancel_stream && client->req_id > 0) - client->cancel_stream(client->dev, client->req_id); + if (client->cancel && client->req_id > 0) + client->cancel(client->dev, client->req_id); goto failed; } -- cgit