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