[pulseaudio-discuss] [RFC v2 02/18] bluetooth: Use new API for exposing A2DP MPEG sinks

Frédéric Dalleau frederic.dalleau at linux.intel.com
Thu Mar 22 09:36:26 PDT 2012


From: Arun Raghavan <arun.raghavan at collabora.co.uk>

This replaces a separate A2DP passthrough profile with dynamically
reconfiguring the A2DP stream for MPEG streams if required. The sink
get_formats() function allows the core to introspect whether the
attached headset supports this or not.
---
 src/modules/bluetooth/bluetooth-util.h          |    1 -
 src/modules/bluetooth/module-bluetooth-device.c |  304 +++++++++++++++--------
 2 files changed, 194 insertions(+), 111 deletions(-)

diff --git a/src/modules/bluetooth/bluetooth-util.h b/src/modules/bluetooth/bluetooth-util.h
index 7ca9cb2..2752a69 100644
--- a/src/modules/bluetooth/bluetooth-util.h
+++ b/src/modules/bluetooth/bluetooth-util.h
@@ -58,7 +58,6 @@ struct pa_bluetooth_uuid {
 enum profile {
     PROFILE_A2DP,
     PROFILE_A2DP_SOURCE,
-    PROFILE_A2DP_PASSTHROUGH,
     PROFILE_HSP,
     PROFILE_HFGW,
     PROFILE_OFF
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 049b0a8..6b97191 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -102,10 +102,17 @@ static const char* const valid_modargs[] = {
     NULL
 };
 
+typedef enum {
+    A2DP_MODE_SBC,
+    A2DP_MODE_MPEG,
+} a2dp_mode_t;
+
 struct a2dp_info {
     sbc_capabilities_t sbc_capabilities;
     mpeg_capabilities_t mpeg_capabilities;
-    pa_bool_t           has_mpeg;
+
+    a2dp_mode_t mode;
+    pa_bool_t has_mpeg;
 
     sbc_t sbc;                           /* Codec data */
     pa_bool_t sbc_initialized;           /* Keep track if the encoder is initialized */
@@ -188,7 +195,7 @@ struct userdata {
 
     pa_bool_t filter_added;
 
-    /* required for PASSTHROUGH profile */
+    /* required for MPEG transport */
     size_t leftover_bytes;
 };
 
@@ -321,7 +328,6 @@ static int parse_mpeg_caps(struct userdata *u, uint8_t seid, const struct bt_get
 
     pa_assert(u);
     pa_assert(rsp);
-    pa_assert( u->profile == PROFILE_A2DP_PASSTHROUGH );
 
     u->a2dp.has_mpeg = FALSE;
 
@@ -428,6 +434,7 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
             return codec->seid;
 
         memcpy(&u->a2dp.sbc_capabilities, codec, sizeof(u->a2dp.sbc_capabilities));
+        pa_log_info("SBC caps detected");
 
     } else if (u->profile == PROFILE_A2DP_SOURCE) {
 
@@ -472,8 +479,7 @@ static int get_caps_msg(struct userdata *u, uint8_t seid, bt_getcaps_msg_t *msg)
     msg->getcaps_req.seid = seid;
 
     pa_strlcpy(msg->getcaps_req.object, u->path, sizeof(msg->getcaps_req.object));
-    if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH ||
-        u->profile == PROFILE_A2DP_SOURCE)
+    if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE)
         msg->getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
     else {
         pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
@@ -514,27 +520,28 @@ static int get_caps(struct userdata *u, uint8_t seid) {
             return -1;
     }
 
-    if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-        seid = 0;
+    seid = 0;
 
+    if (u->profile == PROFILE_A2DP) {
         /* try to find mpeg end-point */
         if (get_caps_msg(u, seid, &msg) < 0)
-            return -1;
+            return 0;
 
         ret = parse_mpeg_caps(u, seid, &msg.getcaps_rsp);
         if (ret < 0)
-            return -1;
+            return 0;
 
         if (ret > 0) {
             /* refine seid caps */
             if (get_caps_msg(u, ret, &msg) < 0)
-                return -1;
+                return 0;
 
             ret = parse_mpeg_caps(u, ret, &msg.getcaps_rsp);
             if (ret < 0)
-                return -1;
+                return 0;
         }
     }
+
     return 0;
 }
 
@@ -600,54 +607,10 @@ static int setup_a2dp(struct userdata *u) {
     };
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH || u->profile == PROFILE_A2DP_SOURCE);
-
-    if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-        mpeg_capabilities_t *mcap;
-
-        if (u->a2dp.has_mpeg) {
-            int rate;
-
-            mcap = &u->a2dp.mpeg_capabilities;
-            rate = u->sample_spec.rate;
-
-            if (u->sample_spec.channels == 1)
-                mcap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
-            else
-                mcap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
-
-            mcap->crc = 0; /* CRC is broken in some encoders */
-            mcap->layer = BT_MPEG_LAYER_3;
-
-            if (rate == 44100)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_44100;
-            else if (rate == 48000)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_48000;
-            else if (rate == 32000)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_32000;
-            else if (rate == 24000)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_24000;
-            else if (rate == 22050)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_22050;
-            else if (rate == 16000)
-                mcap->frequency = BT_MPEG_SAMPLING_FREQ_16000;
-            else {
-                pa_log("unsupported sampling frequency");
-                return -1;
-            }
-
-            mcap->mpf = 0; /* don't use optional IETF payload, send raw frames */
-            mcap->bitrate = 0x8000; /* set for vbr, this covers all cases */
+    pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE);
 
-            return 0;
-        }
-        else {
-            pa_log("setup_a2dp: Trying to set-up A2DP Passthrough configuration but no MPEG endpoint available");
-            return -1; /* return error, profile will not be loaded */
-        }
-    }
+    mpeg_capabilities_t *mcap;
 
