зеркало из https://github.com/mozilla/gecko-dev.git
Bug 919572 - Refactor the MP3 frame parser r=cpearce
This commit is contained in:
Родитель
13a762accd
Коммит
9772d4c7cf
|
@ -9,286 +9,227 @@
|
||||||
#include "MP3FrameParser.h"
|
#include "MP3FrameParser.h"
|
||||||
#include "VideoUtils.h"
|
#include "VideoUtils.h"
|
||||||
|
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
// An ID3Buffer contains data of an ID3v2 header. The supplied buffer must
|
/*
|
||||||
// point to an ID3 header and at least the size of ID_HEADER_LENGTH. Run the
|
* Following code taken from http://www.hydrogenaudio.org/forums/index.php?showtopic=85125
|
||||||
// Parse method to read in the header's values.
|
* with permission from the author, Nick Wallette <sirnickity@gmail.com>.
|
||||||
|
*/
|
||||||
|
|
||||||
class ID3Buffer
|
/* BEGIN shameless copy and paste */
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum {
|
// MPEG versions - use [version]
|
||||||
ID3_HEADER_LENGTH = 10
|
const uint8_t mpeg_versions[4] = { 25, 0, 2, 1 };
|
||||||
};
|
|
||||||
|
|
||||||
ID3Buffer(const uint8_t* aBuffer, uint32_t aLength)
|
// Layers - use [layer]
|
||||||
: mBuffer(aBuffer),
|
const uint8_t mpeg_layers[4] = { 0, 3, 2, 1 };
|
||||||
mLength(aLength),
|
|
||||||
mSize(0)
|
// Bitrates - use [version][layer][bitrate]
|
||||||
{
|
const uint16_t mpeg_bitrates[4][4][16] = {
|
||||||
MOZ_ASSERT(mBuffer || !mLength);
|
{ // Version 2.5
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved
|
||||||
|
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3
|
||||||
|
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2
|
||||||
|
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1
|
||||||
|
},
|
||||||
|
{ // Reserved
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid
|
||||||
|
},
|
||||||
|
{ // Version 2
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved
|
||||||
|
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3
|
||||||
|
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2
|
||||||
|
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1
|
||||||
|
},
|
||||||
|
{ // Version 1
|
||||||
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved
|
||||||
|
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3
|
||||||
|
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2
|
||||||
|
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult Parse();
|
|
||||||
|
|
||||||
int64_t Length() const {
|
|
||||||
return ID3_HEADER_LENGTH + mSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const uint8_t* mBuffer;
|
|
||||||
uint32_t mLength;
|
|
||||||
uint32_t mSize;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
nsresult ID3Buffer::Parse()
|
// Sample rates - use [version][srate]
|
||||||
{
|
const uint16_t mpeg_srates[4][4] = {
|
||||||
NS_ENSURE_TRUE(mBuffer && mLength >= ID3_HEADER_LENGTH, NS_ERROR_INVALID_ARG);
|
{ 11025, 12000, 8000, 0 }, // MPEG 2.5
|
||||||
|
{ 0, 0, 0, 0 }, // Reserved
|
||||||
if ((mBuffer[0] != 'I') ||
|
{ 22050, 24000, 16000, 0 }, // MPEG 2
|
||||||
(mBuffer[1] != 'D') ||
|
{ 44100, 48000, 32000, 0 } // MPEG 1
|
||||||
(mBuffer[2] != '3') ||
|
|
||||||
(mBuffer[6] & 0x80) ||
|
|
||||||
(mBuffer[7] & 0x80) ||
|
|
||||||
(mBuffer[8] & 0x80) ||
|
|
||||||
(mBuffer[9] & 0x80)) {
|
|
||||||
return NS_ERROR_INVALID_ARG;
|
|
||||||
}
|
|
||||||
|
|
||||||
mSize = ((static_cast<uint32_t>(mBuffer[6])<<21) |
|
|
||||||
(static_cast<uint32_t>(mBuffer[7])<<14) |
|
|
||||||
(static_cast<uint32_t>(mBuffer[8])<<7) |
|
|
||||||
static_cast<uint32_t>(mBuffer[9]));
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The MP3Buffer contains MP3 frame data. The supplied buffer must point
|
|
||||||
// to a frame header. Call the method Parse to extract information from
|
|
||||||
// the MP3 frame headers in the supplied buffer.
|
|
||||||
|
|
||||||
class MP3Buffer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
enum {
|
|
||||||
MP3_HEADER_LENGTH = 4,
|
|
||||||
MP3_FRAMESIZE_CONST = 144000,
|
|
||||||
MP3_DURATION_CONST = 8000
|
|
||||||
};
|
|
||||||
|
|
||||||
MP3Buffer(const uint8_t* aBuffer, uint32_t aLength)
|
|
||||||
: mBuffer(aBuffer),
|
|
||||||
mLength(aLength),
|
|
||||||
mDurationUs(0),
|
|
||||||
mNumFrames(0),
|
|
||||||
mBitRateSum(0),
|
|
||||||
mSampleRate(0),
|
|
||||||
mFrameSizeSum(0)
|
|
||||||
{
|
|
||||||
MOZ_ASSERT(mBuffer || !mLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult Parse();
|
|
||||||
|
|
||||||
int64_t GetDuration() const {
|
|
||||||
return mDurationUs;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetNumberOfFrames() const {
|
|
||||||
return mNumFrames;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetBitRateSum() const {
|
|
||||||
return mBitRateSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t GetSampleRate() const {
|
|
||||||
return mSampleRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t GetFrameSizeSum() const {
|
|
||||||
return mFrameSizeSum;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
enum MP3FrameHeaderField {
|
|
||||||
MP3_HDR_FIELD_SYNC,
|
|
||||||
MP3_HDR_FIELD_VERSION,
|
|
||||||
MP3_HDR_FIELD_LAYER,
|
|
||||||
MP3_HDR_FIELD_BITRATE,
|
|
||||||
MP3_HDR_FIELD_SAMPLERATE,
|
|
||||||
MP3_HDR_FIELD_PADDING,
|
|
||||||
MP3_HDR_FIELDS // Must be last enumerator value
|
|
||||||
};
|
|
||||||
|
|
||||||
enum {
|
|
||||||
MP3_HDR_CONST_FRAMESYNC = 0x7ff,
|
|
||||||
MP3_HDR_CONST_VERSION = 3,
|
|
||||||
MP3_HDR_CONST_LAYER = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
static uint32_t ExtractBits(uint32_t aValue, uint32_t aOffset,
|
|
||||||
uint32_t aBits);
|
|
||||||
static uint32_t ExtractFrameHeaderField(uint32_t aHeader,
|
|
||||||
enum MP3FrameHeaderField aField);
|
|
||||||
static uint32_t ExtractFrameHeader(const uint8_t* aBuffer);
|
|
||||||
static nsresult DecodeFrameHeader(const uint8_t* aBuffer,
|
|
||||||
uint32_t* aFrameSize,
|
|
||||||
uint32_t* aBitRate,
|
|
||||||
uint16_t* aSampleRate,
|
|
||||||
uint64_t* aDuration);
|
|
||||||
|
|
||||||
static const uint16_t sBitRate[16];
|
|
||||||
static const uint16_t sSampleRate[4];
|
|
||||||
|
|
||||||
const uint8_t* mBuffer;
|
|
||||||
uint32_t mLength;
|
|
||||||
|
|
||||||
// The duration of this parsers data in milliseconds.
|
|
||||||
int64_t mDurationUs;
|
|
||||||
|
|
||||||
// The number of frames in the range.
|
|
||||||
int64_t mNumFrames;
|
|
||||||
|
|
||||||
// The sum of all frame's bit rates.
|
|
||||||
int64_t mBitRateSum;
|
|
||||||
|
|
||||||
// The number of audio samples per second
|
|
||||||
int16_t mSampleRate;
|
|
||||||
|
|
||||||
// The sum of all frame's sizes in byte.
|
|
||||||
int32_t mFrameSizeSum;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint16_t MP3Buffer::sBitRate[16] = {
|
// Samples per frame - use [version][layer]
|
||||||
0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0
|
const uint16_t mpeg_frame_samples[4][4] = {
|
||||||
|
// Rsvd 3 2 1 < Layer v Version
|
||||||
|
{ 0, 576, 1152, 384 }, // 2.5
|
||||||
|
{ 0, 0, 0, 0 }, // Reserved
|
||||||
|
{ 0, 576, 1152, 384 }, // 2
|
||||||
|
{ 0, 1152, 1152, 384 } // 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint16_t MP3Buffer::sSampleRate[4] = {
|
// Slot size (MPEG unit of measurement) - use [layer]
|
||||||
44100, 48000, 32000, 0
|
const uint8_t mpeg_slot_size[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1
|
||||||
};
|
|
||||||
|
|
||||||
uint32_t MP3Buffer::ExtractBits(uint32_t aValue, uint32_t aOffset, uint32_t aBits)
|
uint16_t
|
||||||
|
MP3Frame::CalculateLength()
|
||||||
{
|
{
|
||||||
return (aValue >> aOffset) & ((0x1ul << aBits) - 1);
|
// Lookup real values of these fields
|
||||||
|
uint32_t bitrate = mpeg_bitrates[mVersion][mLayer][mBitrate] * 1000;
|
||||||
|
uint32_t samprate = mpeg_srates[mVersion][mSampleRate];
|
||||||
|
uint16_t samples = mpeg_frame_samples[mVersion][mLayer];
|
||||||
|
uint8_t slot_size = mpeg_slot_size[mLayer];
|
||||||
|
|
||||||
|
// In-between calculations
|
||||||
|
float bps = (float)samples / 8.0;
|
||||||
|
float fsize = ( (bps * (float)bitrate) / (float)samprate )
|
||||||
|
+ ( (mPad) ? slot_size : 0 );
|
||||||
|
|
||||||
|
// Frame sizes are truncated integers
|
||||||
|
return (uint16_t)fsize;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t MP3Buffer::ExtractFrameHeaderField(uint32_t aHeader, enum MP3FrameHeaderField aField)
|
/* END shameless copy and paste */
|
||||||
{
|
|
||||||
static const uint8_t sField[MP3_HDR_FIELDS][2] = {
|
|
||||||
{21, 11}, {19, 2}, {17, 2}, {12, 4}, {10, 2}, {9, 1}
|
|
||||||
};
|
|
||||||
|
|
||||||
MOZ_ASSERT(aField < MP3_HDR_FIELDS);
|
|
||||||
return ExtractBits(aHeader, sField[aField][0], sField[aField][1]);
|
/** MP3Parser methods **/
|
||||||
|
|
||||||
|
MP3Parser::MP3Parser()
|
||||||
|
: mCurrentChar(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void
|
||||||
|
MP3Parser::Reset()
|
||||||
|
{
|
||||||
|
mCurrentChar = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t MP3Buffer::ExtractFrameHeader(const uint8_t* aBuffer)
|
uint16_t
|
||||||
|
MP3Parser::ParseFrameLength(uint8_t ch)
|
||||||
{
|
{
|
||||||
MOZ_ASSERT(aBuffer);
|
mData.mRaw[mCurrentChar] = ch;
|
||||||
|
|
||||||
uint32_t header = (static_cast<uint32_t>(aBuffer[0])<<24) |
|
MP3Frame &frame = mData.mFrame;
|
||||||
(static_cast<uint32_t>(aBuffer[1])<<16) |
|
|
||||||
(static_cast<uint32_t>(aBuffer[2])<<8) |
|
|
||||||
static_cast<uint32_t>(aBuffer[3]);
|
|
||||||
|
|
||||||
uint32_t frameSync = ExtractFrameHeaderField(header, MP3_HDR_FIELD_SYNC);
|
// Validate MP3 header as we read. We can't mistake the start of an MP3 frame
|
||||||
uint32_t version = ExtractFrameHeaderField(header, MP3_HDR_FIELD_VERSION);
|
// for the middle of another frame due to the sync byte at the beginning
|
||||||
uint32_t layer = ExtractFrameHeaderField(header, MP3_HDR_FIELD_LAYER);
|
// of the frame.
|
||||||
uint32_t bitRate = sBitRate[ExtractFrameHeaderField(header, MP3_HDR_FIELD_BITRATE)];
|
|
||||||
uint32_t sampleRate = sSampleRate[ExtractFrameHeaderField(header, MP3_HDR_FIELD_SAMPLERATE)];
|
|
||||||
|
|
||||||
// branch-less implementation of
|
// The only valid position for an all-high byte is the sync byte at the
|
||||||
//
|
// beginning of the frame.
|
||||||
// if (fields-are-valid)
|
if (ch == 0xff) {
|
||||||
// return header;
|
mCurrentChar = 0;
|
||||||
// else
|
|
||||||
// return 0;
|
|
||||||
//
|
|
||||||
return (frameSync == uint32_t(MP3_HDR_CONST_FRAMESYNC)) *
|
|
||||||
(version == uint32_t(MP3_HDR_CONST_VERSION)) *
|
|
||||||
(layer == uint32_t(MP3_HDR_CONST_LAYER)) * !!bitRate * !!sampleRate * header;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult MP3Buffer::DecodeFrameHeader(const uint8_t* aBuffer,
|
|
||||||
uint32_t* aFrameSize,
|
|
||||||
uint32_t* aBitRate,
|
|
||||||
uint16_t* aSampleRate,
|
|
||||||
uint64_t* aDuration)
|
|
||||||
{
|
|
||||||
uint32_t header = ExtractFrameHeader(aBuffer);
|
|
||||||
|
|
||||||
if (!header) {
|
|
||||||
return NS_ERROR_INVALID_ARG;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bitRate = sBitRate[ExtractFrameHeaderField(header, MP3_HDR_FIELD_BITRATE)];
|
// Make sure the current byte is valid in context. If not, reset the parser.
|
||||||
uint32_t sampleRate = sSampleRate[ExtractFrameHeaderField(header, MP3_HDR_FIELD_SAMPLERATE)];
|
if (mCurrentChar == 2) {
|
||||||
|
if (frame.mBitrate == 0x0f) {
|
||||||
uint32_t padding = ExtractFrameHeaderField(header, MP3_HDR_FIELD_PADDING);
|
goto fail;
|
||||||
uint32_t frameSize = (uint64_t(MP3_FRAMESIZE_CONST) * bitRate) / sampleRate + padding;
|
|
||||||
|
|
||||||
MOZ_ASSERT(aBitRate);
|
|
||||||
*aBitRate = bitRate;
|
|
||||||
|
|
||||||
MOZ_ASSERT(aFrameSize);
|
|
||||||
*aFrameSize = frameSize;
|
|
||||||
|
|
||||||
MOZ_ASSERT(aDuration);
|
|
||||||
*aDuration = (uint64_t(MP3_DURATION_CONST) * frameSize) / bitRate;
|
|
||||||
|
|
||||||
MOZ_ASSERT(aSampleRate);
|
|
||||||
*aSampleRate = sampleRate;
|
|
||||||
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsresult MP3Buffer::Parse()
|
|
||||||
{
|
|
||||||
// We walk over the newly arrived data and sum up the
|
|
||||||
// bit rates, sizes, durations, etc. of the contained
|
|
||||||
// MP3 frames.
|
|
||||||
|
|
||||||
const uint8_t* buffer = mBuffer;
|
|
||||||
uint32_t length = mLength;
|
|
||||||
|
|
||||||
while (length >= MP3_HEADER_LENGTH) {
|
|
||||||
|
|
||||||
uint32_t frameSize;
|
|
||||||
uint32_t bitRate;
|
|
||||||
uint16_t sampleRate;
|
|
||||||
uint64_t duration;
|
|
||||||
|
|
||||||
nsresult rv = DecodeFrameHeader(buffer, &frameSize, &bitRate,
|
|
||||||
&sampleRate, &duration);
|
|
||||||
if (NS_FAILED(rv)) {
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
} else if (mCurrentChar == 1) {
|
||||||
|
if (frame.mSync2 != 0x07
|
||||||
|
|| frame.mVersion == 0x01
|
||||||
|
|| frame.mLayer == 0x00) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mBitRateSum += bitRate;
|
// The only valid character at the beginning of the header is 0xff. Fail if
|
||||||
mDurationUs += duration;
|
// it's different.
|
||||||
++mNumFrames;
|
if (mCurrentChar == 0 && frame.mSync1 != 0xff) {
|
||||||
|
// Couldn't find the sync byte. Fail.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
mFrameSizeSum += frameSize;
|
mCurrentChar++;
|
||||||
|
MOZ_ASSERT(mCurrentChar <= sizeof(MP3Frame));
|
||||||
|
|
||||||
mSampleRate = sampleRate;
|
// Don't have a full header yet.
|
||||||
|
if (mCurrentChar < sizeof(MP3Frame)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (frameSize <= length) {
|
// Woo, valid header. Return the length.
|
||||||
length -= frameSize;
|
mCurrentChar = 0;
|
||||||
|
return frame.CalculateLength();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
Reset();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
MP3Parser::GetSampleRate()
|
||||||
|
{
|
||||||
|
MP3Frame &frame = mData.mFrame;
|
||||||
|
return mpeg_srates[frame.mVersion][frame.mSampleRate];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** ID3Parser methods **/
|
||||||
|
|
||||||
|
const char sID3Head[3] = { 'I', 'D', '3' };
|
||||||
|
const uint32_t ID3_HEADER_LENGTH = 10;
|
||||||
|
|
||||||
|
ID3Parser::ID3Parser()
|
||||||
|
: mCurrentChar(0)
|
||||||
|
, mHeaderLength(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void
|
||||||
|
ID3Parser::Reset()
|
||||||
|
{
|
||||||
|
mCurrentChar = mHeaderLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ID3Parser::ParseChar(char ch)
|
||||||
|
{
|
||||||
|
// First three bytes of an ID3v2 header must match the string "ID3".
|
||||||
|
if (mCurrentChar < sizeof(sID3Head) / sizeof(*sID3Head)
|
||||||
|
&& ch != sID3Head[mCurrentChar]) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last four bytes of the header is a 28-bit unsigned integer with the
|
||||||
|
// high bit of each byte unset.
|
||||||
|
if (mCurrentChar >= 6 && mCurrentChar < ID3_HEADER_LENGTH) {
|
||||||
|
if (ch & 0x80) {
|
||||||
|
goto fail;
|
||||||
} else {
|
} else {
|
||||||
length = 0;
|
mHeaderLength <<= 7;
|
||||||
|
mHeaderLength |= ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer += frameSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NS_OK;
|
mCurrentChar++;
|
||||||
|
|
||||||
|
return IsParsed();
|
||||||
|
|
||||||
|
fail:
|
||||||
|
Reset();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
ID3Parser::IsParsed() const
|
||||||
|
{
|
||||||
|
return mCurrentChar >= ID3_HEADER_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
ID3Parser::GetHeaderLength() const
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(IsParsed(),
|
||||||
|
"Queried length of ID3 header before parsing finished.");
|
||||||
|
return mHeaderLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** MP3FrameParser methods **/
|
||||||
|
|
||||||
// Some MP3's have large ID3v2 tags, up to 150KB, so we allow lots of
|
// Some MP3's have large ID3v2 tags, up to 150KB, so we allow lots of
|
||||||
// skipped bytes to be read, just in case, before we give up and assume
|
// skipped bytes to be read, just in case, before we give up and assume
|
||||||
// we're not parsing an MP3 stream.
|
// we're not parsing an MP3 stream.
|
||||||
|
@ -300,17 +241,17 @@ static const uint32_t MAX_SKIPPED_BYTES = 200 * 1024;
|
||||||
// fairly accurately.
|
// fairly accurately.
|
||||||
static const uint32_t SAMPLES_PER_FRAME = 1152;
|
static const uint32_t SAMPLES_PER_FRAME = 1152;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
MP3_HEADER_LENGTH = 4,
|
||||||
|
};
|
||||||
|
|
||||||
MP3FrameParser::MP3FrameParser(int64_t aLength)
|
MP3FrameParser::MP3FrameParser(int64_t aLength)
|
||||||
: mBufferLength(0),
|
: mLock("MP3FrameParser.mLock"),
|
||||||
mLock("MP3FrameParser.mLock"),
|
|
||||||
mDurationUs(0),
|
|
||||||
mBitRateSum(0),
|
|
||||||
mTotalFrameSize(0),
|
mTotalFrameSize(0),
|
||||||
mNumFrames(0),
|
mNumFrames(0),
|
||||||
mOffset(0),
|
mOffset(0),
|
||||||
mLength(aLength),
|
mLength(aLength),
|
||||||
mMP3Offset(-1),
|
mMP3Offset(-1),
|
||||||
mSkippedBytes(0),
|
|
||||||
mSampleRate(0),
|
mSampleRate(0),
|
||||||
mIsMP3(MAYBE_MP3)
|
mIsMP3(MAYBE_MP3)
|
||||||
{ }
|
{ }
|
||||||
|
@ -322,60 +263,55 @@ nsresult MP3FrameParser::ParseBuffer(const uint8_t* aBuffer,
|
||||||
{
|
{
|
||||||
// Iterate forwards over the buffer, looking for ID3 tag, or MP3
|
// Iterate forwards over the buffer, looking for ID3 tag, or MP3
|
||||||
// Frame headers.
|
// Frame headers.
|
||||||
uint32_t bufferOffset = 0;
|
|
||||||
uint32_t headersParsed = 0;
|
const uint8_t *buffer = aBuffer;
|
||||||
while (bufferOffset < aLength) {
|
const uint8_t *bufferEnd = aBuffer + aLength;
|
||||||
const uint8_t* buffer = aBuffer + bufferOffset;
|
|
||||||
const uint32_t length = aLength - bufferOffset;
|
// If we haven't found any MP3 frame data yet, there might be ID3 headers
|
||||||
if (mMP3Offset == -1) {
|
// we can skip over.
|
||||||
// We've not found any MP3 frames yet, there may still be ID3 tags in
|
if (mMP3Offset < 0) {
|
||||||
// the stream, so test for them.
|
for (const uint8_t *ch = buffer; ch < bufferEnd; ch++) {
|
||||||
if (length < ID3Buffer::ID3_HEADER_LENGTH) {
|
if (mID3Parser.ParseChar(*ch)) {
|
||||||
// We don't have enough data to get a complete ID3 header, bail.
|
// Found an ID3 header. We don't care about the body of the header, so
|
||||||
break;
|
// just skip past.
|
||||||
}
|
buffer = ch + mID3Parser.GetHeaderLength() - (ID3_HEADER_LENGTH - 1);
|
||||||
ID3Buffer id3Buffer(buffer, length);
|
ch = buffer;
|
||||||
if (NS_SUCCEEDED(id3Buffer.Parse())) {
|
|
||||||
bufferOffset += id3Buffer.Length();
|
// Yes, this is an MP3!
|
||||||
// Try to parse the next chunk.
|
mIsMP3 = DEFINITELY_MP3;
|
||||||
headersParsed++;
|
|
||||||
continue;
|
mID3Parser.Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (length < MP3Buffer::MP3_HEADER_LENGTH) {
|
}
|
||||||
// We don't have enough data to get a complete MP3 frame header, bail.
|
|
||||||
break;
|
while (buffer < bufferEnd) {
|
||||||
}
|
uint16_t frameLen = mMP3Parser.ParseFrameLength(*buffer);
|
||||||
MP3Buffer mp3Buffer(buffer, length);
|
|
||||||
if (NS_SUCCEEDED(mp3Buffer.Parse())) {
|
if (frameLen) {
|
||||||
headersParsed++;
|
|
||||||
if (mMP3Offset == -1) {
|
if (mMP3Offset < 0) {
|
||||||
mMP3Offset = aStreamOffset + bufferOffset;
|
// Found our first frame: mark this stream as MP3 and let the decoder
|
||||||
|
// know where in the stream the MP3 data starts.
|
||||||
|
mIsMP3 = DEFINITELY_MP3;
|
||||||
|
// We're at the last byte of an MP3Frame, so MP3 data started
|
||||||
|
// sizeof - 1 bytes ago.
|
||||||
|
mMP3Offset = aStreamOffset
|
||||||
|
+ (buffer - aBuffer)
|
||||||
|
- (sizeof(MP3Frame) - 1);
|
||||||
}
|
}
|
||||||
mDurationUs += mp3Buffer.GetDuration();
|
|
||||||
mBitRateSum += mp3Buffer.GetBitRateSum();
|
mSampleRate = mMP3Parser.GetSampleRate();
|
||||||
mTotalFrameSize += mp3Buffer.GetFrameSizeSum();
|
mTotalFrameSize += frameLen;
|
||||||
mSampleRate = mp3Buffer.GetSampleRate();
|
mNumFrames++;
|
||||||
mNumFrames += mp3Buffer.GetNumberOfFrames();
|
|
||||||
bufferOffset += mp3Buffer.GetFrameSizeSum();
|
buffer += frameLen - sizeof(MP3Frame);
|
||||||
} else {
|
} else {
|
||||||
// No ID3 or MP3 frame header here. Try the next byte.
|
buffer++;
|
||||||
++bufferOffset;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (headersParsed == 0) {
|
|
||||||
if (mIsMP3 == MAYBE_MP3) {
|
*aOutBytesRead = buffer - aBuffer;
|
||||||
mSkippedBytes += aLength;
|
|
||||||
if (mSkippedBytes > MAX_SKIPPED_BYTES) {
|
|
||||||
mIsMP3 = NOT_MP3;
|
|
||||||
return NS_ERROR_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mIsMP3 = DEFINITELY_MP3;
|
|
||||||
mSkippedBytes = 0;
|
|
||||||
}
|
|
||||||
*aOutBytesRead = bufferOffset;
|
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,77 +320,46 @@ void MP3FrameParser::Parse(const char* aBuffer, uint32_t aLength, int64_t aOffse
|
||||||
MutexAutoLock mon(mLock);
|
MutexAutoLock mon(mLock);
|
||||||
|
|
||||||
const uint8_t* buffer = reinterpret_cast<const uint8_t*>(aBuffer);
|
const uint8_t* buffer = reinterpret_cast<const uint8_t*>(aBuffer);
|
||||||
const int64_t lastChunkEnd = mOffset + mBufferLength;
|
int32_t length = aLength;
|
||||||
if (aOffset + aLength <= lastChunkEnd) {
|
int64_t offset = aOffset;
|
||||||
// We already processed this fragment.
|
|
||||||
return;
|
// Got some data we have seen already. Skip forward to what we need.
|
||||||
} else if (aOffset < lastChunkEnd) {
|
if (aOffset < mOffset) {
|
||||||
// mOffset is within the new fragment, shorten range.
|
buffer += mOffset - aOffset;
|
||||||
aLength -= lastChunkEnd - aOffset;
|
length -= mOffset - aOffset;
|
||||||
buffer += lastChunkEnd - aOffset;
|
offset = mOffset;
|
||||||
aOffset = lastChunkEnd;
|
|
||||||
} else if (aOffset > lastChunkEnd) {
|
if (length <= 0) {
|
||||||
// Fragment comes after current position, store difference.
|
return;
|
||||||
mOffset += aOffset - lastChunkEnd;
|
}
|
||||||
mSkippedBytes = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mBufferLength > 0) {
|
// If there is a discontinuity in the input stream, reset the state of the
|
||||||
// We have some data which was left over from the last buffer we received.
|
// parsers so we don't get any partial headers.
|
||||||
// Append to it, so that we have enough data to parse a complete header, and
|
if (mOffset < aOffset) {
|
||||||
// try to parse it.
|
if (!mID3Parser.IsParsed()) {
|
||||||
uint32_t copyLength = std::min<size_t>(NS_ARRAY_LENGTH(mBuffer)-mBufferLength, aLength);
|
// Only reset this if it hasn't finished yet.
|
||||||
memcpy(mBuffer+mBufferLength, buffer, copyLength*sizeof(*mBuffer));
|
mID3Parser.Reset();
|
||||||
// Caculate the offset of the data in the start of the buffer.
|
|
||||||
int64_t streamOffset = mOffset - mBufferLength;
|
|
||||||
uint32_t bufferLength = mBufferLength + copyLength;
|
|
||||||
uint32_t bytesRead = 0;
|
|
||||||
if (NS_FAILED(ParseBuffer(mBuffer,
|
|
||||||
bufferLength,
|
|
||||||
streamOffset,
|
|
||||||
&bytesRead))) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
MOZ_ASSERT(bytesRead >= mBufferLength, "Parse should leave original buffer");
|
mMP3Parser.Reset();
|
||||||
|
|
||||||
// Adjust the incoming buffer pointer/length so that it reflects that we may have
|
|
||||||
// consumed data from buffer.
|
|
||||||
uint32_t adjust = bytesRead - mBufferLength;
|
|
||||||
mBufferLength = 0;
|
|
||||||
if (adjust >= aLength) {
|
|
||||||
// The frame or tag found in the buffer finishes outside the range.
|
|
||||||
// Just set the offset to the end of that tag/frame, and return.
|
|
||||||
mOffset = streamOffset + bytesRead;
|
|
||||||
if (mOffset > mLength) {
|
|
||||||
mLength = mOffset;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aOffset += adjust;
|
|
||||||
MOZ_ASSERT(aLength >= adjust);
|
|
||||||
aLength -= adjust;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bytesRead = 0;
|
uint32_t bytesRead = 0;
|
||||||
if (NS_FAILED(ParseBuffer(buffer,
|
if (NS_FAILED(ParseBuffer(buffer,
|
||||||
aLength,
|
length,
|
||||||
aOffset,
|
offset,
|
||||||
&bytesRead))) {
|
&bytesRead))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mOffset += bytesRead;
|
|
||||||
|
|
||||||
if (bytesRead < aLength) {
|
MOZ_ASSERT(length <= (int)bytesRead, "All bytes should have been consumed");
|
||||||
// We have some data left over. Store trailing bytes in temporary buffer
|
|
||||||
// to be parsed next time we receive more data.
|
|
||||||
uint32_t trailing = aLength - bytesRead;
|
|
||||||
MOZ_ASSERT(trailing < (NS_ARRAY_LENGTH(mBuffer)*sizeof(mBuffer[0])));
|
|
||||||
memcpy(mBuffer, buffer+(aLength-trailing), trailing);
|
|
||||||
mBufferLength = trailing;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mOffset > mLength) {
|
// Update next data offset
|
||||||
mLength = mOffset;
|
mOffset = offset + bytesRead;
|
||||||
|
|
||||||
|
// If we've parsed lots of data and we still have nothing, just give up.
|
||||||
|
if (!mID3Parser.IsParsed() && !mNumFrames && mOffset > MAX_SKIPPED_BYTES) {
|
||||||
|
mIsMP3 = NOT_MP3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,63 @@
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
|
// Simple parser to tell whether we've found an ID3 header and how long it is,
|
||||||
|
// so that we can skip it.
|
||||||
|
// XXX maybe actually parse this stuff?
|
||||||
|
class ID3Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ID3Parser();
|
||||||
|
|
||||||
|
void Reset();
|
||||||
|
bool ParseChar(char ch);
|
||||||
|
bool IsParsed() const;
|
||||||
|
uint32_t GetHeaderLength() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t mCurrentChar;
|
||||||
|
uint32_t mHeaderLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MP3Frame {
|
||||||
|
uint16_t mSync1 : 8; // Always all set
|
||||||
|
uint16_t mProtected : 1; // Ignored
|
||||||
|
uint16_t mLayer : 2;
|
||||||
|
uint16_t mVersion : 2;
|
||||||
|
uint16_t mSync2 : 3; // Always all set
|
||||||
|
uint16_t mPrivate : 1; // Ignored
|
||||||
|
uint16_t mPad : 1;
|
||||||
|
uint16_t mSampleRate : 2; // Index into mpeg_srates above
|
||||||
|
uint16_t mBitrate : 4; // Index into mpeg_bitrates above
|
||||||
|
|
||||||
|
uint16_t CalculateLength();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Buffering parser for MP3 frames.
|
||||||
|
class MP3Parser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MP3Parser();
|
||||||
|
|
||||||
|
// Forget all data the parser has seen so far.
|
||||||
|
void Reset();
|
||||||
|
|
||||||
|
// Parse the given byte. If we have found a frame header, return the length of
|
||||||
|
// the frame.
|
||||||
|
uint16_t ParseFrameLength(uint8_t ch);
|
||||||
|
|
||||||
|
// Get the sample rate from the current header.
|
||||||
|
uint32_t GetSampleRate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t mCurrentChar;
|
||||||
|
union {
|
||||||
|
uint8_t mRaw[3];
|
||||||
|
MP3Frame mFrame;
|
||||||
|
} mData;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// A description of the MP3 format and its extensions is available at
|
// A description of the MP3 format and its extensions is available at
|
||||||
//
|
//
|
||||||
// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
|
// http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header
|
||||||
|
@ -68,17 +125,17 @@ private:
|
||||||
int64_t aStreamOffset,
|
int64_t aStreamOffset,
|
||||||
uint32_t* aOutBytesRead);
|
uint32_t* aOutBytesRead);
|
||||||
|
|
||||||
// mBuffer must be at least 19 bytes long, in case the last byte in the
|
|
||||||
// buffer is the first byte in a 10 byte long ID3 tag header.
|
|
||||||
uint8_t mBuffer[32];
|
|
||||||
uint32_t mBufferLength;
|
|
||||||
|
|
||||||
// A low-contention lock for protecting the parser results
|
// A low-contention lock for protecting the parser results
|
||||||
Mutex mLock;
|
Mutex mLock;
|
||||||
|
|
||||||
|
// ID3 header parser. Keeps state between reads in case the header falls
|
||||||
|
// in between.
|
||||||
|
ID3Parser mID3Parser;
|
||||||
|
|
||||||
|
// MP3 frame header parser.
|
||||||
|
MP3Parser mMP3Parser;
|
||||||
|
|
||||||
// All fields below are protected by mLock
|
// All fields below are protected by mLock
|
||||||
uint64_t mDurationUs;
|
|
||||||
uint64_t mBitRateSum;
|
|
||||||
uint64_t mTotalFrameSize;
|
uint64_t mTotalFrameSize;
|
||||||
uint64_t mNumFrames;
|
uint64_t mNumFrames;
|
||||||
|
|
||||||
|
@ -94,11 +151,6 @@ private:
|
||||||
// first MP3 frame is found.
|
// first MP3 frame is found.
|
||||||
int64_t mMP3Offset;
|
int64_t mMP3Offset;
|
||||||
|
|
||||||
// Count of bytes that have been parsed but skipped over because we couldn't
|
|
||||||
// find a sync pattern or an ID3 header. If this gets too high, we assume
|
|
||||||
// the stream either isn't MP3, or is corrupt.
|
|
||||||
uint32_t mSkippedBytes;
|
|
||||||
|
|
||||||
// Number of audio samples per second. Fixed through the whole file.
|
// Number of audio samples per second. Fixed through the whole file.
|
||||||
uint16_t mSampleRate;
|
uint16_t mSampleRate;
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче