Coverage Report

Created: 2018-09-25 14:53

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