-    /* other profiles */
     cap = &u->a2dp.sbc_capabilities;
 
     /* Find the lowest freq that is at least as high as the requested
@@ -735,6 +698,42 @@ static int setup_a2dp(struct userdata *u) {
     cap->min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool);
     cap->max_bitpool = (uint8_t) PA_MIN(a2dp_default_bitpool(cap->frequency, cap->channel_mode), cap->max_bitpool);
 
+    /* Now convigure the MPEG caps if we have them */
+    if (u->a2dp.has_mpeg) {
+        int rate;
+
+        mcap = &u->a2dp.mpeg_capabilities;
+        rate = u->sample_spec.rate;
+
+        if (u->sample_spec.channels == 1)
+            mcap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+        else
+            mcap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+
+        mcap->crc = 0; /* CRC is broken in some encoders */
+        mcap->layer = BT_MPEG_LAYER_3;
+
+        if (rate == 44100)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_44100;
+        else if (rate == 48000)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_48000;
+        else if (rate == 32000)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_32000;
+        else if (rate == 24000)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_24000;
+        else if (rate == 22050)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_22050;
+        else if (rate == 16000)
+            mcap->frequency = BT_MPEG_SAMPLING_FREQ_16000;
+        else {
+            pa_log("unsupported MPEG sampling frequency");
+            return -1;
+        }
+
+        mcap->mpf = 0; /* don't use optional IETF payload, send raw frames */
+        mcap->bitrate = 0x8000; /* set for vbr, this covers all cases */
+    }
+
     return 0;
 }
 
@@ -853,15 +852,16 @@ static int set_conf(struct userdata *u) {
     pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
 
     if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
-        msg.open_req.seid = u->a2dp.sbc_capabilities.capability.seid;
-    } else if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-        pa_assert(u->a2dp.has_mpeg);
-        msg.open_req.seid = u->a2dp.mpeg_capabilities.capability.seid;
-    } else {
+        if (u->a2dp.mode == A2DP_MODE_SBC)
+            msg.open_req.seid = u->a2dp.sbc_capabilities.capability.seid;
+        else if (u->a2dp.mode == A2DP_MODE_MPEG) {
+            pa_assert(u->a2dp.has_mpeg);
+            msg.open_req.seid = u->a2dp.mpeg_capabilities.capability.seid;
+        }
+    } else
         msg.open_req.seid = BT_A2DP_SEID_RANGE + 1;
