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