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 "MP3Demuxer.h"
8
9 #include <algorithm>
10 #include <inttypes.h>
11 #include <limits>
12
13 #include "ByteWriter.h"
14 #include "TimeUnits.h"
15 #include "VideoUtils.h"
16 #include "mozilla/Assertions.h"
17
18 extern mozilla::LazyLogModule gMediaDemuxerLog;
19 #define MP3LOG(msg, ...) \
20 DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Debug, msg, ##__VA_ARGS__)
21 #define MP3LOGV(msg, ...) \
22 DDMOZ_LOG(gMediaDemuxerLog, LogLevel::Verbose, msg, ##__VA_ARGS__)
23
24 using mozilla::BufferReader;
25 using mozilla::media::TimeInterval;
26 using mozilla::media::TimeIntervals;
27 using mozilla::media::TimeUnit;
28
29 namespace mozilla {
30
31 // MP3Demuxer
32
MP3Demuxer(MediaResource * aSource)33 MP3Demuxer::MP3Demuxer(MediaResource* aSource) : mSource(aSource) {
34 DDLINKCHILD("source", aSource);
35 }
36
InitInternal()37 bool MP3Demuxer::InitInternal() {
38 if (!mTrackDemuxer) {
39 mTrackDemuxer = new MP3TrackDemuxer(mSource);
40 DDLINKCHILD("track demuxer", mTrackDemuxer.get());
41 }
42 return mTrackDemuxer->Init();
43 }
44
Init()45 RefPtr<MP3Demuxer::InitPromise> MP3Demuxer::Init() {
46 if (!InitInternal()) {
47 MP3LOG("MP3Demuxer::Init() failure: waiting for data");
48
49 return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_METADATA_ERR,
50 __func__);
51 }
52
53 MP3LOG("MP3Demuxer::Init() successful");
54 return InitPromise::CreateAndResolve(NS_OK, __func__);
55 }
56
GetNumberTracks(TrackInfo::TrackType aType) const57 uint32_t MP3Demuxer::GetNumberTracks(TrackInfo::TrackType aType) const {
58 return aType == TrackInfo::kAudioTrack ? 1u : 0u;
59 }
60
GetTrackDemuxer(TrackInfo::TrackType aType,uint32_t aTrackNumber)61 already_AddRefed<MediaTrackDemuxer> MP3Demuxer::GetTrackDemuxer(
62 TrackInfo::TrackType aType, uint32_t aTrackNumber) {
63 if (!mTrackDemuxer) {
64 return nullptr;
65 }
66 return RefPtr<MP3TrackDemuxer>(mTrackDemuxer).forget();
67 }
68
IsSeekable() const69 bool MP3Demuxer::IsSeekable() const { return true; }
70
NotifyDataArrived()71 void MP3Demuxer::NotifyDataArrived() {
72 // TODO: bug 1169485.
73 NS_WARNING("Unimplemented function NotifyDataArrived");
74 MP3LOGV("NotifyDataArrived()");
75 }
76
NotifyDataRemoved()77 void MP3Demuxer::NotifyDataRemoved() {
78 // TODO: bug 1169485.
79 NS_WARNING("Unimplemented function NotifyDataRemoved");
80 MP3LOGV("NotifyDataRemoved()");
81 }
82
83 // MP3TrackDemuxer
84
MP3TrackDemuxer(MediaResource * aSource)85 MP3TrackDemuxer::MP3TrackDemuxer(MediaResource* aSource)
86 : mSource(aSource),
87 mFrameLock(false),
88 mOffset(0),
89 mFirstFrameOffset(0),
90 mNumParsedFrames(0),
91 mFrameIndex(0),
92 mTotalFrameLen(0),
93 mSamplesPerFrame(0),
94 mSamplesPerSecond(0),
95 mChannels(0) {
96 DDLINKCHILD("source", aSource);
97 Reset();
98 }
99
Init()100 bool MP3TrackDemuxer::Init() {
101 Reset();
102 FastSeek(TimeUnit());
103 // Read the first frame to fetch sample rate and other meta data.
104 RefPtr<MediaRawData> frame(GetNextFrame(FindFirstFrame()));
105
106 MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d", StreamLength(),
107 !!frame);
108
109 if (!frame) {
110 return false;
111 }
112
113 // Rewind back to the stream begin to avoid dropping the first frame.
114 FastSeek(TimeUnit());
115
116 if (!mInfo) {
117 mInfo = MakeUnique<AudioInfo>();
118 }
119
120 mInfo->mRate = mSamplesPerSecond;
121 mInfo->mChannels = mChannels;
122 mInfo->mBitDepth = 16;
123 mInfo->mMimeType = "audio/mpeg";
124 mInfo->mDuration = Duration().valueOr(TimeUnit::FromInfinity());
125 if (mEncoderDelay) {
126 AutoTArray<uint8_t, 8> trimInfo;
127 ByteWriter<BigEndian> writer(trimInfo);
128 bool ok = false;
129 ok = writer.WriteU32(mEncoderDelay);
130 MOZ_ALWAYS_TRUE(ok);
131 ok = writer.WriteU32(mEncoderPadding);
132 MOZ_ALWAYS_TRUE(ok);
133 mInfo->mCodecSpecificConfig->AppendElements(trimInfo);
134 }
135
136 MP3LOG("Init mInfo={mRate=%d mChannels=%d mBitDepth=%d mDuration=%" PRId64
137 "}",
138 mInfo->mRate, mInfo->mChannels, mInfo->mBitDepth,
139 mInfo->mDuration.ToMicroseconds());
140
141 return mSamplesPerSecond && mChannels;
142 }
143
SeekPosition() const144 media::TimeUnit MP3TrackDemuxer::SeekPosition() const {
145 TimeUnit pos = Duration(mFrameIndex);
146 auto duration = Duration();
147 if (duration) {
148 pos = std::min(*duration, pos);
149 }
150 return pos;
151 }
152
LastFrame() const153 const FrameParser::Frame& MP3TrackDemuxer::LastFrame() const {
154 return mParser.PrevFrame();
155 }
156
DemuxSample()157 RefPtr<MediaRawData> MP3TrackDemuxer::DemuxSample() {
158 return GetNextFrame(FindNextFrame());
159 }
160
ID3Header() const161 const ID3Parser::ID3Header& MP3TrackDemuxer::ID3Header() const {
162 return mParser.ID3Header();
163 }
164
VBRInfo() const165 const FrameParser::VBRHeader& MP3TrackDemuxer::VBRInfo() const {
166 return mParser.VBRInfo();
167 }
168
GetInfo() const169 UniquePtr<TrackInfo> MP3TrackDemuxer::GetInfo() const { return mInfo->Clone(); }
170
Seek(const TimeUnit & aTime)171 RefPtr<MP3TrackDemuxer::SeekPromise> MP3TrackDemuxer::Seek(
172 const TimeUnit& aTime) {
173 // Efficiently seek to the position.
174 FastSeek(aTime);
175 // Correct seek position by scanning the next frames.
176 const TimeUnit seekTime = ScanUntil(aTime);
177
178 return SeekPromise::CreateAndResolve(seekTime, __func__);
179 }
180
FastSeek(const TimeUnit & aTime)181 TimeUnit MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) {
182 MP3LOG("FastSeek(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
183 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
184 aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
185 mFrameIndex, mOffset);
186
187 const auto& vbr = mParser.VBRInfo();
188 if (!aTime.ToMicroseconds()) {
189 // Quick seek to the beginning of the stream.
190 mFrameIndex = 0;
191 } else if (vbr.IsTOCPresent() && Duration() &&
192 *Duration() != TimeUnit::Zero()) {
193 // Use TOC for more precise seeking.
194 const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) /
195 Duration()->ToMicroseconds();
196 mFrameIndex = FrameIndexFromOffset(vbr.Offset(durationFrac));
197 } else if (AverageFrameLength() > 0) {
198 mFrameIndex = FrameIndexFromTime(aTime);
199 }
200
201 mOffset = OffsetFromFrameIndex(mFrameIndex);
202
203 if (mOffset > mFirstFrameOffset && StreamLength() > 0) {
204 mOffset = std::min(StreamLength() - 1, mOffset);
205 }
206
207 mParser.EndFrameSession();
208
209 MP3LOG("FastSeek End TOC=%d avgFrameLen=%f mNumParsedFrames=%" PRIu64
210 " mFrameIndex=%" PRId64 " mFirstFrameOffset=%" PRId64
211 " mOffset=%" PRIu64 " SL=%" PRId64 " NumBytes=%u",
212 vbr.IsTOCPresent(), AverageFrameLength(), mNumParsedFrames,
213 mFrameIndex, mFirstFrameOffset, mOffset, StreamLength(),
214 vbr.NumBytes().valueOr(0));
215
216 return Duration(mFrameIndex);
217 }
218
ScanUntil(const TimeUnit & aTime)219 TimeUnit MP3TrackDemuxer::ScanUntil(const TimeUnit& aTime) {
220 MP3LOG("ScanUntil(%" PRId64 ") avgFrameLen=%f mNumParsedFrames=%" PRIu64
221 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
222 aTime.ToMicroseconds(), AverageFrameLength(), mNumParsedFrames,
223 mFrameIndex, mOffset);
224
225 if (!aTime.ToMicroseconds()) {
226 return FastSeek(aTime);
227 }
228
229 if (Duration(mFrameIndex) > aTime) {
230 // We've seeked past the target time, rewind back a little to correct it.
231 const int64_t rewind = aTime.ToMicroseconds() / 100;
232 FastSeek(aTime - TimeUnit::FromMicroseconds(rewind));
233 }
234
235 if (Duration(mFrameIndex + 1) > aTime) {
236 return SeekPosition();
237 }
238
239 MediaByteRange nextRange = FindNextFrame();
240 while (SkipNextFrame(nextRange) && Duration(mFrameIndex + 1) < aTime) {
241 nextRange = FindNextFrame();
242 MP3LOGV("ScanUntil* avgFrameLen=%f mNumParsedFrames=%" PRIu64
243 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64 " Duration=%" PRId64,
244 AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset,
245 Duration(mFrameIndex + 1).ToMicroseconds());
246 }
247
248 MP3LOG("ScanUntil End avgFrameLen=%f mNumParsedFrames=%" PRIu64
249 " mFrameIndex=%" PRId64 " mOffset=%" PRIu64,
250 AverageFrameLength(), mNumParsedFrames, mFrameIndex, mOffset);
251
252 return SeekPosition();
253 }
254
GetSamples(int32_t aNumSamples)255 RefPtr<MP3TrackDemuxer::SamplesPromise> MP3TrackDemuxer::GetSamples(
256 int32_t aNumSamples) {
257 MP3LOGV("GetSamples(%d) Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
258 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
259 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
260 aNumSamples, mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
261 mSamplesPerFrame, mSamplesPerSecond, mChannels);
262
263 if (!aNumSamples) {
264 return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
265 __func__);
266 }
267
268 RefPtr<SamplesHolder> frames = new SamplesHolder();
269
270 while (aNumSamples--) {
271 RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
272 if (!frame) {
273 break;
274 }
275 if (!frame->HasValidTime()) {
276 return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_DEMUXER_ERR,
277 __func__);
278 }
279 frames->AppendSample(frame);
280 }
281
282 MP3LOGV("GetSamples() End mSamples.Size()=%zu aNumSamples=%d mOffset=%" PRIu64
283 " mNumParsedFrames=%" PRIu64 " mFrameIndex=%" PRId64
284 " mTotalFrameLen=%" PRIu64
285 " mSamplesPerFrame=%d mSamplesPerSecond=%d "
286 "mChannels=%d",
287 frames->GetSamples().Length(), aNumSamples, mOffset, mNumParsedFrames,
288 mFrameIndex, mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond,
289 mChannels);
290
291 if (frames->GetSamples().IsEmpty()) {
292 return SamplesPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_END_OF_STREAM,
293 __func__);
294 }
295 return SamplesPromise::CreateAndResolve(frames, __func__);
296 }
297
Reset()298 void MP3TrackDemuxer::Reset() {
299 MP3LOG("Reset()");
300
301 FastSeek(TimeUnit());
302 mParser.Reset();
303 }
304
305 RefPtr<MP3TrackDemuxer::SkipAccessPointPromise>
SkipToNextRandomAccessPoint(const TimeUnit & aTimeThreshold)306 MP3TrackDemuxer::SkipToNextRandomAccessPoint(const TimeUnit& aTimeThreshold) {
307 // Will not be called for audio-only resources.
308 return SkipAccessPointPromise::CreateAndReject(
309 SkipFailureHolder(NS_ERROR_DOM_MEDIA_DEMUXER_ERR, 0), __func__);
310 }
311
GetResourceOffset() const312 int64_t MP3TrackDemuxer::GetResourceOffset() const { return mOffset; }
313
GetBuffered()314 TimeIntervals MP3TrackDemuxer::GetBuffered() {
315 AutoPinned<MediaResource> stream(mSource.GetResource());
316 TimeIntervals buffered;
317
318 if (Duration() && stream->IsDataCachedToEndOfResource(0)) {
319 // Special case completely cached files. This also handles local files.
320 buffered += TimeInterval(TimeUnit(), *Duration());
321 MP3LOGV("buffered = [[%" PRId64 ", %" PRId64 "]]",
322 TimeUnit().ToMicroseconds(), Duration()->ToMicroseconds());
323 return buffered;
324 }
325
326 MediaByteRangeSet ranges;
327 nsresult rv = stream->GetCachedRanges(ranges);
328 NS_ENSURE_SUCCESS(rv, buffered);
329
330 for (const auto& range : ranges) {
331 if (range.IsEmpty()) {
332 continue;
333 }
334 TimeUnit start = Duration(FrameIndexFromOffset(range.mStart));
335 TimeUnit end = Duration(FrameIndexFromOffset(range.mEnd));
336 MP3LOGV("buffered += [%" PRId64 ", %" PRId64 "]", start.ToMicroseconds(),
337 end.ToMicroseconds());
338 buffered += TimeInterval(start, end);
339 }
340
341 // If the number of frames reported by the header is valid,
342 // the duration calculated from it is the maximal duration.
343 if (ValidNumAudioFrames() && Duration()) {
344 TimeInterval duration = TimeInterval(TimeUnit(), *Duration());
345 return buffered.Intersection(duration);
346 }
347
348 return buffered;
349 }
350
StreamLength() const351 int64_t MP3TrackDemuxer::StreamLength() const { return mSource.GetLength(); }
352
Duration() const353 media::NullableTimeUnit MP3TrackDemuxer::Duration() const {
354 if (!mNumParsedFrames) {
355 return Nothing();
356 }
357
358 int64_t numFrames = 0;
359 const auto numAudioFrames = ValidNumAudioFrames();
360 if (numAudioFrames) {
361 // VBR headers don't include the VBR header frame.
362 numFrames = numAudioFrames.value() + 1;
363 return Some(Duration(numFrames));
364 }
365
366 const int64_t streamLen = StreamLength();
367 if (streamLen < 0) { // Live streams.
368 // Unknown length, we can't estimate duration.
369 return Nothing();
370 }
371 // We can't early return when streamLen < 0 before checking numAudioFrames
372 // since some live radio will give an opening remark before playing music
373 // and the duration of the opening talk can be calculated by numAudioFrames.
374
375 const int64_t size = streamLen - mFirstFrameOffset;
376 MOZ_ASSERT(size);
377
378 // If it's CBR, calculate the duration by bitrate.
379 if (!mParser.VBRInfo().IsValid()) {
380 const int32_t bitrate = mParser.CurrentFrame().Header().Bitrate();
381 return Some(
382 media::TimeUnit::FromSeconds(static_cast<double>(size) * 8 / bitrate));
383 }
384
385 if (AverageFrameLength() > 0) {
386 numFrames = size / AverageFrameLength();
387 }
388
389 return Some(Duration(numFrames));
390 }
391
Duration(int64_t aNumFrames) const392 TimeUnit MP3TrackDemuxer::Duration(int64_t aNumFrames) const {
393 if (!mSamplesPerSecond) {
394 return TimeUnit::FromMicroseconds(-1);
395 }
396
397 const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
398 return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
399 }
400
FindFirstFrame()401 MediaByteRange MP3TrackDemuxer::FindFirstFrame() {
402 // We attempt to find multiple successive frames to avoid locking onto a false
403 // positive if we're fed a stream that has been cut mid-frame.
404 // For compatibility reasons we have to use the same frame count as Chrome,
405 // since some web sites actually use a file that short to test our playback
406 // capabilities.
407 static const int MIN_SUCCESSIVE_FRAMES = 3;
408 mFrameLock = false;
409
410 MediaByteRange candidateFrame = FindNextFrame();
411 int numSuccFrames = candidateFrame.Length() > 0;
412 MediaByteRange currentFrame = candidateFrame;
413 MP3LOGV("FindFirst() first candidate frame: mOffset=%" PRIu64
414 " Length()=%" PRIu64,
415 candidateFrame.mStart, candidateFrame.Length());
416
417 while (candidateFrame.Length()) {
418 mParser.EndFrameSession();
419 mOffset = currentFrame.mEnd;
420 const MediaByteRange prevFrame = currentFrame;
421
422 // FindNextFrame() here will only return frames consistent with our
423 // candidate frame.
424 currentFrame = FindNextFrame();
425 numSuccFrames += currentFrame.Length() > 0;
426 // Multiple successive false positives, which wouldn't be caught by the
427 // consistency checks alone, can be detected by wrong alignment (non-zero
428 // gap between frames).
429 const int64_t frameSeparation = currentFrame.mStart - prevFrame.mEnd;
430
431 if (!currentFrame.Length() || frameSeparation != 0) {
432 MP3LOGV(
433 "FindFirst() not enough successive frames detected, "
434 "rejecting candidate frame: successiveFrames=%d, last "
435 "Length()=%" PRIu64 ", last frameSeparation=%" PRId64,
436 numSuccFrames, currentFrame.Length(), frameSeparation);
437
438 mParser.ResetFrameData();
439 mOffset = candidateFrame.mStart + 1;
440 candidateFrame = FindNextFrame();
441 numSuccFrames = candidateFrame.Length() > 0;
442 currentFrame = candidateFrame;
443 MP3LOGV("FindFirst() new candidate frame: mOffset=%" PRIu64
444 " Length()=%" PRIu64,
445 candidateFrame.mStart, candidateFrame.Length());
446 } else if (numSuccFrames >= MIN_SUCCESSIVE_FRAMES) {
447 MP3LOG(
448 "FindFirst() accepting candidate frame: "
449 "successiveFrames=%d",
450 numSuccFrames);
451 mFrameLock = true;
452 return candidateFrame;
453 } else if (prevFrame.mStart == mParser.TotalID3HeaderSize() &&
454 currentFrame.mEnd == StreamLength()) {
455 // We accept streams with only two frames if both frames are valid. This
456 // is to handle very short files and provide parity with Chrome. See
457 // bug 1432195 for more information. This will not handle short files
458 // with a trailing tag, but as of writing we lack infrastructure to
459 // handle such tags.
460 MP3LOG(
461 "FindFirst() accepting candidate frame for short stream: "
462 "successiveFrames=%d",
463 numSuccFrames);
464 mFrameLock = true;
465 return candidateFrame;
466 }
467 }
468
469 MP3LOG("FindFirst() no suitable first frame found");
470 return candidateFrame;
471 }
472
VerifyFrameConsistency(const FrameParser::Frame & aFrame1,const FrameParser::Frame & aFrame2)473 static bool VerifyFrameConsistency(const FrameParser::Frame& aFrame1,
474 const FrameParser::Frame& aFrame2) {
475 const auto& h1 = aFrame1.Header();
476 const auto& h2 = aFrame2.Header();
477
478 return h1.IsValid() && h2.IsValid() && h1.Layer() == h2.Layer() &&
479 h1.SlotSize() == h2.SlotSize() &&
480 h1.SamplesPerFrame() == h2.SamplesPerFrame() &&
481 h1.Channels() == h2.Channels() && h1.SampleRate() == h2.SampleRate() &&
482 h1.RawVersion() == h2.RawVersion() &&
483 h1.RawProtection() == h2.RawProtection();
484 }
485
FindNextFrame()486 MediaByteRange MP3TrackDemuxer::FindNextFrame() {
487 static const int BUFFER_SIZE = 64;
488 static const uint32_t MAX_SKIPPABLE_BYTES = 1024 * BUFFER_SIZE;
489
490 MP3LOGV("FindNext() Begin mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
491 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
492 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
493 mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
494 mSamplesPerFrame, mSamplesPerSecond, mChannels);
495
496 uint8_t buffer[BUFFER_SIZE];
497 uint32_t read = 0;
498
499 bool foundFrame = false;
500 int64_t frameHeaderOffset = 0;
501 int64_t startOffset = mOffset;
502 const bool searchingForID3 = !mParser.ID3Header().HasSizeBeenSet();
503
504 // Check whether we've found a valid MPEG frame.
505 while (!foundFrame) {
506 // How many bytes we can go without finding a valid MPEG frame
507 // (effectively rounded up to the next full buffer size multiple, as we
508 // only check this before reading the next set of data into the buffer).
509
510 // This default value of 0 will be used during testing whether we're being
511 // fed a valid stream, which shouldn't have any gaps between frames.
512 uint32_t maxSkippableBytes = 0;
513
514 if (!mParser.FirstFrame().Length()) {
515 // We're looking for the first valid frame. A well-formed file should
516 // have its first frame header right at the start (skipping an ID3 tag
517 // if necessary), but in order to support files that might have been
518 // improperly cut, we search the first few kB for a frame header.
519 maxSkippableBytes = MAX_SKIPPABLE_BYTES;
520 // Since we're counting the skipped bytes from the offset we started
521 // this parsing session with, we need to discount the ID3 tag size only
522 // if we were looking for one during the current frame parsing session.
523 if (searchingForID3) {
524 maxSkippableBytes += mParser.TotalID3HeaderSize();
525 }
526 } else if (mFrameLock) {
527 // We've found a valid MPEG stream, so don't impose any limits
528 // to allow skipping corrupted data until we hit EOS.
529 maxSkippableBytes = std::numeric_limits<uint32_t>::max();
530 }
531
532 if ((mOffset - startOffset > maxSkippableBytes) ||
533 (read = Read(buffer, mOffset, BUFFER_SIZE)) == 0) {
534 MP3LOG("FindNext() EOS or exceeded maxSkippeableBytes without a frame");
535 // This is not a valid MPEG audio stream or we've reached EOS, give up.
536 break;
537 }
538
539 BufferReader reader(buffer, read);
540 uint32_t bytesToSkip = 0;
541 auto res = mParser.Parse(&reader, &bytesToSkip);
542 foundFrame = res.unwrapOr(false);
543 int64_t readerOffset = static_cast<int64_t>(reader.Offset());
544 frameHeaderOffset = mOffset + readerOffset - FrameParser::FrameHeader::SIZE;
545
546 // If we've found neither an MPEG frame header nor an ID3v2 tag,
547 // the reader shouldn't have any bytes remaining.
548 MOZ_ASSERT(foundFrame || bytesToSkip || !reader.Remaining());
549
550 if (foundFrame && mParser.FirstFrame().Length() &&
551 !VerifyFrameConsistency(mParser.FirstFrame(), mParser.CurrentFrame())) {
552 // We've likely hit a false-positive, ignore it and proceed with the
553 // search for the next valid frame.
554 foundFrame = false;
555 mOffset = frameHeaderOffset + 1;
556 mParser.EndFrameSession();
557 } else {
558 // Advance mOffset by the amount of bytes read and if necessary,
559 // skip an ID3v2 tag which stretches beyond the current buffer.
560 NS_ENSURE_TRUE(mOffset + read + bytesToSkip > mOffset,
561 MediaByteRange(0, 0));
562 mOffset += static_cast<int64_t>(read + bytesToSkip);
563 }
564 }
565
566 if (!foundFrame || !mParser.CurrentFrame().Length()) {
567 MP3LOG("FindNext() Exit foundFrame=%d mParser.CurrentFrame().Length()=%d ",
568 foundFrame, mParser.CurrentFrame().Length());
569 return {0, 0};
570 }
571
572 if (frameHeaderOffset + mParser.CurrentFrame().Length() + BUFFER_SIZE >
573 StreamLength()) {
574 mEOS = true;
575 }
576
577 MP3LOGV("FindNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
578 " mFrameIndex=%" PRId64 " frameHeaderOffset=%" PRId64
579 " mTotalFrameLen=%" PRIu64
580 " mSamplesPerFrame=%d mSamplesPerSecond=%d"
581 " mChannels=%d",
582 mOffset, mNumParsedFrames, mFrameIndex, frameHeaderOffset,
583 mTotalFrameLen, mSamplesPerFrame, mSamplesPerSecond, mChannels);
584
585 return {frameHeaderOffset,
586 frameHeaderOffset + mParser.CurrentFrame().Length()};
587 }
588
SkipNextFrame(const MediaByteRange & aRange)589 bool MP3TrackDemuxer::SkipNextFrame(const MediaByteRange& aRange) {
590 if (!mNumParsedFrames || !aRange.Length()) {
591 // We can't skip the first frame, since it could contain VBR headers.
592 RefPtr<MediaRawData> frame(GetNextFrame(aRange));
593 return frame;
594 }
595
596 UpdateState(aRange);
597
598 MP3LOGV("SkipNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
599 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
600 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d",
601 mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
602 mSamplesPerFrame, mSamplesPerSecond, mChannels);
603
604 return true;
605 }
606
GetNextFrame(const MediaByteRange & aRange)607 already_AddRefed<MediaRawData> MP3TrackDemuxer::GetNextFrame(
608 const MediaByteRange& aRange) {
609 MP3LOG("GetNext() Begin({mStart=%" PRId64 " Length()=%" PRId64 "})",
610 aRange.mStart, aRange.Length());
611 if (!aRange.Length()) {
612 return nullptr;
613 }
614
615 RefPtr<MediaRawData> frame = new MediaRawData();
616 frame->mOffset = aRange.mStart;
617
618 UniquePtr<MediaRawDataWriter> frameWriter(frame->CreateWriter());
619 if (!frameWriter->SetSize(static_cast<size_t>(aRange.Length()))) {
620 MP3LOG("GetNext() Exit failed to allocated media buffer");
621 return nullptr;
622 }
623
624 const uint32_t read =
625 Read(frameWriter->Data(), frame->mOffset, frame->Size());
626
627 if (read != aRange.Length()) {
628 MP3LOG("GetNext() Exit read=%u frame->Size()=%zu", read, frame->Size());
629 return nullptr;
630 }
631
632 UpdateState(aRange);
633
634 frame->mTime = Duration(mFrameIndex - 1);
635 frame->mDuration = Duration(1);
636 frame->mTimecode = frame->mTime;
637 frame->mKeyframe = true;
638 frame->mEOS = mEOS;
639
640 MOZ_ASSERT(!frame->mTime.IsNegative());
641 MOZ_ASSERT(frame->mDuration.IsPositive());
642
643 if (mNumParsedFrames == 1) {
644 // First frame parsed, let's read VBR info if available.
645 BufferReader reader(frame->Data(), frame->Size());
646 mFirstFrameOffset = frame->mOffset;
647
648 if (mParser.ParseVBRHeader(&reader)) {
649 // Parsing was successful
650 if (mParser.VBRInfo().Type() == FrameParser::VBRHeader::XING) {
651 MP3LOGV("XING header present, skipping encoder delay (%u frames)",
652 mParser.VBRInfo().EncoderDelay());
653 mEncoderDelay = mParser.VBRInfo().EncoderDelay();
654 mEncoderPadding = mParser.VBRInfo().EncoderPadding();
655 }
656 }
657 }
658
659 MP3LOGV("GetNext() End mOffset=%" PRIu64 " mNumParsedFrames=%" PRIu64
660 " mFrameIndex=%" PRId64 " mTotalFrameLen=%" PRIu64
661 " mSamplesPerFrame=%d mSamplesPerSecond=%d mChannels=%d, mEOS=%s",
662 mOffset, mNumParsedFrames, mFrameIndex, mTotalFrameLen,
663 mSamplesPerFrame, mSamplesPerSecond, mChannels,
664 mEOS ? "true" : "false");
665
666 return frame.forget();
667 }
668
OffsetFromFrameIndex(int64_t aFrameIndex) const669 int64_t MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const {
670 int64_t offset = 0;
671 const auto& vbr = mParser.VBRInfo();
672
673 if (vbr.IsComplete()) {
674 offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() /
675 vbr.NumAudioFrames().value();
676 } else if (AverageFrameLength() > 0) {
677 offset = mFirstFrameOffset + aFrameIndex * AverageFrameLength();
678 }
679
680 MP3LOGV("OffsetFromFrameIndex(%" PRId64 ") -> %" PRId64, aFrameIndex, offset);
681 return std::max<int64_t>(mFirstFrameOffset, offset);
682 }
683
FrameIndexFromOffset(int64_t aOffset) const684 int64_t MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
685 int64_t frameIndex = 0;
686 const auto& vbr = mParser.VBRInfo();
687
688 if (vbr.IsComplete()) {
689 frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
690 vbr.NumBytes().value() * vbr.NumAudioFrames().value();
691 frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
692 } else if (AverageFrameLength() > 0) {
693 frameIndex = (aOffset - mFirstFrameOffset) / AverageFrameLength();
694 }
695
696 MP3LOGV("FrameIndexFromOffset(%" PRId64 ") -> %" PRId64, aOffset, frameIndex);
697 return std::max<int64_t>(0, frameIndex);
698 }
699
FrameIndexFromTime(const media::TimeUnit & aTime) const700 int64_t MP3TrackDemuxer::FrameIndexFromTime(
701 const media::TimeUnit& aTime) const {
702 int64_t frameIndex = 0;
703 if (mSamplesPerSecond > 0 && mSamplesPerFrame > 0) {
704 frameIndex = aTime.ToSeconds() * mSamplesPerSecond / mSamplesPerFrame - 1;
705 }
706
707 MP3LOGV("FrameIndexFromOffset(%fs) -> %" PRId64, aTime.ToSeconds(),
708 frameIndex);
709 return std::max<int64_t>(0, frameIndex);
710 }
711
UpdateState(const MediaByteRange & aRange)712 void MP3TrackDemuxer::UpdateState(const MediaByteRange& aRange) {
713 // Prevent overflow.
714 if (mTotalFrameLen + aRange.Length() < mTotalFrameLen) {
715 // These variables have a linear dependency and are only used to derive the
716 // average frame length.
717 mTotalFrameLen /= 2;
718 mNumParsedFrames /= 2;
719 }
720
721 // Full frame parsed, move offset to its end.
722 mOffset = aRange.mEnd;
723
724 mTotalFrameLen += aRange.Length();
725
726 if (!mSamplesPerFrame) {
727 mSamplesPerFrame = mParser.CurrentFrame().Header().SamplesPerFrame();
728 mSamplesPerSecond = mParser.CurrentFrame().Header().SampleRate();
729 mChannels = mParser.CurrentFrame().Header().Channels();
730 }
731
732 ++mNumParsedFrames;
733 ++mFrameIndex;
734 MOZ_ASSERT(mFrameIndex > 0);
735
736 // Prepare the parser for the next frame parsing session.
737 mParser.EndFrameSession();
738 }
739
Read(uint8_t * aBuffer,int64_t aOffset,int32_t aSize)740 uint32_t MP3TrackDemuxer::Read(uint8_t* aBuffer, int64_t aOffset,
741 int32_t aSize) {
742 MP3LOGV("MP3TrackDemuxer::Read(%p %" PRId64 " %d)", aBuffer, aOffset, aSize);
743
744 const int64_t streamLen = StreamLength();
745 if (mInfo && streamLen > 0) {
746 // Prevent blocking reads after successful initialization.
747 uint64_t max = streamLen > aOffset ? streamLen - aOffset : 0;
748 aSize = std::min<int64_t>(aSize, max);
749 }
750
751 uint32_t read = 0;
752 MP3LOGV("MP3TrackDemuxer::Read -> ReadAt(%u)", aSize);
753 const nsresult rv = mSource.ReadAt(aOffset, reinterpret_cast<char*>(aBuffer),
754 static_cast<uint32_t>(aSize), &read);
755 NS_ENSURE_SUCCESS(rv, 0);
756 return read;
757 }
758
AverageFrameLength() const759 double MP3TrackDemuxer::AverageFrameLength() const {
760 if (mNumParsedFrames) {
761 return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
762 }
763 const auto& vbr = mParser.VBRInfo();
764 if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) {
765 return static_cast<double>(vbr.NumBytes().value()) /
766 (vbr.NumAudioFrames().value() + 1);
767 }
768 return 0.0;
769 }
770
ValidNumAudioFrames() const771 Maybe<uint32_t> MP3TrackDemuxer::ValidNumAudioFrames() const {
772 return mParser.VBRInfo().IsValid() &&
773 mParser.VBRInfo().NumAudioFrames().valueOr(0) + 1 > 1
774 ? mParser.VBRInfo().NumAudioFrames()
775 : Nothing();
776 }
777
778 } // namespace mozilla
779
780 #undef MP3LOG
781 #undef MP3LOGV
782