-    }
 
-    msg.open_req.lock = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
+    msg.open_req.lock = u->profile == PROFILE_A2DP ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
 
     if (service_send(u, &msg.open_req.h) < 0)
         return -1;
@@ -869,7 +869,7 @@ static int set_conf(struct userdata *u) {
     if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
         return -1;
 
-    if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH || u->profile == PROFILE_A2DP_SOURCE ) {
+    if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE ) {
 
         /* for passthrough we expect little-endian data to be compatible with the ALSA iec958
            devices */
@@ -891,10 +891,10 @@ static int set_conf(struct userdata *u) {
     msg.setconf_req.h.length = sizeof(msg.setconf_req);
 
     if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
-        memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
-    } else if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-        pa_assert(u->a2dp.has_mpeg);
-        memcpy(&msg.setconf_req.codec, &u->a2dp.mpeg_capabilities, sizeof(u->a2dp.mpeg_capabilities));
+        if (u->a2dp.mode == A2DP_MODE_SBC)
+            memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities, sizeof(u->a2dp.sbc_capabilities));
+        else if (u->a2dp.mode == A2DP_MODE_MPEG)
+            memcpy(&msg.setconf_req.codec, &u->a2dp.mpeg_capabilities, sizeof(u->a2dp.mpeg_capabilities));
     } else {
         msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
         msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1;
@@ -912,21 +912,21 @@ static int set_conf(struct userdata *u) {
 
     /* setup SBC encoder now we agree on parameters */
     if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
-        setup_sbc(&u->a2dp, u->profile);
+        if (u->a2dp.mode == A2DP_MODE_SBC) {
+            setup_sbc(&u->a2dp, u->profile);
 
-        u->block_size =
-            ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct sbc_rtp_payload))
-             / u->a2dp.frame_length
-             * u->a2dp.codesize);
+            u->block_size =
+                ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct sbc_rtp_payload))
+                 / u->a2dp.frame_length
+                 * u->a2dp.codesize);
 
-        pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
+            pa_log_info("SBC parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n",
                     u->a2dp.sbc.allocation, u->a2dp.sbc.subbands, u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool);
-    } else if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-
-        /* available payload per packet */
-        u->block_size = 1152*4; /* this is the size of an IEC61937 frame for MPEG layer 3 */
-        u->leftover_bytes=0;
-
+        } else if (u->a2dp.mode == A2DP_MODE_MPEG) {
+            /* available payload per packet */
+            u->block_size = 1152*4; /* this is the size of an IEC61937 frame for MPEG layer 3 */
+            u->leftover_bytes = 0;
+        }
     } else
         u->block_size = u->link_mtu;
 
@@ -1083,6 +1083,26 @@ static int stop_stream_fd(struct userdata *u) {
     return r;
 }
 
