/src/mozilla-central/dom/media/mp3/MP3FrameParser.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "MP3FrameParser.h" |
8 | | |
9 | | #include <algorithm> |
10 | | #include <inttypes.h> |
11 | | |
12 | | #include "mozilla/Assertions.h" |
13 | | #include "mozilla/EndianUtils.h" |
14 | | #include "mozilla/Pair.h" |
15 | | #include "mozilla/ResultExtensions.h" |
16 | | #include "VideoUtils.h" |
17 | | |
18 | | extern mozilla::LazyLogModule gMediaDemuxerLog; |
19 | | #define MP3LOG(msg, ...) \ |
20 | 0 | MOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, ("MP3Demuxer " msg, ##__VA_ARGS__)) |
21 | | #define MP3LOGV(msg, ...) \ |
22 | 0 | MOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__)) |
23 | | |
24 | | namespace mozilla { |
25 | | |
26 | | // FrameParser |
27 | | |
28 | | namespace frame_header { |
29 | | // FrameHeader mRaw byte offsets. |
30 | | static const int SYNC1 = 0; |
31 | | static const int SYNC2_VERSION_LAYER_PROTECTION = 1; |
32 | | static const int BITRATE_SAMPLERATE_PADDING_PRIVATE = 2; |
33 | | static const int CHANNELMODE_MODEEXT_COPY_ORIG_EMPH = 3; |
34 | | } // namespace frame_header |
35 | | |
36 | | FrameParser::FrameParser() |
37 | 0 | { |
38 | 0 | } |
39 | | |
40 | | void |
41 | | FrameParser::Reset() |
42 | 0 | { |
43 | 0 | mID3Parser.Reset(); |
44 | 0 | mFrame.Reset(); |
45 | 0 | } |
46 | | |
47 | | void |
48 | | FrameParser::ResetFrameData() |
49 | 0 | { |
50 | 0 | mFrame.Reset(); |
51 | 0 | mFirstFrame.Reset(); |
52 | 0 | mPrevFrame.Reset(); |
53 | 0 | } |
54 | | |
55 | | void |
56 | | FrameParser::EndFrameSession() |
57 | 0 | { |
58 | 0 | if (!mID3Parser.Header().IsValid()) { |
59 | 0 | // Reset ID3 tags only if we have not parsed a valid ID3 header yet. |
60 | 0 | mID3Parser.Reset(); |
61 | 0 | } |
62 | 0 | mPrevFrame = mFrame; |
63 | 0 | mFrame.Reset(); |
64 | 0 | } |
65 | | |
66 | | const FrameParser::Frame& |
67 | | FrameParser::CurrentFrame() const |
68 | 0 | { |
69 | 0 | return mFrame; |
70 | 0 | } |
71 | | |
72 | | const FrameParser::Frame& |
73 | | FrameParser::PrevFrame() const |
74 | 0 | { |
75 | 0 | return mPrevFrame; |
76 | 0 | } |
77 | | |
78 | | const FrameParser::Frame& |
79 | | FrameParser::FirstFrame() const |
80 | 0 | { |
81 | 0 | return mFirstFrame; |
82 | 0 | } |
83 | | |
84 | | const ID3Parser::ID3Header& |
85 | | FrameParser::ID3Header() const |
86 | 0 | { |
87 | 0 | return mID3Parser.Header(); |
88 | 0 | } |
89 | | |
90 | | const FrameParser::VBRHeader& |
91 | | FrameParser::VBRInfo() const |
92 | 0 | { |
93 | 0 | return mVBRHeader; |
94 | 0 | } |
95 | | |
96 | | Result<bool, nsresult> |
97 | | FrameParser::Parse(BufferReader* aReader, uint32_t* aBytesToSkip) |
98 | 0 | { |
99 | 0 | MOZ_ASSERT(aReader && aBytesToSkip); |
100 | 0 | *aBytesToSkip = 0; |
101 | 0 |
|
102 | 0 | if (!mID3Parser.Header().Size() && !mFirstFrame.Length()) { |
103 | 0 | // No MP3 frames have been parsed yet, look for ID3v2 headers at file begin. |
104 | 0 | // ID3v1 tags may only be at file end. |
105 | 0 | // TODO: should we try to read ID3 tags at end of file/mid-stream, too? |
106 | 0 | const size_t prevReaderOffset = aReader->Offset(); |
107 | 0 | uint32_t tagSize; |
108 | 0 | MOZ_TRY_VAR(tagSize, mID3Parser.Parse(aReader)); |
109 | 0 | if (!!tagSize) { |
110 | 0 | // ID3 tag found, skip past it. |
111 | 0 | const uint32_t skipSize = tagSize - ID3Parser::ID3Header::SIZE; |
112 | 0 |
|
113 | 0 | if (skipSize > aReader->Remaining()) { |
114 | 0 | // Skipping across the ID3v2 tag would take us past the end of the |
115 | 0 | // buffer, therefore we return immediately and let the calling function |
116 | 0 | // handle skipping the rest of the tag. |
117 | 0 | MP3LOGV("ID3v2 tag detected, size=%d," |
118 | 0 | " needing to skip %zu bytes past the current buffer", |
119 | 0 | tagSize, skipSize - aReader->Remaining()); |
120 | 0 | *aBytesToSkip = skipSize - aReader->Remaining(); |
121 | 0 | return false; |
122 | 0 | } |
123 | 0 | MP3LOGV("ID3v2 tag detected, size=%d", tagSize); |
124 | 0 | aReader->Read(skipSize); |
125 | 0 | } else { |
126 | 0 | // No ID3v2 tag found, rewinding reader in order to search for a MPEG |
127 | 0 | // frame header. |
128 | 0 | aReader->Seek(prevReaderOffset); |
129 | 0 | } |
130 | 0 | } |
131 | 0 |
|
132 | 0 | for (auto res = aReader->ReadU8(); |
133 | 0 | res.isOk() && !mFrame.ParseNext(res.unwrap()); res = aReader->ReadU8()) |
134 | 0 | {} |
135 | 0 |
|
136 | 0 | if (mFrame.Length()) { |
137 | 0 | // MP3 frame found. |
138 | 0 | if (!mFirstFrame.Length()) { |
139 | 0 | mFirstFrame = mFrame; |
140 | 0 | } |
141 | 0 | // Indicate success. |
142 | 0 | return true; |
143 | 0 | } |
144 | 0 | return false; |
145 | 0 | } |
146 | | |
147 | | // FrameParser::Header |
148 | | |
149 | | FrameParser::FrameHeader::FrameHeader() |
150 | 0 | { |
151 | 0 | Reset(); |
152 | 0 | } |
153 | | |
154 | | uint8_t |
155 | | FrameParser::FrameHeader::Sync1() const |
156 | 0 | { |
157 | 0 | return mRaw[frame_header::SYNC1]; |
158 | 0 | } |
159 | | |
160 | | uint8_t |
161 | | FrameParser::FrameHeader::Sync2() const |
162 | 0 | { |
163 | 0 | return 0x7 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 5; |
164 | 0 | } |
165 | | |
166 | | uint8_t |
167 | | FrameParser::FrameHeader::RawVersion() const |
168 | 0 | { |
169 | 0 | return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 3; |
170 | 0 | } |
171 | | |
172 | | uint8_t |
173 | | FrameParser::FrameHeader::RawLayer() const |
174 | 0 | { |
175 | 0 | return 0x3 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 1; |
176 | 0 | } |
177 | | |
178 | | uint8_t |
179 | | FrameParser::FrameHeader::RawProtection() const |
180 | 0 | { |
181 | 0 | return 0x1 & mRaw[frame_header::SYNC2_VERSION_LAYER_PROTECTION] >> 6; |
182 | 0 | } |
183 | | |
184 | | uint8_t |
185 | | FrameParser::FrameHeader::RawBitrate() const |
186 | 0 | { |
187 | 0 | return 0xF & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 4; |
188 | 0 | } |
189 | | |
190 | | uint8_t |
191 | | FrameParser::FrameHeader::RawSampleRate() const |
192 | 0 | { |
193 | 0 | return 0x3 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 2; |
194 | 0 | } |
195 | | |
196 | | uint8_t |
197 | | FrameParser::FrameHeader::Padding() const |
198 | 0 | { |
199 | 0 | return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE] >> 1; |
200 | 0 | } |
201 | | |
202 | | uint8_t |
203 | | FrameParser::FrameHeader::Private() const |
204 | 0 | { |
205 | 0 | return 0x1 & mRaw[frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE]; |
206 | 0 | } |
207 | | |
208 | | uint8_t |
209 | | FrameParser::FrameHeader::RawChannelMode() const |
210 | 0 | { |
211 | 0 | return 0x3 & mRaw[frame_header::CHANNELMODE_MODEEXT_COPY_ORIG_EMPH] >> 6; |
212 | 0 | } |
213 | | |
214 | | int32_t |
215 | | FrameParser::FrameHeader::Layer() const |
216 | 0 | { |
217 | 0 | static const uint8_t LAYERS[4] = { 0, 3, 2, 1 }; |
218 | 0 |
|
219 | 0 | return LAYERS[RawLayer()]; |
220 | 0 | } |
221 | | |
222 | | int32_t |
223 | | FrameParser::FrameHeader::SampleRate() const |
224 | 0 | { |
225 | 0 | // Sample rates - use [version][srate] |
226 | 0 | static const uint16_t SAMPLE_RATE[4][4] = { |
227 | 0 | // clang-format off |
228 | 0 | { 11025, 12000, 8000, 0 }, // MPEG 2.5 |
229 | 0 | { 0, 0, 0, 0 }, // Reserved |
230 | 0 | { 22050, 24000, 16000, 0 }, // MPEG 2 |
231 | 0 | { 44100, 48000, 32000, 0 } // MPEG 1 |
232 | 0 | // clang-format on |
233 | 0 | }; |
234 | 0 |
|
235 | 0 | return SAMPLE_RATE[RawVersion()][RawSampleRate()]; |
236 | 0 | } |
237 | | |
238 | | int32_t |
239 | | FrameParser::FrameHeader::Channels() const |
240 | 0 | { |
241 | 0 | // 3 is single channel (mono), any other value is some variant of dual |
242 | 0 | // channel. |
243 | 0 | return RawChannelMode() == 3 ? 1 : 2; |
244 | 0 | } |
245 | | |
246 | | int32_t |
247 | | FrameParser::FrameHeader::SamplesPerFrame() const |
248 | 0 | { |
249 | 0 | // Samples per frame - use [version][layer] |
250 | 0 | static const uint16_t FRAME_SAMPLE[4][4] = { |
251 | 0 | // clang-format off |
252 | 0 | // Layer 3 2 1 Version |
253 | 0 | { 0, 576, 1152, 384 }, // 2.5 |
254 | 0 | { 0, 0, 0, 0 }, // Reserved |
255 | 0 | { 0, 576, 1152, 384 }, // 2 |
256 | 0 | { 0, 1152, 1152, 384 } // 1 |
257 | 0 | // clang-format on |
258 | 0 | }; |
259 | 0 |
|
260 | 0 | return FRAME_SAMPLE[RawVersion()][RawLayer()]; |
261 | 0 | } |
262 | | |
263 | | int32_t |
264 | | FrameParser::FrameHeader::Bitrate() const |
265 | 0 | { |
266 | 0 | // Bitrates - use [version][layer][bitrate] |
267 | 0 | static const uint16_t BITRATE[4][4][16] = { |
268 | 0 | // clang-format off |
269 | 0 | { // Version 2.5 |
270 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
271 | 0 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 |
272 | 0 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 |
273 | 0 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 |
274 | 0 | }, |
275 | 0 | { // Reserved |
276 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
277 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
278 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Invalid |
279 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } // Invalid |
280 | 0 | }, |
281 | 0 | { // Version 2 |
282 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
283 | 0 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 3 |
284 | 0 | { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }, // Layer 2 |
285 | 0 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 } // Layer 1 |
286 | 0 | }, |
287 | 0 | { // Version 1 |
288 | 0 | { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // Reserved |
289 | 0 | { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }, // Layer 3 |
290 | 0 | { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 }, // Layer 2 |
291 | 0 | { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 }, // Layer 1 |
292 | 0 | } |
293 | 0 | // clang-format on |
294 | 0 | }; |
295 | 0 |
|
296 | 0 | return 1000 * BITRATE[RawVersion()][RawLayer()][RawBitrate()]; |
297 | 0 | } |
298 | | |
299 | | int32_t |
300 | | FrameParser::FrameHeader::SlotSize() const |
301 | 0 | { |
302 | 0 | // Slot size (MPEG unit of measurement) - use [layer] |
303 | 0 | static const uint8_t SLOT_SIZE[4] = { 0, 1, 1, 4 }; // Rsvd, 3, 2, 1 |
304 | 0 |
|
305 | 0 | return SLOT_SIZE[RawLayer()]; |
306 | 0 | } |
307 | | |
308 | | bool |
309 | | FrameParser::FrameHeader::ParseNext(uint8_t c) |
310 | 0 | { |
311 | 0 | if (!Update(c)) { |
312 | 0 | Reset(); |
313 | 0 | if (!Update(c)) { |
314 | 0 | Reset(); |
315 | 0 | } |
316 | 0 | } |
317 | 0 | return IsValid(); |
318 | 0 | } |
319 | | |
320 | | bool |
321 | | FrameParser::FrameHeader::IsValid(int aPos) const |
322 | 0 | { |
323 | 0 | if (aPos >= SIZE) { |
324 | 0 | return true; |
325 | 0 | } |
326 | 0 | if (aPos == frame_header::SYNC1) { |
327 | 0 | return Sync1() == 0xFF; |
328 | 0 | } |
329 | 0 | if (aPos == frame_header::SYNC2_VERSION_LAYER_PROTECTION) { |
330 | 0 | return Sync2() == 7 && |
331 | 0 | RawVersion() != 1 && |
332 | 0 | Layer() == 3; |
333 | 0 | } |
334 | 0 | if (aPos == frame_header::BITRATE_SAMPLERATE_PADDING_PRIVATE) { |
335 | 0 | return RawBitrate() != 0xF && RawBitrate() != 0 && |
336 | 0 | RawSampleRate() != 3; |
337 | 0 | } |
338 | 0 | return true; |
339 | 0 | } |
340 | | |
341 | | bool |
342 | | FrameParser::FrameHeader::IsValid() const |
343 | 0 | { |
344 | 0 | return mPos >= SIZE; |
345 | 0 | } |
346 | | |
347 | | void |
348 | | FrameParser::FrameHeader::Reset() |
349 | 0 | { |
350 | 0 | mPos = 0; |
351 | 0 | } |
352 | | |
353 | | bool |
354 | | FrameParser::FrameHeader::Update(uint8_t c) |
355 | 0 | { |
356 | 0 | if (mPos < SIZE) { |
357 | 0 | mRaw[mPos] = c; |
358 | 0 | } |
359 | 0 | return IsValid(mPos++); |
360 | 0 | } |
361 | | |
362 | | // FrameParser::VBRHeader |
363 | | |
364 | | namespace vbr_header { |
365 | | static const char* TYPE_STR[3] = {"NONE", "XING", "VBRI"}; |
366 | | static const uint32_t TOC_SIZE = 100; |
367 | | } // namespace vbr_header |
368 | | |
369 | | FrameParser::VBRHeader::VBRHeader() |
370 | | : mType(NONE) |
371 | 0 | { |
372 | 0 | } |
373 | | |
374 | | FrameParser::VBRHeader::VBRHeaderType |
375 | | FrameParser::VBRHeader::Type() const |
376 | 0 | { |
377 | 0 | return mType; |
378 | 0 | } |
379 | | |
380 | | const Maybe<uint32_t>& |
381 | | FrameParser::VBRHeader::NumAudioFrames() const |
382 | 0 | { |
383 | 0 | return mNumAudioFrames; |
384 | 0 | } |
385 | | |
386 | | const Maybe<uint32_t>& |
387 | | FrameParser::VBRHeader::NumBytes() const |
388 | 0 | { |
389 | 0 | return mNumBytes; |
390 | 0 | } |
391 | | |
392 | | const Maybe<uint32_t>& |
393 | | FrameParser::VBRHeader::Scale() const |
394 | 0 | { |
395 | 0 | return mScale; |
396 | 0 | } |
397 | | |
398 | | bool |
399 | | FrameParser::VBRHeader::IsTOCPresent() const |
400 | 0 | { |
401 | 0 | return mTOC.size() == vbr_header::TOC_SIZE; |
402 | 0 | } |
403 | | |
404 | | bool |
405 | | FrameParser::VBRHeader::IsValid() const |
406 | 0 | { |
407 | 0 | return mType != NONE; |
408 | 0 | } |
409 | | |
410 | | bool |
411 | | FrameParser::VBRHeader::IsComplete() const |
412 | 0 | { |
413 | 0 | return IsValid() && |
414 | 0 | mNumAudioFrames.valueOr(0) > 0 && |
415 | 0 | mNumBytes.valueOr(0) > 0 |
416 | 0 | // We don't care about the scale for any computations here. |
417 | 0 | // && mScale < 101 |
418 | 0 | ; |
419 | 0 | } |
420 | | |
421 | | int64_t |
422 | | FrameParser::VBRHeader::Offset(float aDurationFac) const |
423 | 0 | { |
424 | 0 | if (!IsTOCPresent()) { |
425 | 0 | return -1; |
426 | 0 | } |
427 | 0 | |
428 | 0 | // Constrain the duration percentage to [0, 99]. |
429 | 0 | const float durationPer = |
430 | 0 | 100.0f * std::min(0.99f, std::max(0.0f, aDurationFac)); |
431 | 0 | const size_t fullPer = durationPer; |
432 | 0 | const float rest = durationPer - fullPer; |
433 | 0 |
|
434 | 0 | MOZ_ASSERT(fullPer < mTOC.size()); |
435 | 0 | int64_t offset = mTOC.at(fullPer); |
436 | 0 |
|
437 | 0 | if (rest > 0.0 && fullPer + 1 < mTOC.size()) { |
438 | 0 | offset += rest * (mTOC.at(fullPer + 1) - offset); |
439 | 0 | } |
440 | 0 |
|
441 | 0 | return offset; |
442 | 0 | } |
443 | | |
444 | | Result<bool, nsresult> |
445 | | FrameParser::VBRHeader::ParseXing(BufferReader* aReader) |
446 | 0 | { |
447 | 0 | static const uint32_t XING_TAG = BigEndian::readUint32("Xing"); |
448 | 0 | static const uint32_t INFO_TAG = BigEndian::readUint32("Info"); |
449 | 0 |
|
450 | 0 | enum Flags |
451 | 0 | { |
452 | 0 | NUM_FRAMES = 0x01, |
453 | 0 | NUM_BYTES = 0x02, |
454 | 0 | TOC = 0x04, |
455 | 0 | VBR_SCALE = 0x08 |
456 | 0 | }; |
457 | 0 |
|
458 | 0 | MOZ_ASSERT(aReader); |
459 | 0 | const size_t prevReaderOffset = aReader->Offset(); |
460 | 0 |
|
461 | 0 | // We have to search for the Xing header as its position can change. |
462 | 0 | for (auto res = aReader->PeekU32(); |
463 | 0 | res.isOk() && res.unwrap() != XING_TAG && res.unwrap() != INFO_TAG;) { |
464 | 0 | aReader->Read(1); |
465 | 0 | res = aReader->PeekU32(); |
466 | 0 | } |
467 | 0 |
|
468 | 0 | // Skip across the VBR header ID tag. |
469 | 0 | MOZ_TRY(aReader->ReadU32()); |
470 | 0 | mType = XING; |
471 | 0 |
|
472 | 0 | uint32_t flags; |
473 | 0 | MOZ_TRY_VAR(flags, aReader->ReadU32()); |
474 | 0 |
|
475 | 0 | if (flags & NUM_FRAMES) { |
476 | 0 | uint32_t frames; |
477 | 0 | MOZ_TRY_VAR(frames, aReader->ReadU32()); |
478 | 0 | mNumAudioFrames = Some(frames); |
479 | 0 | } |
480 | 0 | if (flags & NUM_BYTES) { |
481 | 0 | uint32_t bytes; |
482 | 0 | MOZ_TRY_VAR(bytes, aReader->ReadU32()); |
483 | 0 | mNumBytes = Some(bytes); |
484 | 0 | } |
485 | 0 | if (flags & TOC && aReader->Remaining() >= vbr_header::TOC_SIZE) { |
486 | 0 | if (!mNumBytes) { |
487 | 0 | // We don't have the stream size to calculate offsets, skip the TOC. |
488 | 0 | aReader->Read(vbr_header::TOC_SIZE); |
489 | 0 | } else { |
490 | 0 | mTOC.clear(); |
491 | 0 | mTOC.reserve(vbr_header::TOC_SIZE); |
492 | 0 | uint8_t data; |
493 | 0 | for (size_t i = 0; i < vbr_header::TOC_SIZE; ++i) { |
494 | 0 | MOZ_TRY_VAR(data, aReader->ReadU8()); |
495 | 0 | mTOC.push_back(1.0f / 256.0f * data * mNumBytes.value()); |
496 | 0 | } |
497 | 0 | } |
498 | 0 | } |
499 | 0 | if (flags & VBR_SCALE) { |
500 | 0 | uint32_t scale; |
501 | 0 | MOZ_TRY_VAR(scale, aReader->ReadU32()); |
502 | 0 | mScale = Some(scale); |
503 | 0 | } |
504 | 0 |
|
505 | 0 | aReader->Seek(prevReaderOffset); |
506 | 0 | return mType == XING; |
507 | 0 | } |
508 | | |
509 | | Result<bool, nsresult> |
510 | | FrameParser::VBRHeader::ParseVBRI(BufferReader* aReader) |
511 | 0 | { |
512 | 0 | static const uint32_t TAG = BigEndian::readUint32("VBRI"); |
513 | 0 | static const uint32_t OFFSET = 32 + FrameParser::FrameHeader::SIZE; |
514 | 0 | static const uint32_t FRAME_COUNT_OFFSET = OFFSET + 14; |
515 | 0 | static const uint32_t MIN_FRAME_SIZE = OFFSET + 26; |
516 | 0 |
|
517 | 0 | MOZ_ASSERT(aReader); |
518 | 0 | // ParseVBRI assumes that the ByteReader offset points to the beginning of a |
519 | 0 | // frame, therefore as a simple check, we look for the presence of a frame |
520 | 0 | // sync at that position. |
521 | 0 | auto sync = aReader->PeekU16(); |
522 | 0 | if (sync.isOk()) { // To avoid compiler complains 'set but unused'. |
523 | 0 | MOZ_ASSERT((sync.unwrap() & 0xFFE0) == 0xFFE0); |
524 | 0 | } |
525 | 0 | const size_t prevReaderOffset = aReader->Offset(); |
526 | 0 |
|
527 | 0 | // VBRI have a fixed relative position, so let's check for it there. |
528 | 0 | if (aReader->Remaining() > MIN_FRAME_SIZE) { |
529 | 0 | aReader->Seek(prevReaderOffset + OFFSET); |
530 | 0 | uint32_t tag, frames; |
531 | 0 | MOZ_TRY_VAR(tag, aReader->ReadU32()); |
532 | 0 | if (tag == TAG) { |
533 | 0 | aReader->Seek(prevReaderOffset + FRAME_COUNT_OFFSET); |
534 | 0 | MOZ_TRY_VAR(frames, aReader->ReadU32()); |
535 | 0 | mNumAudioFrames = Some(frames); |
536 | 0 | mType = VBRI; |
537 | 0 | aReader->Seek(prevReaderOffset); |
538 | 0 | return true; |
539 | 0 | } |
540 | 0 | } |
541 | 0 | aReader->Seek(prevReaderOffset); |
542 | 0 | return false; |
543 | 0 | } |
544 | | |
545 | | bool |
546 | | FrameParser::VBRHeader::Parse(BufferReader* aReader) |
547 | 0 | { |
548 | 0 | auto res = MakePair(ParseVBRI(aReader), ParseXing(aReader)); |
549 | 0 | const bool rv = (res.first().isOk() && res.first().unwrap()) || |
550 | 0 | (res.second().isOk() && res.second().unwrap()); |
551 | 0 | if (rv) { |
552 | 0 | MP3LOG("VBRHeader::Parse found valid VBR/CBR header: type=%s" |
553 | 0 | " NumAudioFrames=%u NumBytes=%u Scale=%u TOC-size=%zu", |
554 | 0 | vbr_header::TYPE_STR[Type()], NumAudioFrames().valueOr(0), |
555 | 0 | NumBytes().valueOr(0), Scale().valueOr(0), mTOC.size()); |
556 | 0 | } |
557 | 0 | return rv; |
558 | 0 | } |
559 | | |
560 | | // FrameParser::Frame |
561 | | |
562 | | void |
563 | | FrameParser::Frame::Reset() |
564 | 0 | { |
565 | 0 | mHeader.Reset(); |
566 | 0 | } |
567 | | |
568 | | int32_t |
569 | | FrameParser::Frame::Length() const |
570 | 0 | { |
571 | 0 | if (!mHeader.IsValid() || !mHeader.SampleRate()) { |
572 | 0 | return 0; |
573 | 0 | } |
574 | 0 | |
575 | 0 | const float bitsPerSample = mHeader.SamplesPerFrame() / 8.0f; |
576 | 0 | const int32_t frameLen = bitsPerSample * mHeader.Bitrate() |
577 | 0 | / mHeader.SampleRate() |
578 | 0 | + mHeader.Padding() * mHeader.SlotSize(); |
579 | 0 | return frameLen; |
580 | 0 | } |
581 | | |
582 | | bool |
583 | | FrameParser::Frame::ParseNext(uint8_t c) |
584 | 0 | { |
585 | 0 | return mHeader.ParseNext(c); |
586 | 0 | } |
587 | | |
588 | | const FrameParser::FrameHeader& |
589 | | FrameParser::Frame::Header() const |
590 | 0 | { |
591 | 0 | return mHeader; |
592 | 0 | } |
593 | | |
594 | | bool |
595 | | FrameParser::ParseVBRHeader(BufferReader* aReader) |
596 | 0 | { |
597 | 0 | return mVBRHeader.Parse(aReader); |
598 | 0 | } |
599 | | |
600 | | // ID3Parser |
601 | | |
602 | | // Constants |
603 | | namespace id3_header { |
604 | | static const int ID_LEN = 3; |
605 | | static const int VERSION_LEN = 2; |
606 | | static const int FLAGS_LEN = 1; |
607 | | static const int SIZE_LEN = 4; |
608 | | |
609 | | static const int ID_END = ID_LEN; |
610 | | static const int VERSION_END = ID_END + VERSION_LEN; |
611 | | static const int FLAGS_END = VERSION_END + FLAGS_LEN; |
612 | | static const int SIZE_END = FLAGS_END + SIZE_LEN; |
613 | | |
614 | | static const uint8_t ID[ID_LEN] = {'I', 'D', '3'}; |
615 | | |
616 | | static const uint8_t MIN_MAJOR_VER = 2; |
617 | | static const uint8_t MAX_MAJOR_VER = 4; |
618 | | } // namespace id3_header |
619 | | |
620 | | Result<uint32_t, nsresult> |
621 | | ID3Parser::Parse(BufferReader* aReader) |
622 | 0 | { |
623 | 0 | MOZ_ASSERT(aReader); |
624 | 0 |
|
625 | 0 | for (auto res = aReader->ReadU8(); |
626 | 0 | res.isOk() && !mHeader.ParseNext(res.unwrap()); res = aReader->ReadU8()) |
627 | 0 | {} |
628 | 0 |
|
629 | 0 | return mHeader.TotalTagSize(); |
630 | 0 | } |
631 | | |
632 | | void |
633 | | ID3Parser::Reset() |
634 | 0 | { |
635 | 0 | mHeader.Reset(); |
636 | 0 | } |
637 | | |
638 | | const ID3Parser::ID3Header& |
639 | | ID3Parser::Header() const |
640 | 0 | { |
641 | 0 | return mHeader; |
642 | 0 | } |
643 | | |
644 | | // ID3Parser::Header |
645 | | |
646 | | ID3Parser::ID3Header::ID3Header() |
647 | 0 | { |
648 | 0 | Reset(); |
649 | 0 | } |
650 | | |
651 | | void |
652 | | ID3Parser::ID3Header::Reset() |
653 | 0 | { |
654 | 0 | mSize = 0; |
655 | 0 | mPos = 0; |
656 | 0 | } |
657 | | |
658 | | uint8_t |
659 | | ID3Parser::ID3Header::MajorVersion() const |
660 | 0 | { |
661 | 0 | return mRaw[id3_header::ID_END]; |
662 | 0 | } |
663 | | |
664 | | uint8_t |
665 | | ID3Parser::ID3Header::MinorVersion() const |
666 | 0 | { |
667 | 0 | return mRaw[id3_header::ID_END + 1]; |
668 | 0 | } |
669 | | |
670 | | uint8_t |
671 | | ID3Parser::ID3Header::Flags() const |
672 | 0 | { |
673 | 0 | return mRaw[id3_header::FLAGS_END - id3_header::FLAGS_LEN]; |
674 | 0 | } |
675 | | |
676 | | uint32_t |
677 | | ID3Parser::ID3Header::Size() const |
678 | 0 | { |
679 | 0 | if (!IsValid()) { |
680 | 0 | return 0; |
681 | 0 | } |
682 | 0 | return mSize; |
683 | 0 | } |
684 | | |
685 | | uint8_t |
686 | | ID3Parser::ID3Header::FooterSize() const |
687 | 0 | { |
688 | 0 | if (Flags() & (1 << 4)) { |
689 | 0 | return SIZE; |
690 | 0 | } |
691 | 0 | return 0; |
692 | 0 | } |
693 | | |
694 | | uint32_t |
695 | | ID3Parser::ID3Header::TotalTagSize() const |
696 | 0 | { |
697 | 0 | if (IsValid()) { |
698 | 0 | // Header found, return total tag size. |
699 | 0 | return ID3Header::SIZE + Size() + FooterSize(); |
700 | 0 | } |
701 | 0 | return 0; |
702 | 0 | } |
703 | | |
704 | | bool |
705 | | ID3Parser::ID3Header::ParseNext(uint8_t c) |
706 | 0 | { |
707 | 0 | if (!Update(c)) { |
708 | 0 | Reset(); |
709 | 0 | if (!Update(c)) { |
710 | 0 | Reset(); |
711 | 0 | } |
712 | 0 | } |
713 | 0 | return IsValid(); |
714 | 0 | } |
715 | | |
716 | | bool |
717 | | ID3Parser::ID3Header::IsValid(int aPos) const |
718 | 0 | { |
719 | 0 | if (aPos >= SIZE) { |
720 | 0 | return true; |
721 | 0 | } |
722 | 0 | const uint8_t c = mRaw[aPos]; |
723 | 0 | switch (aPos) { |
724 | 0 | case 0: case 1: case 2: |
725 | 0 | // Expecting "ID3". |
726 | 0 | return id3_header::ID[aPos] == c; |
727 | 0 | case 3: |
728 | 0 | return MajorVersion() >= id3_header::MIN_MAJOR_VER && |
729 | 0 | MajorVersion() <= id3_header::MAX_MAJOR_VER; |
730 | 0 | case 4: |
731 | 0 | return MinorVersion() < 0xFF; |
732 | 0 | case 5: |
733 | 0 | // Validate flags for supported versions, see bug 949036. |
734 | 0 | return ((0xFF >> MajorVersion()) & c) == 0; |
735 | 0 | case 6: case 7: case 8: case 9: |
736 | 0 | return c < 0x80; |
737 | 0 | } |
738 | 0 | return true; |
739 | 0 | } |
740 | | |
741 | | bool |
742 | | ID3Parser::ID3Header::IsValid() const |
743 | 0 | { |
744 | 0 | return mPos >= SIZE; |
745 | 0 | } |
746 | | |
747 | | bool |
748 | | ID3Parser::ID3Header::Update(uint8_t c) |
749 | 0 | { |
750 | 0 | if (mPos >= id3_header::SIZE_END - id3_header::SIZE_LEN && |
751 | 0 | mPos < id3_header::SIZE_END) { |
752 | 0 | mSize <<= 7; |
753 | 0 | mSize |= c; |
754 | 0 | } |
755 | 0 | if (mPos < SIZE) { |
756 | 0 | mRaw[mPos] = c; |
757 | 0 | } |
758 | 0 | return IsValid(mPos++); |
759 | 0 | } |
760 | | |
761 | | } // namespace mozilla |