Source code

Revision control

Other Tools

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