File icecast-mp3-frame-validation.patch of Package icecast
Description: MP3 Frame validation
Author: Paul Kelly <paul@stjohnspoint.co.uk>
Icecast does not make any attempt to demarcate the boundaries between MP3
frames, and when a listening client connects to the server it generally is
sent an initial partial frame that can't be decoded. This is not a problem
for almost all client players.
It becomes a problem however when a "pre-roll" intro clip is used. When
Icecast connects the listener to the main stream after playing the intro
clip, it will very likely cut in in the middle of a frame, which causes a
problem for some players. Flash player in particular exhibits strange
behaviour with the audio cutting in and out every few seconds. Pausing the
player and resuming cures the problem.
http://lists.xiph.org/pipermail//icecast-dev/2011-October/001998.html
---
src/format_mp3.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/format_mp3.h | 10 +++
2 files changed, 170 insertions(+)
--- a/src/format_mp3.c
+++ b/src/format_mp3.c
@@ -509,6 +509,161 @@ static int complete_read (source_t *sour
return 1;
}
+static int bitrate_table[2][3][14] =
+{
+ {
+ /* MPEG-2 Layer III */
+ { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
+ /* MPEG-2 Layer II */
+ { 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
+ /* MPEG-2 Layer I */
+ {32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}
+ },
+ {
+ /* MPEG-1 Layer III */
+ {32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320},
+ /* MPEG-1 Layer II */
+ {32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384},
+ /* MPEG-1 Layer I */
+ {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}
+ }
+};
+
+static int samplerate_table[4][3] =
+{
+ {11025, 12000, 8000}, /* MPEG-2 LSF */
+ { 0, 0, 0}, /* Reserved */
+ {22050, 24000, 16000}, /* MPEG-2 */
+ {44100, 48000, 32000} /* MPEG-1 */
+};
+
+static int framesize_table[2][3] =
+{
+ /* L.III L.II L.I */
+ { 576, 1152, 384}, /* MPEG-2 */
+ { 1152, 1152, 384} /* MPEG-1 */
+};
+
+static int slotsize_table[3] =
+{
+ 1, /* L. III */
+ 1, /* L. II */
+ 4 /* L. I */
+};
+
+static int validate_header(mpeg_frame_t *fr)
+{
+ unsigned char version_code, layer_code, bitrate_code,
+ samplerate_code, padding;
+ char message[200];
+
+#define MAX_FRAME_LEN 2880 /* 160kbps Layer II @ 8kHz */
+
+ /* Check sync word is present */
+ if (fr->data[0] != 0xff || (fr->data[1] & 0xe0) != 0xe0)
+ goto invalid_frame;
+
+ /* Validate header by checking no reserved values are present */
+ if ( (version_code = (fr->data[1] & 0x18) >> 3) == 1
+ || (layer_code = (fr->data[1] & 0x06) >> 1) == 0
+ || (bitrate_code = (fr->data[2] & 0xf0) >> 4) == 15
+ || (samplerate_code = (fr->data[2] & 0x0c) >> 2) == 3)
+ goto invalid_frame;
+
+ if (bitrate_code == 0) /* Free-format bitrate */
+ /* We can't calculate the frame length anyway from this so can go no
+ * further with validation, so return the header as invalid. This is
+ * arguably a bug. */
+ goto invalid_frame;
+
+ /* Calculate data length of frame */
+ fr->kbps = bitrate_table[version_code & 1][layer_code - 1][bitrate_code - 1];
+ fr->sample_rate_Hz = samplerate_table[version_code][samplerate_code];
+ padding = (fr->data[2] & 0x02) >> 1;
+ fr->bytes = (framesize_table[version_code & 1][layer_code - 1] / 8
+ / slotsize_table[layer_code - 1] * fr->kbps * 1000
+ / fr->sample_rate_Hz + padding) * slotsize_table[layer_code - 1];
+
+ if (fr->bytes <= 0 || fr->bytes > MAX_FRAME_LEN)
+ goto invalid_frame;
+
+ if ((fr->data[3] & 0xc0) >> 6 == 3) /* mono */
+ fr->channels = 1;
+ else
+ fr->channels = 2;
+
+ return fr->bytes;
+
+invalid_frame:
+ fr->bytes = -1;
+ return -1;
+}
+
+/* Parse the MP3 data (also handles MPEG audio layers I/II) to check if there
+ * is a partial frame at the end of the data. If so the partial data is stored
+ * in the MP3 state (where it will be appended to the next time complete_read()
+ * is called) and the modified refbuf containing only complete frames is
+ * returned.
+ * Note that incomplete frames occuring at the *start* of the data buffer are
+ * ignored. This is so that huge frames (greater than the REFBUF size) can still
+ * be handled correctly.
+ */
+static void remove_partial_frames(refbuf_t *refbuf, mp3_state *source_mp3)
+{
+ int frame_offset = 0, valid_frames = 0;
+
+ /* while there are enough bytes remaining for a valid header */
+ while (refbuf->len - frame_offset >= 4)
+ {
+ mpeg_frame_t fr;
+
+ /* check the header is valid and determine the total frame length */
+ fr.data = (unsigned char *)refbuf->data+frame_offset;
+ validate_header(&fr);
+
+ /* If any frames are bigger than the refbuf size, then we need to leave
+ * them intact and just put up with the fact they will be split up. Such
+ * huge frames should be very rare in practice. */
+ if (fr.bytes > REFBUF_SIZE)
+ return;
+
+ if (fr.bytes > 0) /* if header is valid */
+ {
+ if(frame_offset + fr.bytes > refbuf->len)
+ /* this frame extends beyond the buffer */
+ break;
+
+ valid_frames++;
+ /* Skip to start of next frame */
+ frame_offset += fr.bytes;
+ continue;
+ }
+
+ /* Validation failed: shift forward by one byte and keep searching for header */
+ frame_offset++;
+ }
+
+ if (frame_offset == refbuf->len || valid_frames == 0)
+ /* buffer contained only either wholly complete or wholly partial frames */
+ return;
+
+ /* Allocate a new refbuf to hold the partial last frame. When complete_read()
+ * is called again it will append to this. */
+ source_mp3->read_data = refbuf_new (REFBUF_SIZE);
+ source_mp3->read_count = refbuf->len - frame_offset;
+
+ /* Copy the partial frame into the new refbuf and reduce the byte count for
+ * the existing refbuf accordingly. */
+ memcpy (source_mp3->read_data->data, refbuf->data+frame_offset,
+ source_mp3->read_count);
+ refbuf->len = frame_offset;
+
+ /* Finally adjust the metadata interval offset to avoid "double-counting" of
+ * the bytes in the partial frame. */
+ source_mp3->offset -= source_mp3->read_count;
+
+ return;
+}
/* read an mp3 stream which does not have shoutcast style metadata */
static refbuf_t *mp3_get_no_meta (source_t *source)
@@ -529,7 +684,10 @@ static refbuf_t *mp3_get_no_meta (source
}
refbuf->associated = source_mp3->metadata;
refbuf_addref (source_mp3->metadata);
+
+ remove_partial_frames(refbuf, source_mp3);
refbuf->sync_point = 1;
+
return refbuf;
}
@@ -650,6 +808,8 @@ static refbuf_t *mp3_get_filter_meta (so
}
refbuf->associated = source_mp3->metadata;
refbuf_addref (source_mp3->metadata);
+
+ remove_partial_frames(refbuf, source_mp3);
refbuf->sync_point = 1;
return refbuf;
--- a/src/format_mp3.h
+++ b/src/format_mp3.h
@@ -39,6 +39,16 @@ typedef struct {
char build_metadata[4081];
} mp3_state;
+typedef struct
+{
+ unsigned char *data; /* Pointer to complete MPEG audio frame (including
+ * sync word and header */
+ int bytes; /* Length of MPEG frame in bytes */
+ int kbps; /* Bitrate in kilobits per second */
+ int sample_rate_Hz; /* Sample rate in hertz */
+ int channels; /* Number of channels (mono/stereo) */
+} mpeg_frame_t;
+
int format_mp3_get_plugin(struct source_tag *src);
#endif /* __FORMAT_MP3_H__ */