+static int close_stream(struct userdata *u) {
+    union {
+        struct bt_close_req close_req;
+        struct bt_close_rsp close_rsp;
+        bt_audio_error_t error;
+        uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+    } msg;
+
+    memset(&msg, 0, sizeof(msg));
+    msg.close_req.h.type = BT_REQUEST;
+    msg.close_req.h.name = BT_CLOSE;
+    msg.close_req.h.length = sizeof(msg.close_req);
+
+    if (service_send(u, &msg.close_req.h) < 0)
+        return -1;
+
+    if (service_expect(u, &msg.close_rsp.h, sizeof(msg), BT_CLOSE, sizeof(msg.close_rsp)) < 0)
+        return -1;
+}
+
 static void bt_transport_release(struct userdata *u) {
     const char *accesstype = "rw";
     const pa_bluetooth_transport *t;
@@ -1162,6 +1182,56 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
 
     switch (code) {
 
+        case PA_SINK_MESSAGE_ADD_INPUT: {
+            /* If you change anything here, make sure to change the
+             * sink input handling a few lines down at
+             * PA_SINK_MESSAGE_FINISH_MOVE, too. */
+
+            /* FIXME: returning failure here causes the server to just die.
+             * Would be better to be able to abort the stream gracefully. */
+
+            pa_sink_input *i = PA_SINK_INPUT(data);
+            a2dp_mode_t mode;
+
+            if (u->profile == PROFILE_A2DP) {
+                switch(i->format->encoding) {
+                    case PA_ENCODING_PCM:
+                        mode = A2DP_MODE_SBC;
+                        break;
+
+                    case PA_ENCODING_MPEG_IEC61937:
+                        pa_assert(u->a2dp.has_mpeg);
+                        mode = A2DP_MODE_MPEG;
+                        break;
+
+                    default:
+                        pa_assert_not_reached();
+                }
+
+                if (PA_UNLIKELY(mode != u->a2dp.mode)) {
+                    /* FIXME: Just suspend should suffice? This resets the smoother */
+                    if (stop_stream_fd(u) < 0 || close_stream(u) < 0) {
+                        failed = TRUE;
+                        break;
+                    }
+
+                    u->a2dp.mode = mode;
+                    if (set_conf(u) < 0) {
+                        failed = TRUE;
+                        break;
+                    }
+
+                    if (start_stream_fd(u) < 0) {
+                        failed = TRUE;
+                        break;
+                    }
+                }
+            }
+
+            break;
+        }
+
+
         case PA_SINK_MESSAGE_SET_STATE:
 
             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
@@ -1766,7 +1836,6 @@ static int a2dp_passthrough_process_render(struct userdata *u) {
     int ret = 0;
 
     pa_assert(u);
-    pa_assert(u->profile == PROFILE_A2DP_PASSTHROUGH);
     pa_assert(u->sink);
 
     /* inits for output buffer */
@@ -2246,11 +2315,14 @@ static void thread_func(void *userdata) {
                         u->started_at = pa_rtclock_now();
 
                     if (u->profile == PROFILE_A2DP) {
-                        if ((n_written = a2dp_process_render(u)) < 0)
-                            goto fail;
-                    } else if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
-                        if ((n_written = a2dp_passthrough_process_render(u)) < 0)
-                            goto fail;
+                        if (u->a2dp.mode == A2DP_MODE_SBC) {
+                            if ((n_written = a2dp_process_render(u)) < 0)
+                                goto fail;
+                        } else if (u->a2dp.mode == A2DP_MODE_MPEG) {
+                            if ((n_written = a2dp_passthrough_process_render(u)) < 0)
+                                goto fail;
+                        } else
+                            pa_assert_not_reached();
                     } else {
                         if ((n_written = hsp_process_render(u)) < 0)
                             goto fail;
@@ -2627,6 +2699,31 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct
     return PA_HOOK_OK;
 }
 
+static pa_idxset* sink_get_formats(pa_sink *s) {
+    struct userdata *u;
+    pa_idxset *formats;
+    pa_format_info *f;
+
+    pa_assert(s);
+
+    formats = pa_idxset_new(NULL, NULL);
+
+    f = pa_format_info_new();
+    f->encoding = PA_ENCODING_PCM;
+    pa_idxset_put(formats, f, NULL);
+
+    u = (struct userdata *) s->userdata;
+
+    if (u->profile == PROFILE_A2DP && u->a2dp.has_mpeg) {
+        f = pa_format_info_new();
+        f->encoding = PA_ENCODING_MPEG_IEC61937;
+        /* FIXME: Populate supported rates, layers, ... */
+        pa_idxset_put(formats, f, NULL);
+    }
+
+    return formats;
+}
+
 /* Run from main thread */
 static int add_sink(struct userdata *u) {
     char *k;
@@ -2651,7 +2748,7 @@ static int add_sink(struct userdata *u) {
         data.driver = __FILE__;
         data.module = u->module;
         pa_sink_new_data_set_sample_spec(&data, &u->sample_spec);
-        pa_proplist_sets(data.proplist, "bluetooth.protocol", (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH) ? "a2dp" : "sco");
+        pa_proplist_sets(data.proplist, "bluetooth.protocol", (u->profile == PROFILE_A2DP) ? "a2dp" : "sco");
         if (u->profile == PROFILE_HSP)
             pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
         data.card = u->card;
@@ -2674,13 +2771,13 @@ static int add_sink(struct userdata *u) {
 
         u->sink->userdata = u;
         u->sink->parent.process_msg = sink_process_msg;
+        u->sink->get_formats = sink_get_formats;
 
         pa_sink_set_max_request(u->sink, u->block_size);
         pa_sink_set_fixed_latency(u->sink,
-                                  (((u->profile == PROFILE_A2DP)||
-                                    (u->profile == PROFILE_A2DP_PASSTHROUGH)) ?
-                                   FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) +
-                                  pa_bytes_to_usec(u->block_size, &u->sample_spec));
+                                  ((u->profile == PROFILE_A2DP) ?
+                                   FIXED_LATENCY_PLAYBACK_A2DP :
+                                   FIXED_LATENCY_PLAYBACK_HSP) + pa_bytes_to_usec(u->block_size, &u->sample_spec));
     }
 
     if (u->profile == PROFILE_HSP) {
@@ -2692,9 +2789,6 @@ static int add_sink(struct userdata *u) {
         pa_xfree(k);
     }
 
-    if (u->profile == PROFILE_A2DP_PASSTHROUGH)
-        u->sink->flags |= PA_SINK_PASSTHROUGH;
-
     return 0;
 }
 
@@ -2982,6 +3076,11 @@ static int setup_bt(struct userdata *u) {
 
     pa_log_debug("Got device capabilities");
 
+    if (u->profile == PROFILE_A2DP) {
+        /* Connect for SBC to start with, switch later if required */
+        u->a2dp.mode = A2DP_MODE_SBC;
+    }
+
     if (set_conf(u) < 0)
         return -1;
 
@@ -3007,7 +3106,6 @@ static int init_profile(struct userdata *u) {
         return -1;
 
     if (u->profile == PROFILE_A2DP ||
-        u->profile == PROFILE_A2DP_PASSTHROUGH ||
         u->profile == PROFILE_HSP ||
         u->profile == PROFILE_HFGW)
         if (add_sink(u) < 0)
@@ -3317,19 +3415,6 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
         pa_hashmap_put(data.profiles, p->name, p);
 
-        /* add passthrough profile */
-        p = pa_card_profile_new("passthrough", _("MP3 passthrough (A2DP)"), sizeof(enum profile));
-        p->priority = 5;
-        p->n_sinks = 1;
-        p->n_sources = 0;
-        p->max_sink_channels = 2;
-        p->max_source_channels = 0;
-
-        d = PA_CARD_PROFILE_DATA(p);
-        *d = PROFILE_A2DP_PASSTHROUGH;
-
-        pa_hashmap_put(data.profiles, p->name, p);
-
     }
 
     if (pa_bluetooth_uuid_has(device->uuids, A2DP_SOURCE_UUID)) {
@@ -3404,7 +3489,6 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
 
     if ((device->headset_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HSP) ||
         (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP) ||
-        (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP_PASSTHROUGH) ||
         (device->hfgw_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW)) {
         pa_log_warn("Default profile not connected, selecting off profile");
         u->card->active_profile = pa_hashmap_get(u->card->profiles, "off");
-- 
1.7.5.4



More information about the pulseaudio-discuss mailing list