Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:42.2
icecast
icecast-mp3-frame-validation.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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__ */
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor