[pulseaudio-discuss] [RFC - MP3 passthrough over A2DP 2/2] mp3 passthrough: Bluetooth changes
bossart.nospam at gmail.com
bossart.nospam at gmail.com
Tue Sep 21 16:00:56 PDT 2010
From: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart at intel.com>
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 61fe369..d12c4e3 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -109,6 +109,9 @@ static const char* const valid_modargs[] = {
struct a2dp_info {
sbc_capabilities_t sbc_capabilities;
+ mpeg_capabilities_t mpeg_capabilities;
+ pa_bool_t has_mpeg;
+
sbc_t sbc; /* Codec data */
pa_bool_t sbc_initialized; /* Keep track if the encoder is initialized */
size_t codesize, frame_length; /* SBC Codesize, frame_length. We simply cache those values here */
@@ -131,6 +134,7 @@ struct hsp_info {
enum profile {
PROFILE_A2DP,
+ PROFILE_A2DP_PASSTHROUGH,
PROFILE_A2DP_SOURCE,
PROFILE_HSP,
PROFILE_HFGW,
@@ -157,7 +161,7 @@ struct userdata {
pa_rtpoll_item *rtpoll_item;
pa_thread *thread;
- uint64_t read_index, write_index;
+ uint64_t s_read_index, s_write_index; /* count in samples instead of bytes */
pa_usec_t started_at;
pa_smoother *read_smoother;
@@ -169,7 +173,8 @@ struct userdata {
int stream_fd;
size_t link_mtu;
- size_t block_size;
+ size_t block_size; /* in bytes */
+ size_t samples_per_block; /* in samples */
struct a2dp_info a2dp;
struct hsp_info hsp;
@@ -182,8 +187,22 @@ struct userdata {
int service_write_type, service_read_type;
pa_bool_t filter_added;
+
+ /* required for PASSTHROUGH profile */
+ size_t leftover_bytes;
+ pa_memchunk leftover_memchunk; /* storage for bytes that could not be sent
+ in the last packet */
+
+
};
+static pa_usec_t samples_to_usec(uint64_t length, const pa_sample_spec *spec) {
+ pa_assert(spec);
+ pa_return_val_if_fail(pa_sample_spec_valid(spec), 0);
+
+ return ((pa_usec_t) (length * PA_USEC_PER_SEC) / spec->rate);
+}
+
#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_RECORD_A2DP (25*PA_USEC_PER_MSEC)
#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC)
@@ -300,6 +319,68 @@ static ssize_t service_expect(struct userdata*u, bt_audio_msg_header_t *rsp, siz
}
/* Run from main thread */
+static int parse_mpeg_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) {
+ uint16_t bytes_left;
+ const codec_capabilities_t *codec;
+ pa_bool_t has_mpeg=FALSE;
+
+ pa_assert(u);
+ pa_assert(rsp);
+ pa_assert( u->profile == PROFILE_A2DP_PASSTHROUGH );
+
+ bytes_left = rsp->h.length - sizeof(*rsp);
+
+ if (bytes_left < sizeof(codec_capabilities_t)) {
+ pa_log_error("Packet too small to store codec information.");
+ return -1;
+ }
+
+ codec = (codec_capabilities_t *) rsp->data; /** ALIGNMENT? **/
+
+ pa_log_debug("Payload size is %lu %lu", (unsigned long) bytes_left, (unsigned long) sizeof(*codec));
+
+ if ((u->profile == PROFILE_A2DP_PASSTHROUGH) && codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP) {
+ pa_log_error("Got capabilities for wrong codec.");
+ return -1;
+ }
+
+ while (bytes_left > 0) {
+ if ((codec->type == BT_A2DP_MPEG12_SINK) && !codec->lock) {
+ has_mpeg = TRUE;
+ break;
+ }
+ bytes_left -= codec->length;
+ codec = (const codec_capabilities_t*) ((const uint8_t*) codec + codec->length);
+ }
+
+ if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.mpeg_capabilities))
+ return -1;
+
+ pa_assert(codec->type == BT_A2DP_MPEG12_SINK);
+
+ if (codec->configured && seid == 0)
+ return codec->seid;
+
+ has_mpeg = TRUE;
+ memcpy(&u->a2dp.mpeg_capabilities, codec, sizeof(u->a2dp.mpeg_capabilities));
+
+ pa_log_info("MPEG caps detected");
+ pa_log_info("channel_mode %d crc %d layer %d frequency %d mpf %d bitrate %d",
+ u->a2dp.mpeg_capabilities.channel_mode,
+ u->a2dp.mpeg_capabilities.crc,
+ u->a2dp.mpeg_capabilities.layer,
+ u->a2dp.mpeg_capabilities.frequency,
+ u->a2dp.mpeg_capabilities.mpf,
+ u->a2dp.mpeg_capabilities. bitrate);
+
+ u->a2dp.has_mpeg = has_mpeg;
+
+ return 0;
+}
+
+
+
+/* Run from main thread */
static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) {
uint16_t bytes_left;
const codec_capabilities_t *codec;
@@ -380,44 +461,91 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa
return 0;
}
-/* Run from main thread */
-static int get_caps(struct userdata *u, uint8_t seid) {
- union {
- struct bt_get_capabilities_req getcaps_req;
- struct bt_get_capabilities_rsp getcaps_rsp;
- bt_audio_error_t error;
- uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
- } msg;
- int ret;
+typedef union {
+ struct bt_get_capabilities_req getcaps_req;
+ struct bt_get_capabilities_rsp getcaps_rsp;
+ bt_audio_error_t error;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
+} bt_getcaps_msg_t;
+
+
+static int get_caps_msg(struct userdata *u, uint8_t seid, bt_getcaps_msg_t *msg) {
pa_assert(u);
- memset(&msg, 0, sizeof(msg));
- msg.getcaps_req.h.type = BT_REQUEST;
- msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
- msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
- 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_SOURCE)
- msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
+ memset(msg, 0, sizeof(bt_getcaps_msg_t));
+ msg->getcaps_req.h.type = BT_REQUEST;
+ msg->getcaps_req.h.name = BT_GET_CAPABILITIES;
+ msg->getcaps_req.h.length = sizeof(struct bt_get_capabilities_req);
+ 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)
+ msg->getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
else {
pa_assert(u->profile == PROFILE_HSP || u->profile == PROFILE_HFGW);
- msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO;
+ msg->getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO;
}
- msg.getcaps_req.flags = u->auto_connect ? BT_FLAG_AUTOCONNECT : 0;
+ msg->getcaps_req.flags = u->auto_connect ? BT_FLAG_AUTOCONNECT : 0;
- if (service_send(u, &msg.getcaps_req.h) < 0)
+ if (service_send(u, &msg->getcaps_req.h) < 0)
return -1;
- if (service_expect(u, &msg.getcaps_rsp.h, sizeof(msg), BT_GET_CAPABILITIES, 0) < 0)
+ if (service_expect(u, &msg->getcaps_rsp.h, sizeof(bt_getcaps_msg_t), BT_GET_CAPABILITIES, 0) < 0) {
+ pa_log("BT_GET_CAPABILITIES expect failed");
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Run from main thread */
+static int get_caps(struct userdata *u, uint8_t seid) {
+ bt_getcaps_msg_t msg;
+ int ret;
+
+ pa_assert(u);
+
+ if( get_caps_msg(u,seid,&msg) < 0)
return -1;
ret = parse_caps(u, seid, &msg.getcaps_rsp);
- if (ret <= 0)
- return ret;
+ if (ret < 0)
+ return -1;
+
+ if( ret > 0 ) {
+ /* refine seid caps */
+ if( get_caps_msg(u,ret,&msg) < 0)
+ return -1;
+
+ ret = parse_caps(u, ret, &msg.getcaps_rsp);
+ if (ret < 0)
+ return -1;
+ }
+
+ if (u->profile == PROFILE_A2DP_PASSTHROUGH ) {
+ seid = 0;
- return get_caps(u, ret);
+ /* try to find mpeg end-point */
+ if( get_caps_msg(u,seid,&msg) < 0)
+ return -1;
+
+ ret = parse_mpeg_caps(u, seid, &msg.getcaps_rsp);
+ if (ret < 0)
+ return -1;
+
+ if( ret > 0 ) {
+ /* refine seid caps */
+ if( get_caps_msg(u,ret,&msg) < 0)
+ return -1;
+
+ ret = parse_mpeg_caps(u, ret, &msg.getcaps_rsp);
+ if (ret < 0)
+ return -1;
+ }
+ }
+ return 0;
}
/* Run from main thread */
@@ -482,95 +610,142 @@ static int setup_a2dp(struct userdata *u) {
};
pa_assert(u);
- pa_assert(u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE);
+ 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;
+ }
- cap = &u->a2dp.sbc_capabilities;
+ mcap->mpf = 0; /* mpf - we will only use the mandatory one */
- /* Find the lowest freq that is at least as high as the requested
- * sampling rate */
- for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
- if (freq_table[i].rate >= u->sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
- u->sample_spec.rate = freq_table[i].rate;
- cap->frequency = freq_table[i].cap;
- break;
+ /* vbr - we always say its vbr, we don't have how to know it */
+ mcap->bitrate = 0x8000;
+
+ }
+ else {
+ pa_log("setup_a2dp: Trying to set-up A2DP Passthrough configuration but no MPEG endpoint available");
+ return -1; /* this should not happen */
}
+ } else {
- if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
- for (--i; i >= 0; i--) {
- if (cap->frequency & freq_table[i].cap) {
+ cap = &u->a2dp.sbc_capabilities;
+
+ /* Find the lowest freq that is at least as high as the requested
+ * sampling rate */
+ for (i = 0; (unsigned) i < PA_ELEMENTSOF(freq_table); i++)
+ if (freq_table[i].rate >= u->sample_spec.rate && (cap->frequency & freq_table[i].cap)) {
u->sample_spec.rate = freq_table[i].rate;
cap->frequency = freq_table[i].cap;
break;
}
- }
- if (i < 0) {
- pa_log("Not suitable sample rate");
- return -1;
+ if ((unsigned) i == PA_ELEMENTSOF(freq_table)) {
+ for (--i; i >= 0; i--) {
+ if (cap->frequency & freq_table[i].cap) {
+ u->sample_spec.rate = freq_table[i].rate;
+ cap->frequency = freq_table[i].cap;
+ break;
+ }
+ }
+
+ if (i < 0) {
+ pa_log("Not suitable sample rate");
+ return -1;
+ }
}
- }
- pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
+ pa_assert((unsigned) i < PA_ELEMENTSOF(freq_table));
- if (cap->capability.configured)
- return 0;
+ if (cap->capability.configured)
+ return 0;
- if (u->sample_spec.channels <= 1) {
- if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
- cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
- u->sample_spec.channels = 1;
- } else
+ if (u->sample_spec.channels <= 1) {
+ if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+ cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+ u->sample_spec.channels = 1;
+ } else
+ u->sample_spec.channels = 2;
+ }
+
+ if (u->sample_spec.channels >= 2) {
u->sample_spec.channels = 2;
- }
- if (u->sample_spec.channels >= 2) {
- u->sample_spec.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;
+ u->sample_spec.channels = 1;
+ } else {
+ pa_log("No supported channel modes");
+ return -1;
+ }
+ }
- 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;
- u->sample_spec.channels = 1;
- } else {
- pa_log("No supported channel modes");
+ 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 {
+ pa_log_error("No supported block lengths");
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 {
- pa_log_error("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 {
- pa_log_error("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;
+ 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 {
+ pa_log_error("No supported subbands");
+ return -1;
+ }
- 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);
+ 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;
+ 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);
+ }
return 0;
}
@@ -683,17 +858,37 @@ static int set_conf(struct userdata *u) {
msg.open_req.h.length = sizeof(msg.open_req);
pa_strlcpy(msg.open_req.object, u->path, sizeof(msg.open_req.object));
- msg.open_req.seid = (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) ? u->a2dp.sbc_capabilities.capability.seid : BT_A2DP_SEID_RANGE + 1;
- msg.open_req.lock = (u->profile == PROFILE_A2DP) ? BT_WRITE_LOCK : BT_READ_LOCK | BT_WRITE_LOCK;
+
+ 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) {
+ if (u->a2dp.has_mpeg) {
+ msg.open_req.seid = u->a2dp.mpeg_capabilities.capability.seid;
+ } else {
+ pa_log("set_conf(): Trying to open MPEG endpoint but no MPEG endpoint available");
+ return -1;
+ }
+ } 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;
if (service_send(u, &msg.open_req.h) < 0)
return -1;
- if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0)
+ if (service_expect(u, &msg.open_rsp.h, sizeof(msg), BT_OPEN, sizeof(msg.open_rsp)) < 0) {
+ pa_log("BT_OPEN expect failed");
return -1;
+ }
- if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
- u->sample_spec.format = PA_SAMPLE_S16LE;
+ if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH || u->profile == PROFILE_A2DP_SOURCE ) {
+
+ if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
+ u->sample_spec.format = PA_MP3;
+ } else {
+ u->sample_spec.format = PA_SAMPLE_S16LE;
+ }
if (setup_a2dp(u) < 0)
return -1;
@@ -712,6 +907,13 @@ static int set_conf(struct userdata *u) {
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) {
+ if(u->a2dp.has_mpeg) {
+ memcpy(&msg.setconf_req.codec, &u->a2dp.mpeg_capabilities, sizeof(u->a2dp.mpeg_capabilities));
+ } else {
+ pa_log("set_conf(): Trying to configure MPEG endpoint but no MPEG endpoint available");
+ return -1;
+ }
} else {
msg.setconf_req.codec.transport = BT_CAPABILITIES_TRANSPORT_SCO;
msg.setconf_req.codec.seid = BT_A2DP_SEID_RANGE + 1;
@@ -719,28 +921,50 @@ static int set_conf(struct userdata *u) {
}
msg.setconf_req.h.length += msg.setconf_req.codec.length - sizeof(msg.setconf_req.codec);
- if (service_send(u, &msg.setconf_req.h) < 0)
+ if (service_send(u, &msg.setconf_req.h) < 0) {
+ pa_log("BT_SET_CONFIGURATION send failed");
return -1;
-
- if (service_expect(u, &msg.setconf_rsp.h, sizeof(msg), BT_SET_CONFIGURATION, sizeof(msg.setconf_rsp)) < 0)
+ }
+ if (service_expect(u, &msg.setconf_rsp.h, sizeof(msg), BT_SET_CONFIGURATION, sizeof(msg.setconf_rsp)) < 0) {
+ pa_log("BT_SET_CONFIGURATION expect failed");
return -1;
-
+ }
u->link_mtu = msg.setconf_rsp.link_mtu;
/* setup SBC encoder now we agree on parameters */
if (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_SOURCE) {
+
setup_sbc(&u->a2dp);
+ /* number of input bytes per packet */
u->block_size =
- ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
- / u->a2dp.frame_length
- * u->a2dp.codesize);
+ (u->link_mtu - sizeof(struct rtp_header) - sizeof(struct sbc_rtp_payload))
+ / (u->a2dp.frame_length)
+ * (u->a2dp.codesize);
+
+ /* number of samples per packet */
+ u->samples_per_block =
+ (u->link_mtu - sizeof(struct rtp_header) - sizeof(struct sbc_rtp_payload))
+ / (u->a2dp.frame_length)
+ * (u->a2dp.sbc.subbands ? 8 : 4) * (4 + (u->a2dp.sbc.blocks * 4));
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
- u->block_size = u->link_mtu;
+ } else if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
+ /* available payload per packet */
+ u->block_size =
+ ((u->link_mtu - sizeof(struct rtp_header) - sizeof(struct mpeg_rtp_payload)) /4 ) * 4; /* force 32-bit length to simplify synchronization */
+
+ /* FIXME : number of samples per packet */
+ u->samples_per_block = 1152;
+
+ u->leftover_bytes=0;
+
+ } else {
+ u->block_size = u->link_mtu;
+ u->samples_per_block = u->link_mtu/pa_frame_size(&u->sample_spec);
+ }
return 0;
}
@@ -795,7 +1019,7 @@ static int start_stream_fd(struct userdata *u) {
pollfd->fd = u->stream_fd;
pollfd->events = pollfd->revents = 0;
- u->read_index = u->write_index = 0;
+ u->s_read_index = u->s_write_index = 0;
u->started_at = 0;
if (u->source)
@@ -902,14 +1126,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_usec_t wi, ri;
ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
- wi = pa_bytes_to_usec(u->write_index + u->block_size, &u->sample_spec);
+ wi = samples_to_usec(u->s_write_index + u->samples_per_block, &u->sample_spec);
*((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
} else {
pa_usec_t ri, wi;
ri = pa_rtclock_now() - u->started_at;
- wi = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+ wi = samples_to_usec(u->s_write_index, &u->sample_spec);
*((pa_usec_t*) data) = wi > ri ? wi - ri : 0;
}
@@ -975,7 +1199,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off
if (u->read_smoother) {
wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
- ri = pa_bytes_to_usec(u->read_index, &u->sample_spec);
+ ri = samples_to_usec(u->s_read_index, &u->sample_spec);
*((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->thread_info.fixed_latency;
} else
@@ -1043,8 +1267,7 @@ static int hsp_process_render(struct userdata *u) {
ret = -1;
break;
}
-
- u->write_index += (uint64_t) u->write_memchunk.length;
+ u->s_write_index += (uint64_t) u->write_memchunk.length/pa_frame_size(&u->sample_spec);
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
@@ -1111,7 +1334,7 @@ static int hsp_process_push(struct userdata *u) {
pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
memchunk.length = (size_t) l;
- u->read_index += (uint64_t) l;
+ u->s_read_index += (uint64_t) l/pa_frame_size(&u->sample_spec);
for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm))
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) {
@@ -1127,7 +1350,7 @@ static int hsp_process_push(struct userdata *u) {
tstamp = pa_rtclock_now();
}
- pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_put(u->read_smoother, tstamp, samples_to_usec(u->s_read_index, &u->sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, TRUE);
pa_source_post(u->source, &memchunk);
@@ -1157,7 +1380,7 @@ static void a2dp_prepare_buffer(struct userdata *u) {
static int a2dp_process_render(struct userdata *u) {
struct a2dp_info *a2dp;
struct rtp_header *header;
- struct rtp_payload *payload;
+ struct sbc_rtp_payload *payload;
size_t nbytes;
void *d;
const void *p;
@@ -1179,7 +1402,7 @@ static int a2dp_process_render(struct userdata *u) {
a2dp = &u->a2dp;
header = a2dp->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+ payload = (struct sbc_rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
frame_count = 0;
@@ -1237,7 +1460,7 @@ static int a2dp_process_render(struct userdata *u) {
header->v = 2;
header->pt = 1;
header->sequence_number = htons(a2dp->seq_num++);
- header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec));
+ header->timestamp = htonl(u->s_write_index); /* sample count */
header->ssrc = htonl(1);
payload->frame_count = frame_count;
@@ -1275,7 +1498,7 @@ static int a2dp_process_render(struct userdata *u) {
break;
}
- u->write_index += (uint64_t) u->write_memchunk.length;
+ u->s_write_index += (uint64_t) u->samples_per_block;
pa_memblock_unref(u->write_memchunk.memblock);
pa_memchunk_reset(&u->write_memchunk);
@@ -1287,6 +1510,334 @@ static int a2dp_process_render(struct userdata *u) {
return ret;
}
+
+static uint32_t uextract(uint32_t x, uint32_t position, uint32_t nbits)
+{
+ int mask;
+
+ pa_assert(position <= 31);
+ pa_assert(nbits <= 31);
+
+ x = x >> position;
+ mask = (1<<nbits)-1;
+ x = x & mask;
+
+ return x;
+}
+
+
+#define MPEG_LAYER_INDEX 4
+#define MPEG_BITRATE_INDEX 16
+#define MPEG_SAMPFREQ_INDEX 4
+#define MPEG_INDEX 2
+
+unsigned short const mpeg_sampling_frequencies[MPEG_INDEX][MPEG_SAMPFREQ_INDEX] = {
+ {22050, 24000, 16000, 0},
+ {44100, 48000, 32000, 0}
+};
+
+short const mpeg_layer_bitrates[MPEG_INDEX][MPEG_BITRATE_INDEX] = {
+ { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1},
+ { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1}
+};
+
+short const mpeg_samples_per_frame[MPEG_INDEX][MPEG_LAYER_INDEX] = {
+ {0, 576, 1152, 384},
+ {0, 1152, 1152, 384}
+};
+
+static int mp3_synclength(uint32_t hi, uint32_t *len, uint32_t *sample_len)
+{
+ unsigned int tmp;
+ unsigned int idex;
+ unsigned int id;
+ unsigned int bitrate;
+ unsigned int freq;
+ unsigned int bits;
+
+ tmp = uextract(hi, 21, 11);
+ if (tmp == 0x7ff) { /* valid sync word */
+ tmp = uextract(hi,19,2);
+ if (tmp != 1) { /* valid IDex */
+
+ idex = tmp >> 1;
+ if (idex != 0) { /* MP3 2.5, not supported by A2DP */
+
+ id = tmp & 1;
+ tmp = uextract(hi,17,2);
+
+ if (tmp == 0x1) { /* layer 3 */
+
+ bitrate = uextract(hi,12,4);
+ if ((bitrate != 0) && /* not free format */
+ (bitrate != 0xf)) { /* not reserved */
+
+ freq = uextract(hi,10, 2);
+
+ if (freq != 3) { /* valid sampling frequency */
+
+ tmp = uextract(hi,9,1);
+
+ bitrate = mpeg_layer_bitrates[id][bitrate] * 1000;
+
+ bits =
+ (unsigned int) ((bitrate * mpeg_samples_per_frame[id][1]) /
+ mpeg_sampling_frequencies[id][freq]);
+
+
+ bits /= 8; /* # of bytes */
+ if (tmp) { /* padding */
+ bits += 1;
+ }
+
+ /* sanity check */
+ if (bits > 4 && bits <= 1728) { /* max frame length */
+ *len = bits;
+ *sample_len = mpeg_samples_per_frame[id][1];
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ if(hi!=0)
+ pa_log("no sync word found %x",hi);
+ }
+ return 1;
+}
+
+static int do_sync(uint8_t **p_src,uint8_t **p_dest, uint32_t *input_bytes, uint32_t *output_bytes,
+ uint32_t *frame_count, uint32_t *sample_count)
+{
+ int sync_found=0;
+ uint32_t len, sample_len;
+ uint8_t *src=*p_src;
+ uint8_t *dest=*p_dest;
+
+ len = 0; /* remove compiler warning */
+ while (1) {
+
+ if ( *input_bytes< 4 )
+ break;
+
+ /* we need 4 bytes to detect the frame length */
+ if (!mp3_synclength(src[0]<<24|src[1]<<16|src[2]<<8|src[3],
+ &len, &sample_len) ) {
+
+ sync_found = 1;
+
+ if (len>*input_bytes) {
+ /* we don't have a full frame in the input buffer */
+ break;
+ }
+
+ /* make sure there's enough room in the output buffer */
+ if (len > *output_bytes)
+ break;
+
+ /* copy complete frame */
+ memcpy(dest,src,len);
+ dest = dest + len;
+ src = src + len;
+
+ *input_bytes -= len;
+ *output_bytes -= len;
+
+ *frame_count += 1;
+ *sample_count += sample_len;
+
+ } else {
+
+ sync_found = 0;
+
+ /* try to find a new syncword */
+ src += 1;
+ *input_bytes -= 1;
+ }
+ }
+ *p_src=src;
+ *p_dest=dest;
+
+ return sync_found;
+} /* end do_sync() */
+
+
+static int a2dp_passthrough_process_render(struct userdata *u) {
+ struct a2dp_info *a2dp;
+ struct rtp_header *header;
+ struct mpeg_rtp_payload *payload;
+ size_t nbytes;
+ void *d;
+ const void *p;
+ size_t dest_bytes; /* in the destination buffer */
+ size_t orig_bytes=0; /* in the sink buffer */
+ uint32_t frame_count;
+ uint32_t sample_count=0;
+ uint32_t sync_found = 0;
+ uint32_t talk_spurt;
+
+ int ret = 0;
+
+ pa_assert(u);
+ pa_assert(u->profile == PROFILE_A2DP_PASSTHROUGH);
+ pa_assert(u->sink);
+
+ /* inits for output buffer */
+ a2dp_prepare_buffer(u);
+
+ a2dp = &u->a2dp;
+ header = a2dp->buffer;
+ payload = (struct mpeg_rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+ d = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
+ dest_bytes = a2dp->buffer_size - sizeof(*header) - sizeof(*payload);
+
+ if (dest_bytes > u->block_size) {
+ dest_bytes = u->block_size;
+ }
+
+ frame_count = 0;
+
+
+ /* render some data */
+ if (!u->write_memchunk.memblock) {
+ pa_memchunk *c;
+
+ pa_assert(u->leftover_bytes == 0);
+ c = &u->write_memchunk;
+ c->memblock = pa_memblock_new(u->sink->core->mempool,u->block_size);
+ c->index = 0;
+ c->length = u->block_size;
+ }
+
+ if (u->leftover_bytes) { /* incomplete frame that wasn't handled in the previous call */
+ pa_memchunk *c;
+ pa_memblock *n;
+ void *tdata, *sdata;
+ size_t l;
+
+ c = &u->write_memchunk;
+ n = pa_memblock_new(u->sink->core->mempool,u->block_size);
+
+ sdata = pa_memblock_acquire(c->memblock);
+ tdata = pa_memblock_acquire(n);
+
+ l = c->length;
+ memcpy(tdata, (uint8_t*) sdata + c->index, l);
+
+ pa_memblock_release(c->memblock);
+ pa_memblock_release(n);
+
+ pa_memblock_unref(c->memblock);
+
+ c->memblock = n;
+ c->index = l;
+ c->length = u->block_size - l;
+ }
+
+ /* fill memchunck, previous leftover bytes have been copied into beginning of frame already */
+ pa_sink_render_into_full(u->sink, &u->write_memchunk);
+
+ orig_bytes = u->block_size;
+
+ p = (const uint8_t*) pa_memblock_acquire(u->write_memchunk.memblock);
+
+ sync_found = do_sync((uint8_t**)&p,(uint8_t**)&d, &orig_bytes, &dest_bytes,
+ &frame_count, &sample_count);
+
+ u->write_memchunk.index = u->block_size-orig_bytes;
+ u->write_memchunk.length = orig_bytes;
+
+ pa_memblock_release(u->write_memchunk.memblock);
+
+ if (sync_found) {
+ nbytes = (uint8_t*) d - (uint8_t*) a2dp->buffer;
+
+ u->leftover_bytes=orig_bytes;
+
+ if (u->leftover_bytes == 0) {
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+ }
+
+ talk_spurt = 1;
+ } else {
+ /* we lost the sync here, zero out rest of buffer */
+ memset(d, 0, dest_bytes);
+
+ u->leftover_bytes=0;
+ pa_memblock_unref(u->write_memchunk.memblock);
+ pa_memchunk_reset(&u->write_memchunk);
+
+ /* FIXME: this is completely broken, force the write index to some value */
+ u->s_write_index += (uint64_t) 1152;
+ ret = 1;
+
+ return ret;
+ }
+
+ pa_assert(nbytes!=0);
+ pa_assert(nbytes<=(u->block_size+sizeof(*header)+sizeof(*payload)));
+
+ /* write it to the fifo */
+
+ memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload));
+ header->v = 2; /* rtp packet v2 */
+ header->pt = 14; /* MPA payload type */
+ header->timestamp = htonl((u->s_write_index*90000)/u->sample_spec.rate); /* 90kHz timestamp */
+ header->m = talk_spurt;
+ header->sequence_number = htons(a2dp->seq_num++);
+ header->ssrc = htonl(1); /* should in theory be random */
+
+ payload->mbz = 0;
+ payload->frag_offset = 0;
+
+ pa_assert(nbytes != 0);
+ pa_assert(nbytes <= u->link_mtu);
+
+ for (;;) {
+ ssize_t l;
+
+ l = pa_write(u->stream_fd, a2dp->buffer, nbytes, &u->stream_write_type);
+
+ pa_assert(l != 0);
+
+ if (l < 0) {
+
+ if (errno == EINTR)
+ /* Retry right away if we got interrupted */
+ continue;
+
+ else if (errno == EAGAIN)
+ /* Hmm, apparently the socket was not writable, give up for now */
+ break;
+
+ pa_log_error("Failed to write data to socket: %s", pa_cstrerror(errno));
+ ret = -1;
+ break;
+ }
+
+ pa_assert((size_t) l <= nbytes);
+
+ if ((size_t) l != nbytes) {
+ pa_log_warn("Wrote memory block to socket only partially! %llu written, wanted to write %llu.",
+ (unsigned long long) l,
+ (unsigned long long) nbytes);
+ ret = -1;
+ break;
+ }
+
+ u->s_write_index += (uint64_t) sample_count;
+
+ ret = 1;
+
+ break;
+ }
+
+ return ret;
+}
+
static int a2dp_process_push(struct userdata *u) {
int ret = 0;
pa_memchunk memchunk;
@@ -1304,7 +1855,7 @@ static int a2dp_process_push(struct userdata *u) {
pa_usec_t tstamp;
struct a2dp_info *a2dp;
struct rtp_header *header;
- struct rtp_payload *payload;
+ struct sbc_rtp_payload *payload;
const void *p;
void *d;
ssize_t l;
@@ -1315,7 +1866,7 @@ static int a2dp_process_push(struct userdata *u) {
a2dp = &u->a2dp;
header = a2dp->buffer;
- payload = (struct rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
+ payload = (struct sbc_rtp_payload*) ((uint8_t*) a2dp->buffer + sizeof(*header));
l = pa_read(u->stream_fd, a2dp->buffer, a2dp->buffer_size, &u->stream_write_type);
@@ -1336,7 +1887,7 @@ static int a2dp_process_push(struct userdata *u) {
pa_assert((size_t) l <= a2dp->buffer_size);
- u->read_index += (uint64_t) l;
+ u->s_read_index += (uint64_t) l/pa_frame_size(&u->sample_spec);
/* TODO: get timestamp from rtp */
if (!found_tstamp) {
@@ -1344,7 +1895,7 @@ static int a2dp_process_push(struct userdata *u) {
tstamp = pa_rtclock_now();
}
- pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec));
+ pa_smoother_put(u->read_smoother, tstamp, samples_to_usec(u->s_read_index, &u->sample_spec));
pa_smoother_resume(u->read_smoother, tstamp, TRUE);
p = (uint8_t*) a2dp->buffer + sizeof(*header) + sizeof(*payload);
@@ -1428,8 +1979,7 @@ static void thread_func(void *userdata) {
/* We should send two blocks to the device before we expect
* a response. */
-
- if (u->write_index == 0 && u->read_index <= 0)
+ if (u->s_write_index == 0 && u->s_read_index <= 0)
do_write = 2;
if (pollfd && (pollfd->revents & POLLIN)) {
@@ -1465,19 +2015,20 @@ static void thread_func(void *userdata) {
* to. So let's do things by time */
time_passed = pa_rtclock_now() - u->started_at;
- audio_sent = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+ audio_sent = samples_to_usec(u->s_write_index, &u->sample_spec);
if (audio_sent <= time_passed) {
pa_usec_t audio_to_send = time_passed - audio_sent;
/* Never try to catch up for more than 100ms */
- if (u->write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
+ if (u->s_write_index > 0 && audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC) {
pa_usec_t skip_usec;
uint64_t skip_bytes;
skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC;
skip_bytes = pa_usec_to_bytes(skip_usec, &u->sample_spec);
+ /* FIXME: this doesn't work for PASSTHROUGH */
if (skip_bytes > 0) {
pa_memchunk tmp;
@@ -1487,9 +2038,10 @@ static void thread_func(void *userdata) {
pa_sink_render_full(u->sink, skip_bytes, &tmp);
pa_memblock_unref(tmp.memblock);
- u->write_index += skip_bytes;
- }
- }
+ pa_assert(u->profile != PROFILE_A2DP_PASSTHROUGH);
+ u->s_write_index += skip_bytes/pa_frame_size(&u->sample_spec);
+ }
+ }
do_write = 1;
}
@@ -1498,12 +2050,15 @@ static void thread_func(void *userdata) {
if (writable && do_write > 0) {
int n_written;
- if (u->write_index <= 0)
+ if (u->s_write_index <= 0)
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;
} else {
if ((n_written = hsp_process_render(u)) < 0)
goto fail;
@@ -1523,7 +2078,7 @@ static void thread_func(void *userdata) {
* to. So let's estimate when we need to wake up the latest */
time_passed = pa_rtclock_now() - u->started_at;
- next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec);
+ next_write_at = samples_to_usec(u->s_write_index, &u->sample_spec);
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
@@ -1782,7 +2337,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 ? "a2dp" : "sco");
+ pa_proplist_sets(data.proplist, "bluetooth.protocol", (u->profile == PROFILE_A2DP || u->profile == PROFILE_A2DP_PASSTHROUGH) ? "a2dp" : "sco");
if (u->profile == PROFILE_HSP)
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_INTENDED_ROLES, "phone");
data.card = u->card;
@@ -1807,16 +2362,24 @@ static int add_sink(struct userdata *u) {
u->sink->parent.process_msg = sink_process_msg;
pa_sink_set_max_request(u->sink, u->block_size);
- pa_sink_set_fixed_latency(u->sink,
- (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_A2DP_PASSTHROUGH) {
+ /* FIXME: latency is broken for now */
+ pa_sink_set_fixed_latency(u->sink,FIXED_LATENCY_PLAYBACK_A2DP);
+ } else {
+ pa_sink_set_fixed_latency(u->sink,
+ ((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) {
u->sink->set_volume = sink_set_volume_cb;
u->sink->n_volume_steps = 16;
}
+ if (u->profile == PROFILE_A2DP_PASSTHROUGH) {
+ u->sink->flags |= PA_SINK_PASSTHROUGH;
+ }
return 0;
}
@@ -1962,6 +2525,7 @@ 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)
@@ -2104,6 +2668,10 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
pa_log_warn("A2DP is not connected, refused to switch profile");
return -PA_ERR_IO;
}
+ else if (device->audio_sink_state < PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_A2DP_PASSTHROUGH) {
+ pa_log_warn("A2DP Passthrough is not connected, refused to switch profile");
+ return -PA_ERR_IO;
+ }
else if (device->hfgw_state <= PA_BT_AUDIO_STATE_CONNECTED && *d == PROFILE_HFGW) {
pa_log_warn("HandsfreeGateway is not connected, refused to switch profile");
return -PA_ERR_IO;
@@ -2212,6 +2780,20 @@ static int add_card(struct userdata *u, const pa_bluetooth_device *device) {
*d = PROFILE_A2DP;
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)) {
@@ -2286,6 +2868,7 @@ 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");
diff --git a/src/modules/bluetooth/rtp.h b/src/modules/bluetooth/rtp.h
index 1457362..19307ac 100644
--- a/src/modules/bluetooth/rtp.h
+++ b/src/modules/bluetooth/rtp.h
@@ -38,7 +38,7 @@ struct rtp_header {
uint32_t csrc[0];
} __attribute__ ((packed));
-struct rtp_payload {
+struct sbc_rtp_payload {
unsigned frame_count:4;
unsigned rfa0:1;
unsigned is_last_fragment:1;
@@ -46,6 +46,11 @@ struct rtp_payload {
unsigned is_fragmented:1;
} __attribute__ ((packed));
+struct mpeg_rtp_payload {
+ unsigned mbz:16;
+ unsigned frag_offset:16;
+} __attribute__ ((packed));
+
#elif __BYTE_ORDER == __BIG_ENDIAN
struct rtp_header {
@@ -63,7 +68,7 @@ struct rtp_header {
uint32_t csrc[0];
} __attribute__ ((packed));
-struct rtp_payload {
+struct sbc_rtp_payload {
unsigned is_fragmented:1;
unsigned is_first_fragment:1;
unsigned is_last_fragment:1;
@@ -71,6 +76,11 @@ struct rtp_payload {
unsigned frame_count:4;
} __attribute__ ((packed));
+struct mpeg_rtp_payload {
+ unsigned frag_offset:16;
+ unsigned mbz:16;
+} __attribute__ ((packed));
+
#else
#error "Unknown byte order"
#endif
--
1.7.2.3
More information about the pulseaudio-discuss
mailing list