Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | #include <algorithm> |
michael@0 | 8 | |
michael@0 | 9 | #include "nsMemory.h" |
michael@0 | 10 | #include "MP3FrameParser.h" |
michael@0 | 11 | #include "VideoUtils.h" |
michael@0 | 12 | |
michael@0 | 13 | |
michael@0 | 14 | #define FROM_BIG_ENDIAN(X) ((uint32_t)((uint8_t)(X)[0] << 24 | (uint8_t)(X)[1] << 16 | \ |
michael@0 | 15 | (uint8_t)(X)[2] << 8 | (uint8_t)(X)[3])) |
michael@0 | 16 | |
michael@0 | 17 | |
michael@0 | 18 | namespace mozilla { |
michael@0 | 19 | |
michael@0 | 20 | /* |
michael@0 | 21 | * Following code taken from http://www.hydrogenaudio.org/forums/index.php?showtopic=85125 |
michael@0 | 22 | * with permission from the author, Nick Wallette <sirnickity@gmail.com>. |
michael@0 | 23 | */ |
michael@0 | 24 | |
michael@0 | 25 | /* BEGIN shameless copy and paste */ |
michael@0 | 26 | |
michael@0 | 27 | // Bitrates - use [version][layer][bitrate] |
michael@0 | 28 | const uint16_t mpeg_bitrates[4][4][16] = { |
michael@0 | 29 | { // Version 2.5 |
michael@0 | 30 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
michael@0 | 31 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 |
michael@0 | 32 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 |
michael@0 | 33 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 |
michael@0 | 34 | }, |
michael@0 | 35 | { // Reserved |
michael@0 | 36 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
michael@0 | 37 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
michael@0 | 38 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
michael@0 | 39 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid |
michael@0 | 40 | }, |
michael@0 | 41 | { // Version 2 |
michael@0 | 42 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
michael@0 | 43 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 |
michael@0 | 44 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 |
michael@0 | 45 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 |
michael@0 | 46 | }, |
michael@0 | 47 | { // Version 1 |
michael@0 | 48 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
michael@0 | 49 | { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3 |
michael@0 | 50 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2 |
michael@0 | 51 | { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1 |
michael@0 | 52 | } |
michael@0 | 53 | }; |
michael@0 | 54 | |
michael@0 | 55 | // Sample rates - use [version][srate] |
michael@0 | 56 | const uint16_t mpeg_srates[4][4] = { |
michael@0 | 57 | { 11025, 12000, 8000, 0 }, // MPEG 2.5 |
michael@0 | 58 | { 0, 0, 0, 0 }, // Reserved |
michael@0 | 59 | { 22050, 24000, 16000, 0 }, // MPEG 2 |
michael@0 | 60 | { 44100, 48000, 32000, 0 } // MPEG 1 |
michael@0 | 61 | }; |
michael@0 | 62 | |
michael@0 | 63 | // Samples per frame - use [version][layer] |
michael@0 | 64 | const uint16_t mpeg_frame_samples[4][4] = { |
michael@0 | 65 | // Rsvd 3 2 1 < Layer v Version |
michael@0 | 66 | { 0, 576, 1152, 384 }, // 2.5 |
michael@0 | 67 | { 0, 0, 0, 0 }, // Reserved |
michael@0 | 68 | { 0, 576, 1152, 384 }, // 2 |
michael@0 | 69 | { 0, 1152, 1152, 384 } // 1 |
michael@0 | 70 | }; |
michael@0 | 71 | |
michael@0 | 72 | // Slot size (MPEG unit of measurement) - use [layer] |
michael@0 | 73 | const uint8_t mpeg_slot_size[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1 |
michael@0 | 74 | |
michael@0 | 75 | uint16_t |
michael@0 | 76 | MP3Frame::CalculateLength() |
michael@0 | 77 | { |
michael@0 | 78 | // Lookup real values of these fields |
michael@0 | 79 | uint32_t bitrate = mpeg_bitrates[mVersion][mLayer][mBitrate] * 1000; |
michael@0 | 80 | uint32_t samprate = mpeg_srates[mVersion][mSampleRate]; |
michael@0 | 81 | uint16_t samples = mpeg_frame_samples[mVersion][mLayer]; |
michael@0 | 82 | uint8_t slot_size = mpeg_slot_size[mLayer]; |
michael@0 | 83 | |
michael@0 | 84 | // In-between calculations |
michael@0 | 85 | float bps = (float)samples / 8.0; |
michael@0 | 86 | float fsize = ( (bps * (float)bitrate) / (float)samprate ) |
michael@0 | 87 | + ( (mPad) ? slot_size : 0 ); |
michael@0 | 88 | |
michael@0 | 89 | // Frame sizes are truncated integers |
michael@0 | 90 | return (uint16_t)fsize; |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | /* END shameless copy and paste */ |
michael@0 | 94 | |
michael@0 | 95 | |
michael@0 | 96 | /** MP3Parser methods **/ |
michael@0 | 97 | |
michael@0 | 98 | MP3Parser::MP3Parser() |
michael@0 | 99 | : mCurrentChar(0) |
michael@0 | 100 | { } |
michael@0 | 101 | |
michael@0 | 102 | void |
michael@0 | 103 | MP3Parser::Reset() |
michael@0 | 104 | { |
michael@0 | 105 | mCurrentChar = 0; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | uint16_t |
michael@0 | 109 | MP3Parser::ParseFrameLength(uint8_t ch) |
michael@0 | 110 | { |
michael@0 | 111 | mData.mRaw[mCurrentChar] = ch; |
michael@0 | 112 | |
michael@0 | 113 | MP3Frame &frame = mData.mFrame; |
michael@0 | 114 | |
michael@0 | 115 | // Validate MP3 header as we read. We can't mistake the start of an MP3 frame |
michael@0 | 116 | // for the middle of another frame due to the sync byte at the beginning |
michael@0 | 117 | // of the frame. |
michael@0 | 118 | |
michael@0 | 119 | // The only valid position for an all-high byte is the sync byte at the |
michael@0 | 120 | // beginning of the frame. |
michael@0 | 121 | if (ch == 0xff) { |
michael@0 | 122 | mCurrentChar = 0; |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | // Make sure the current byte is valid in context. If not, reset the parser. |
michael@0 | 126 | if (mCurrentChar == 2) { |
michael@0 | 127 | if (frame.mBitrate == 0x0f) { |
michael@0 | 128 | goto fail; |
michael@0 | 129 | } |
michael@0 | 130 | } else if (mCurrentChar == 1) { |
michael@0 | 131 | if (frame.mSync2 != 0x07 |
michael@0 | 132 | || frame.mVersion == 0x01 |
michael@0 | 133 | || frame.mLayer == 0x00) { |
michael@0 | 134 | goto fail; |
michael@0 | 135 | } |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | // The only valid character at the beginning of the header is 0xff. Fail if |
michael@0 | 139 | // it's different. |
michael@0 | 140 | if (mCurrentChar == 0 && frame.mSync1 != 0xff) { |
michael@0 | 141 | // Couldn't find the sync byte. Fail. |
michael@0 | 142 | return 0; |
michael@0 | 143 | } |
michael@0 | 144 | |
michael@0 | 145 | mCurrentChar++; |
michael@0 | 146 | MOZ_ASSERT(mCurrentChar <= sizeof(MP3Frame)); |
michael@0 | 147 | |
michael@0 | 148 | // Don't have a full header yet. |
michael@0 | 149 | if (mCurrentChar < sizeof(MP3Frame)) { |
michael@0 | 150 | return 0; |
michael@0 | 151 | } |
michael@0 | 152 | |
michael@0 | 153 | // Woo, valid header. Return the length. |
michael@0 | 154 | mCurrentChar = 0; |
michael@0 | 155 | return frame.CalculateLength(); |
michael@0 | 156 | |
michael@0 | 157 | fail: |
michael@0 | 158 | Reset(); |
michael@0 | 159 | return 0; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | uint32_t |
michael@0 | 163 | MP3Parser::GetSampleRate() |
michael@0 | 164 | { |
michael@0 | 165 | MP3Frame &frame = mData.mFrame; |
michael@0 | 166 | return mpeg_srates[frame.mVersion][frame.mSampleRate]; |
michael@0 | 167 | } |
michael@0 | 168 | |
michael@0 | 169 | uint32_t |
michael@0 | 170 | MP3Parser::GetSamplesPerFrame() |
michael@0 | 171 | { |
michael@0 | 172 | MP3Frame &frame = mData.mFrame; |
michael@0 | 173 | return mpeg_frame_samples[frame.mVersion][frame.mLayer]; |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | |
michael@0 | 177 | /** ID3Parser methods **/ |
michael@0 | 178 | |
michael@0 | 179 | const char sID3Head[3] = { 'I', 'D', '3' }; |
michael@0 | 180 | const uint32_t ID3_HEADER_LENGTH = 10; |
michael@0 | 181 | |
michael@0 | 182 | ID3Parser::ID3Parser() |
michael@0 | 183 | : mCurrentChar(0) |
michael@0 | 184 | , mHeaderLength(0) |
michael@0 | 185 | { } |
michael@0 | 186 | |
michael@0 | 187 | void |
michael@0 | 188 | ID3Parser::Reset() |
michael@0 | 189 | { |
michael@0 | 190 | mCurrentChar = mHeaderLength = 0; |
michael@0 | 191 | } |
michael@0 | 192 | |
michael@0 | 193 | bool |
michael@0 | 194 | ID3Parser::ParseChar(char ch) |
michael@0 | 195 | { |
michael@0 | 196 | // First three bytes of an ID3v2 header must match the string "ID3". |
michael@0 | 197 | if (mCurrentChar < sizeof(sID3Head) / sizeof(*sID3Head) |
michael@0 | 198 | && ch != sID3Head[mCurrentChar]) { |
michael@0 | 199 | goto fail; |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | // The last four bytes of the header is a 28-bit unsigned integer with the |
michael@0 | 203 | // high bit of each byte unset. |
michael@0 | 204 | if (mCurrentChar >= 6 && mCurrentChar < ID3_HEADER_LENGTH) { |
michael@0 | 205 | if (ch & 0x80) { |
michael@0 | 206 | goto fail; |
michael@0 | 207 | } else { |
michael@0 | 208 | mHeaderLength <<= 7; |
michael@0 | 209 | mHeaderLength |= ch; |
michael@0 | 210 | } |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | mCurrentChar++; |
michael@0 | 214 | |
michael@0 | 215 | return IsParsed(); |
michael@0 | 216 | |
michael@0 | 217 | fail: |
michael@0 | 218 | Reset(); |
michael@0 | 219 | return false; |
michael@0 | 220 | } |
michael@0 | 221 | |
michael@0 | 222 | bool |
michael@0 | 223 | ID3Parser::IsParsed() const |
michael@0 | 224 | { |
michael@0 | 225 | return mCurrentChar >= ID3_HEADER_LENGTH; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | uint32_t |
michael@0 | 229 | ID3Parser::GetHeaderLength() const |
michael@0 | 230 | { |
michael@0 | 231 | MOZ_ASSERT(IsParsed(), |
michael@0 | 232 | "Queried length of ID3 header before parsing finished."); |
michael@0 | 233 | return mHeaderLength; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | |
michael@0 | 237 | /** VBR header helper stuff **/ |
michael@0 | 238 | |
michael@0 | 239 | // Helper function to find a VBR header in an MP3 frame. |
michael@0 | 240 | // Based on information from |
michael@0 | 241 | // http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header |
michael@0 | 242 | |
michael@0 | 243 | const uint32_t VBRI_TAG = FROM_BIG_ENDIAN("VBRI"); |
michael@0 | 244 | const uint32_t VBRI_OFFSET = 32 - sizeof(MP3Frame); |
michael@0 | 245 | const uint32_t VBRI_FRAME_COUNT_OFFSET = VBRI_OFFSET + 14; |
michael@0 | 246 | const uint32_t VBRI_MIN_FRAME_SIZE = VBRI_OFFSET + 26; |
michael@0 | 247 | |
michael@0 | 248 | const uint32_t XING_TAG = FROM_BIG_ENDIAN("Xing"); |
michael@0 | 249 | enum XingFlags { |
michael@0 | 250 | XING_HAS_NUM_FRAMES = 0x01, |
michael@0 | 251 | XING_HAS_NUM_BYTES = 0x02, |
michael@0 | 252 | XING_HAS_TOC = 0x04, |
michael@0 | 253 | XING_HAS_VBR_SCALE = 0x08 |
michael@0 | 254 | }; |
michael@0 | 255 | |
michael@0 | 256 | static int64_t |
michael@0 | 257 | ParseXing(const char *aBuffer) |
michael@0 | 258 | { |
michael@0 | 259 | uint32_t flags = FROM_BIG_ENDIAN(aBuffer + 4); |
michael@0 | 260 | |
michael@0 | 261 | if (!(flags & XING_HAS_NUM_FRAMES)) { |
michael@0 | 262 | NS_WARNING("VBR file without frame count. Duration estimation likely to " |
michael@0 | 263 | "be totally wrong."); |
michael@0 | 264 | return -1; |
michael@0 | 265 | } |
michael@0 | 266 | |
michael@0 | 267 | int64_t numFrames = -1; |
michael@0 | 268 | if (flags & XING_HAS_NUM_FRAMES) { |
michael@0 | 269 | numFrames = FROM_BIG_ENDIAN(aBuffer + 8); |
michael@0 | 270 | } |
michael@0 | 271 | |
michael@0 | 272 | return numFrames; |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | static int64_t |
michael@0 | 276 | FindNumVBRFrames(const nsAutoCString& aFrame) |
michael@0 | 277 | { |
michael@0 | 278 | const char *buffer = aFrame.get(); |
michael@0 | 279 | const char *bufferEnd = aFrame.get() + aFrame.Length(); |
michael@0 | 280 | |
michael@0 | 281 | // VBRI header is nice and well-defined; let's try to find that first. |
michael@0 | 282 | if (aFrame.Length() > VBRI_MIN_FRAME_SIZE && |
michael@0 | 283 | FROM_BIG_ENDIAN(buffer + VBRI_OFFSET) == VBRI_TAG) { |
michael@0 | 284 | return FROM_BIG_ENDIAN(buffer + VBRI_FRAME_COUNT_OFFSET); |
michael@0 | 285 | } |
michael@0 | 286 | |
michael@0 | 287 | // We have to search for the Xing header as its position can change. |
michael@0 | 288 | for (; buffer + sizeof(XING_TAG) < bufferEnd; buffer++) { |
michael@0 | 289 | if (FROM_BIG_ENDIAN(buffer) == XING_TAG) { |
michael@0 | 290 | return ParseXing(buffer); |
michael@0 | 291 | } |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | return -1; |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | |
michael@0 | 298 | /** MP3FrameParser methods **/ |
michael@0 | 299 | |
michael@0 | 300 | // Some MP3's have large ID3v2 tags, up to 150KB, so we allow lots of |
michael@0 | 301 | // skipped bytes to be read, just in case, before we give up and assume |
michael@0 | 302 | // we're not parsing an MP3 stream. |
michael@0 | 303 | static const uint32_t MAX_SKIPPED_BYTES = 4096; |
michael@0 | 304 | |
michael@0 | 305 | // The number of audio samples per MP3 frame. This is constant over all MP3 |
michael@0 | 306 | // streams. With this constant, the stream's sample rate, and an estimated |
michael@0 | 307 | // number of frames in the stream, we can estimate the stream's duration |
michael@0 | 308 | // fairly accurately. |
michael@0 | 309 | static const uint32_t SAMPLES_PER_FRAME = 1152; |
michael@0 | 310 | |
michael@0 | 311 | enum { |
michael@0 | 312 | MP3_HEADER_LENGTH = 4, |
michael@0 | 313 | }; |
michael@0 | 314 | |
michael@0 | 315 | MP3FrameParser::MP3FrameParser(int64_t aLength) |
michael@0 | 316 | : mLock("MP3FrameParser.mLock"), |
michael@0 | 317 | mTotalID3Size(0), |
michael@0 | 318 | mTotalFrameSize(0), |
michael@0 | 319 | mFrameCount(0), |
michael@0 | 320 | mOffset(0), |
michael@0 | 321 | mLength(aLength), |
michael@0 | 322 | mMP3Offset(-1), |
michael@0 | 323 | mSamplesPerSecond(0), |
michael@0 | 324 | mFirstFrameEnd(-1), |
michael@0 | 325 | mIsMP3(MAYBE_MP3) |
michael@0 | 326 | { } |
michael@0 | 327 | |
michael@0 | 328 | nsresult MP3FrameParser::ParseBuffer(const uint8_t* aBuffer, |
michael@0 | 329 | uint32_t aLength, |
michael@0 | 330 | int64_t aStreamOffset, |
michael@0 | 331 | uint32_t* aOutBytesRead) |
michael@0 | 332 | { |
michael@0 | 333 | // Iterate forwards over the buffer, looking for ID3 tag, or MP3 |
michael@0 | 334 | // Frame headers. |
michael@0 | 335 | const uint8_t *buffer = aBuffer; |
michael@0 | 336 | const uint8_t *bufferEnd = aBuffer + aLength; |
michael@0 | 337 | |
michael@0 | 338 | // If we haven't found any MP3 frame data yet, there might be ID3 headers |
michael@0 | 339 | // we can skip over. |
michael@0 | 340 | if (mMP3Offset < 0) { |
michael@0 | 341 | for (const uint8_t *ch = buffer; ch < bufferEnd; ch++) { |
michael@0 | 342 | if (mID3Parser.ParseChar(*ch)) { |
michael@0 | 343 | // Found an ID3 header. We don't care about the body of the header, so |
michael@0 | 344 | // just skip past. |
michael@0 | 345 | buffer = ch + mID3Parser.GetHeaderLength() - (ID3_HEADER_LENGTH - 1); |
michael@0 | 346 | ch = buffer; |
michael@0 | 347 | |
michael@0 | 348 | mTotalID3Size += mID3Parser.GetHeaderLength(); |
michael@0 | 349 | |
michael@0 | 350 | // Yes, this is an MP3! |
michael@0 | 351 | mIsMP3 = DEFINITELY_MP3; |
michael@0 | 352 | |
michael@0 | 353 | mID3Parser.Reset(); |
michael@0 | 354 | } |
michael@0 | 355 | } |
michael@0 | 356 | } |
michael@0 | 357 | |
michael@0 | 358 | // The first MP3 frame in a variable bitrate stream can contain metadata |
michael@0 | 359 | // for duration estimation and seeking, so we buffer that first frame here. |
michael@0 | 360 | if (aStreamOffset < mFirstFrameEnd) { |
michael@0 | 361 | uint64_t copyLen = std::min((int64_t)aLength, mFirstFrameEnd - aStreamOffset); |
michael@0 | 362 | mFirstFrame.Append((const char *)buffer, copyLen); |
michael@0 | 363 | buffer += copyLen; |
michael@0 | 364 | } |
michael@0 | 365 | |
michael@0 | 366 | while (buffer < bufferEnd) { |
michael@0 | 367 | uint16_t frameLen = mMP3Parser.ParseFrameLength(*buffer); |
michael@0 | 368 | |
michael@0 | 369 | if (frameLen) { |
michael@0 | 370 | // We've found an MP3 frame! |
michael@0 | 371 | // This is the first frame (and the only one we'll bother parsing), so: |
michael@0 | 372 | // * Mark this stream as MP3; |
michael@0 | 373 | // * Store the offset at which the MP3 data started; and |
michael@0 | 374 | // * Start buffering the frame, as it might contain handy metadata. |
michael@0 | 375 | |
michael@0 | 376 | // We're now sure this is an MP3 stream. |
michael@0 | 377 | mIsMP3 = DEFINITELY_MP3; |
michael@0 | 378 | |
michael@0 | 379 | // We need to know these to convert the number of frames in the stream |
michael@0 | 380 | // to the length of the stream in seconds. |
michael@0 | 381 | mSamplesPerSecond = mMP3Parser.GetSampleRate(); |
michael@0 | 382 | mSamplesPerFrame = mMP3Parser.GetSamplesPerFrame(); |
michael@0 | 383 | |
michael@0 | 384 | // If the stream has a constant bitrate, we should only need the length |
michael@0 | 385 | // of the first frame and the length (in bytes) of the stream to |
michael@0 | 386 | // estimate the length (in seconds). |
michael@0 | 387 | mTotalFrameSize += frameLen; |
michael@0 | 388 | mFrameCount++; |
michael@0 | 389 | |
michael@0 | 390 | // If |mMP3Offset| isn't set then this is the first MP3 frame we have |
michael@0 | 391 | // seen in the stream, which is useful for duration estimation. |
michael@0 | 392 | if (mMP3Offset > -1) { |
michael@0 | 393 | uint16_t skip = frameLen - sizeof(MP3Frame); |
michael@0 | 394 | buffer += skip ? skip : 1; |
michael@0 | 395 | continue; |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | // Remember the offset of the MP3 stream. |
michael@0 | 399 | // We're at the last byte of an MP3Frame, so MP3 data started |
michael@0 | 400 | // sizeof(MP3Frame) - 1 bytes ago. |
michael@0 | 401 | mMP3Offset = aStreamOffset |
michael@0 | 402 | + (buffer - aBuffer) |
michael@0 | 403 | - (sizeof(MP3Frame) - 1); |
michael@0 | 404 | |
michael@0 | 405 | buffer++; |
michael@0 | 406 | |
michael@0 | 407 | // If the stream has a variable bitrate, the first frame has metadata |
michael@0 | 408 | // we need for duration estimation and seeking. Start buffering it so we |
michael@0 | 409 | // can parse it later. |
michael@0 | 410 | mFirstFrameEnd = mMP3Offset + frameLen; |
michael@0 | 411 | uint64_t currOffset = buffer - aBuffer + aStreamOffset; |
michael@0 | 412 | uint64_t copyLen = std::min(mFirstFrameEnd - currOffset, |
michael@0 | 413 | (uint64_t)(bufferEnd - buffer)); |
michael@0 | 414 | mFirstFrame.Append((const char *)buffer, copyLen); |
michael@0 | 415 | |
michael@0 | 416 | buffer += copyLen; |
michael@0 | 417 | |
michael@0 | 418 | } else { |
michael@0 | 419 | // Nothing to see here. Move along. |
michael@0 | 420 | buffer++; |
michael@0 | 421 | } |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | *aOutBytesRead = buffer - aBuffer; |
michael@0 | 425 | |
michael@0 | 426 | if (mFirstFrameEnd > -1 && mFirstFrameEnd <= aStreamOffset + buffer - aBuffer) { |
michael@0 | 427 | // We have our whole first frame. Try to find a VBR header. |
michael@0 | 428 | mNumFrames = FindNumVBRFrames(mFirstFrame); |
michael@0 | 429 | mFirstFrameEnd = -1; |
michael@0 | 430 | } |
michael@0 | 431 | |
michael@0 | 432 | return NS_OK; |
michael@0 | 433 | } |
michael@0 | 434 | |
michael@0 | 435 | void MP3FrameParser::Parse(const char* aBuffer, uint32_t aLength, uint64_t aOffset) |
michael@0 | 436 | { |
michael@0 | 437 | MutexAutoLock mon(mLock); |
michael@0 | 438 | |
michael@0 | 439 | if (HasExactDuration()) { |
michael@0 | 440 | // We know the duration; nothing to do here. |
michael@0 | 441 | return; |
michael@0 | 442 | } |
michael@0 | 443 | |
michael@0 | 444 | const uint8_t* buffer = reinterpret_cast<const uint8_t*>(aBuffer); |
michael@0 | 445 | int32_t length = aLength; |
michael@0 | 446 | uint64_t offset = aOffset; |
michael@0 | 447 | |
michael@0 | 448 | // Got some data we have seen already. Skip forward to what we need. |
michael@0 | 449 | if (aOffset < mOffset) { |
michael@0 | 450 | buffer += mOffset - aOffset; |
michael@0 | 451 | length -= mOffset - aOffset; |
michael@0 | 452 | offset = mOffset; |
michael@0 | 453 | |
michael@0 | 454 | if (length <= 0) { |
michael@0 | 455 | return; |
michael@0 | 456 | } |
michael@0 | 457 | } |
michael@0 | 458 | |
michael@0 | 459 | // If there is a discontinuity in the input stream, reset the state of the |
michael@0 | 460 | // parsers so we don't get any partial headers. |
michael@0 | 461 | if (mOffset < aOffset) { |
michael@0 | 462 | if (!mID3Parser.IsParsed()) { |
michael@0 | 463 | // Only reset this if it hasn't finished yet. |
michael@0 | 464 | mID3Parser.Reset(); |
michael@0 | 465 | } |
michael@0 | 466 | |
michael@0 | 467 | if (mFirstFrameEnd > -1) { |
michael@0 | 468 | NS_WARNING("Discontinuity in input while buffering first frame."); |
michael@0 | 469 | mFirstFrameEnd = -1; |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | mMP3Parser.Reset(); |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | uint32_t bytesRead = 0; |
michael@0 | 476 | if (NS_FAILED(ParseBuffer(buffer, |
michael@0 | 477 | length, |
michael@0 | 478 | offset, |
michael@0 | 479 | &bytesRead))) { |
michael@0 | 480 | return; |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | MOZ_ASSERT(length <= (int)bytesRead, "All bytes should have been consumed"); |
michael@0 | 484 | |
michael@0 | 485 | // Update next data offset |
michael@0 | 486 | mOffset = offset + bytesRead; |
michael@0 | 487 | |
michael@0 | 488 | // If we've parsed lots of data and we still have nothing, just give up. |
michael@0 | 489 | // We don't count ID3 headers towards the skipped bytes count, as MP3 files |
michael@0 | 490 | // can have massive ID3 sections. |
michael@0 | 491 | if (!mID3Parser.IsParsed() && mMP3Offset < 0 && |
michael@0 | 492 | mOffset - mTotalID3Size > MAX_SKIPPED_BYTES) { |
michael@0 | 493 | mIsMP3 = NOT_MP3; |
michael@0 | 494 | } |
michael@0 | 495 | } |
michael@0 | 496 | |
michael@0 | 497 | int64_t MP3FrameParser::GetDuration() |
michael@0 | 498 | { |
michael@0 | 499 | MutexAutoLock mon(mLock); |
michael@0 | 500 | |
michael@0 | 501 | if (!ParsedHeaders() || !mSamplesPerSecond) { |
michael@0 | 502 | // Not a single frame decoded yet. |
michael@0 | 503 | return -1; |
michael@0 | 504 | } |
michael@0 | 505 | |
michael@0 | 506 | MOZ_ASSERT(mFrameCount > 0 && mTotalFrameSize > 0, |
michael@0 | 507 | "Frame parser should have seen at least one MP3 frame of positive length."); |
michael@0 | 508 | |
michael@0 | 509 | if (!mFrameCount || !mTotalFrameSize) { |
michael@0 | 510 | // This should never happen. |
michael@0 | 511 | return -1; |
michael@0 | 512 | } |
michael@0 | 513 | |
michael@0 | 514 | double frames; |
michael@0 | 515 | if (mNumFrames < 0) { |
michael@0 | 516 | // Estimate the number of frames in the stream based on the average frame |
michael@0 | 517 | // size and the length of the MP3 file. |
michael@0 | 518 | double frameSize = (double)mTotalFrameSize / mFrameCount; |
michael@0 | 519 | frames = (double)(mLength - mMP3Offset) / frameSize; |
michael@0 | 520 | } else { |
michael@0 | 521 | // We know the exact number of frames from the VBR header. |
michael@0 | 522 | frames = mNumFrames; |
michael@0 | 523 | } |
michael@0 | 524 | |
michael@0 | 525 | // The duration of each frame is constant over a given stream. |
michael@0 | 526 | double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond; |
michael@0 | 527 | |
michael@0 | 528 | return frames * usPerFrame; |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | int64_t MP3FrameParser::GetMP3Offset() |
michael@0 | 532 | { |
michael@0 | 533 | MutexAutoLock mon(mLock); |
michael@0 | 534 | return mMP3Offset; |
michael@0 | 535 | } |
michael@0 | 536 | |
michael@0 | 537 | bool MP3FrameParser::ParsedHeaders() |
michael@0 | 538 | { |
michael@0 | 539 | // We have seen both the beginning and the end of the first MP3 frame in the |
michael@0 | 540 | // stream. |
michael@0 | 541 | return mMP3Offset > -1 && mFirstFrameEnd < 0; |
michael@0 | 542 | } |
michael@0 | 543 | |
michael@0 | 544 | bool MP3FrameParser::HasExactDuration() |
michael@0 | 545 | { |
michael@0 | 546 | return ParsedHeaders() && mNumFrames > -1; |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | bool MP3FrameParser::NeedsData() |
michael@0 | 550 | { |
michael@0 | 551 | // If we don't know the duration exactly then either: |
michael@0 | 552 | // - we're still waiting for a VBR header; or |
michael@0 | 553 | // - we look at all frames to constantly update our duration estimate. |
michael@0 | 554 | return IsMP3() && !HasExactDuration(); |
michael@0 | 555 | } |
michael@0 | 556 | |
michael@0 | 557 | } |