[pulseaudio-discuss] [RFC v2 14/18] bluetooth: Build and push fragmented MPEG frames
Frédéric Dalleau
frederic.dalleau at linux.intel.com
Thu Mar 22 09:36:38 PDT 2012
---
src/modules/bluetooth/module-bluetooth-device.c | 229 +++++++++++++++++++----
1 files changed, 192 insertions(+), 37 deletions(-)
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 0acce34..cb4714d 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -213,6 +213,9 @@ enum {
#define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source))
+#define MPEG_MIN_FRAME_SIZE 4
+#define MPEG_MAX_FRAME_SIZE 1728
+
static int init_bt(struct userdata *u);
static int init_profile(struct userdata *u);
@@ -1785,7 +1788,7 @@ static pa_bool_t mp3_synclength(uint32_t hi, uint32_t *len, uint32_t *sample_len
}
/* sanity check */
- if (bits > 4 && bits <= 1728) { /* max frame length */
+ if (bits > MPEG_MIN_FRAME_SIZE && bits <= MPEG_MAX_FRAME_SIZE) { /* max frame length */
*len = bits;
*sample_len = mpeg_frame_length[id][1];
return TRUE;
@@ -1818,7 +1821,7 @@ static pa_bool_t do_sync_iec958(const uint8_t **p_src, uint32_t *input_bytes)
/* we need 4 bytes to detect the Pa,Pb preambles */
preambles = src[0]<<24 | src[1]<<16 | src[2]<<8 | src[3];
-#define IEC958_MAGIC_NUMBER 0x72F81F4E
+#define IEC958_MAGIC_NUMBER 0x72F81F4E /* little endian encoded */
if (preambles == IEC958_MAGIC_NUMBER) { /* little endian assumed */
sync_found = TRUE;
break;
@@ -2083,6 +2086,46 @@ static int a2dp_passthrough_process_render(struct userdata *u) {
return ret;
}
+/* Size of an IEC958 padded MPEG frame. */
+#define MPEGP_IEC_FRAME_SIZE (1152*4)
+/* Size of the IEC958 header. */
+#define MPEGP_IEC_HEADER_SIZE 8
+/* Size of the MPEG header. */
+#define MPEGP_MPEG_HEADER_SIZE 4
+
+typedef struct {
+ uint8_t sync_byte;
+
+ uint8_t protect:1;
+ uint8_t layer:2;
+ uint8_t sync:3;
+ uint8_t version:2;
+
+ uint8_t priv:1;
+ uint8_t padding:1;
+ uint8_t sampling:2;
+ uint8_t bitrate:4;
+
+ uint8_t emphasys:2;
+ uint8_t original:1;
+ uint8_t copyright:1;
+ uint8_t extension:2;
+ uint8_t channelmode:2;
+} mpeg_header;
+
+static int mp3_length(uint32_t header)
+{
+ mpeg_header *hdr = (mpeg_header *)&header;
+ int mpeg_frame_size = mpeg_layer_bitrates[hdr->version & 0x1][hdr->bitrate] * 1000
+ * mpeg_frame_length[hdr->version & 0x1][hdr->layer]
+ / mpeg_sampling_frequencies[hdr->version & 0x1][hdr->sampling] / 8;
+
+ if (hdr->padding)
+ mpeg_frame_size ++;
+
+ return mpeg_frame_size;
+}
+
static int a2dp_process_push(struct userdata *u) {
int ret = 0;
pa_memchunk memchunk;
@@ -2092,11 +2135,16 @@ static int a2dp_process_push(struct userdata *u) {
pa_assert(u->source);
pa_assert(u->read_smoother);
- memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size);
- memchunk.index = memchunk.length = 0;
+ if (u->write_index == 0) {
+ memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size);
+ memchunk.index = memchunk.length = 0;
+ } else {
+ memchunk = u->write_memchunk;
+ }
for (;;) {
pa_bool_t found_tstamp = FALSE;
+ pa_bool_t complete = FALSE;
pa_usec_t tstamp;
struct a2dp_info *a2dp;
struct rtp_header *header;
@@ -2147,7 +2195,10 @@ static int a2dp_process_push(struct userdata *u) {
to_decode = l - sizeof(*header) - payload_size;
d = pa_memblock_acquire(memchunk.memblock);
- to_write = memchunk.length = pa_memblock_get_length(memchunk.memblock);
+ if (memchunk.length == 0)
+ memchunk.length = pa_memblock_get_length(memchunk.memblock);
+
+ to_write = memchunk.length;
while (PA_LIKELY(to_decode > 0)) {
size_t written;
@@ -2177,42 +2228,140 @@ static int a2dp_process_push(struct userdata *u) {
pa_assert_fp((size_t) decoded <= to_decode);
pa_assert_fp((size_t) decoded == a2dp->frame_length);
pa_assert_fp((size_t) written == a2dp->codesize);
+ complete = TRUE;
break;
case A2DP_MODE_MPEG: {
- uint8_t *iec, *orig;
+ uint8_t *iec;
+ const uint8_t *orig;
+ size_t mp3_len, payload_len, swap_len, sample_length;
size_t i;
- pa_assert(to_write == 1152 * 4);
- pa_assert(to_decode < 1728);
- pa_assert(to_decode < u->link_mtu);
-
- /* Build IEC frame (16le formatting) */
- iec = d;
- iec[0] = 0x72;
- iec[1] = 0xF8;
- iec[2] = 0x1F;
- iec[3] = 0x4E;
- iec[4] = 0x05;
- iec[5] = 0x05;
- iec[6] = to_decode % 256 * 8;
- iec[7] = to_decode / 256 % 256 * 8;
- iec += 8;
-
orig = p;
- for (i = 0; i < to_decode / 2; i++) {
- *(iec + 1) = *orig;
- *iec = *(orig + 1);
- iec += 2;
- orig += 2;
- }
+ pa_log_debug("MPEG: first bytes received : %x %x %x %x", (int)orig[0], (int)orig[1], (int)orig[2], (int)orig[3]);
+ if (u->write_index == 0) {
+
+ if (mp3_synclength(orig[0]<<24 | orig[1]<<16 | orig[2]<<8 | orig[3], &mp3_len, &sample_length)) {
+
+ mp3_len = mp3_length(orig[0]<<24 | orig[1]<<16 | orig[2]<<8 | orig[3]);
+
+ /* round to ceiling for payload length, required because of byte swaps */
+ payload_len = mp3_len + 1;
+ payload_len >>= 1;
+ payload_len <<= 1;
+
+ /* memblock contains enough room for full IEC frame */
+
+ /* If the frame is not complete, then either
+ store and wait for another frame
+ or push the existing data into PA (is it possible or useful, the zero padding would not be added?)
+ his should not add latency since nothing can be done using an incomplete frame
+ note that the iec header is added only on a starting frame
+ */
+ /* Maximum MPEG frame as seen elsewhere in this code : MPEG_MAX_FRAME_SIZE bytes */
+ /* Example bluetooth MTU 672 bytes */
+
+ /* Build IEC header (16le formatting) */
+ iec = d;
+ *iec++ = 0x72;
+ *iec++ = 0xf8;
+ *iec++ = 0x1f;
+ *iec++ = 0x4e;
+ *iec++ = 0x05;
+ *iec++ = 0x05;
+ *iec++ = (mp3_len * 8) & 0xFF;
+ *iec++ = ((mp3_len * 8) >> 8) & 0xFF;
+
+ /* Take the incoming bytes and swap to 16LE */
+ swap_len = PA_MIN(to_decode,payload_len);
+ for (i = 0; i < swap_len / 2; i++) {
+ *iec++ = *(orig + 1);
+ *iec++ = *orig;
+ orig += 2;
+ }
+ if (swap_len % 2) {
+ *iec++ = 0;
+ *iec++ = *orig++;
+ }
- memset(iec, 0, to_write - to_decode - 8);
+ /* FIXME: Should use mp3_len or payload_len? */
+ /* Payload_len works with packet sent from pulse, but may not work if packet is sent by someone else */
+ if (payload_len < to_decode) {
+ /* This +1 is a really dirty hack because A2DP process render has sent a packet of the wrong size */
+ /* Not implemented, should push packet and continue buffer processing */
+ /* Not sure this happen in real life though */
+ pa_assert_not_reached();
+ } else if(payload_len == to_decode) {
+ pa_assert(payload_len == (size_t)iec - (size_t)d - 8);
+ /* If an MP3 packet is processed, pad with zeroes and push */
+ memset(iec, 0, u->block_size - payload_len - 8);
+ decoded = to_decode;
+ written = memchunk.length;
+ complete = TRUE;
+ } else /* payload_len > to_decode */ {
+ /* If we have only a part of an MP3 packet, then keep track of current state for next packet */
+ pa_log_debug("MPEG: fragmented MP3 frame, mp3_len %u, to_decode %u", mp3_len, to_decode);
+ u->write_index = to_decode; /* Number of MPEG data written until now */
+ pa_assert(u->write_index % 2 == 0);
+ u->write_memchunk = memchunk;
+ pa_memblock_ref(memchunk.memblock);
+ decoded = to_decode;
+ written = decoded;
+ }
- decoded = to_decode;
- written = to_write;
- pa_log_debug("MPEG: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written);
+ pa_log_debug("MPEG: decoded: %lu; written: %lu", (unsigned long) decoded, (unsigned long) written);
+ } else {
+ pa_log("MPEG: Invalid frame, dropped");
+ pa_assert_not_reached();
+ }
+ } else {
+ const uint8_t *hdr = d;
+ pa_log_debug("MPEG: first bytes stored : %x %x %x %x %x %x %x %x %x %x %x %x", (int)hdr[0], (int)hdr[1], (int)hdr[2], (int)hdr[3], (int)hdr[4], (int)hdr[5], (int)hdr[6], (int)hdr[7], (int)hdr[8], (int)hdr[9], (int)hdr[10], (int)hdr[11]);
+ mp3_len = mp3_length(hdr[10]<<24 | hdr[11]<<16 | hdr[8]<<8 | hdr[9]);
+ pa_log_debug("MPEG: Continuation frame u->write_index %lu, mp3_len %u, to_decode %u", (long unsigned int)u->write_index, mp3_len, to_decode);
+
+ /* round to ceiling for payload length, required because of byte swaps */
+ payload_len = mp3_len + 1;
+ payload_len >>= 1;
+ payload_len <<= 1;
+
+ pa_assert(u->write_index % 2 == 0);
+ pa_assert(u->write_index < MPEG_MAX_FRAME_SIZE);
+ pa_assert(mp3_len <= MPEG_MAX_FRAME_SIZE);
+
+ iec = d;
+ iec += u->write_index + 8;
+
+ /* Take the incoming bytes and swap to 16LE */
+ swap_len = PA_MIN(to_decode, payload_len - u->write_index);
+ for (i = 0; i < swap_len / 2; i++) {
+ *iec++ = *(orig + 1);
+ *iec++ = *orig;
+ orig += 2;
+ }
+ if (swap_len % 2) {
+ *iec++ = 0;
+ *iec++ = *orig++;
+ }
+ if (payload_len == u->write_index + swap_len) {
+ pa_assert(payload_len == (size_t)iec - (size_t)d - 8);
+ /* Pad with zeroes */
+ pa_log_debug("MPEG: fragmented MP3 frame, mp3_len %u, swap_len %u", mp3_len, swap_len);
+ memset(iec, 0, u->block_size - u->write_index - swap_len - 8);
+ decoded = to_decode;
+ written = memchunk.length;
+ complete = TRUE;
+ } else if (payload_len > u->write_index + swap_len) {
+ pa_log_debug("MPEG: to be continued");
+ u->write_index += swap_len;
+ pa_assert(u->write_index % 2 == 0);
+ decoded = to_decode;
+ written = decoded;
+ } else {
+ pa_log("MPEG: Something is wrong, mp3_len %u < u->write_index %u + swap_len %u", mp3_len, (unsigned int)u->write_index, swap_len);
+ }
+ }
break;
}
default:
@@ -2229,17 +2378,23 @@ static int a2dp_process_push(struct userdata *u) {
}
memchunk.length -= to_write;
-
pa_memblock_release(memchunk.memblock);
- pa_source_post(u->source, &memchunk);
+ if (complete) {
+ pa_log_debug("MPEG: frame pushed");
+ pa_source_post(u->source, &memchunk);
+ pa_memblock_unref(memchunk.memblock);
+
+ u->write_index = u->write_memchunk.index = u->write_memchunk.length = 0;
+ u->write_memchunk.memblock = NULL;
+ } else {
+ pa_log_debug("MPEG: frame kept for later");
+ }
ret = 1;
break;
}
- pa_memblock_unref(memchunk.memblock);
-
return ret;
}
@@ -3139,7 +3294,7 @@ static int bt_transport_config_a2dp_mpeg(struct userdata *u) {
pa_assert_not_reached();
}
- u->block_size = 1152*4;
+ u->block_size = 1152*4; /* Maximum size of an IEC frame */
u->leftover_bytes = 0;
pa_log_info("MPEG selected\n");
--
1.7.5.4
More information about the pulseaudio-discuss
mailing list