- updated to version 2.4.1: * Fixes in logging, <auth> in default mounts, JSON status API * SSL Security improvements: * Handle empty strings in config file better * Require Content-Type header for PUT requests * Fix possible leak of on-connect scripts (CVE-2014-9018,bnc#906538) More details, see http://icecast.org/news/icecast-release-2_4_1/ - Remove obsoleted patch: icecast-2.4.0-produce-valid-json.patch - Change doc subpackage to noarch - Spec file cleanup OBS-URL: https://build.opensuse.org/request/show/263076 OBS-URL: https://build.opensuse.org/package/show/multimedia:apps/icecast?expand=0&rev=27
226 lines
7.7 KiB
Diff
226 lines
7.7 KiB
Diff
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__ */
|