#604: M1236639 M1257931 M1256590 M1291543 M1363647 + backbugs

This commit is contained in:
Cameron Kaiser 2020-06-14 22:01:40 -07:00
parent 7850b17a7d
commit 80ece6a151
2 changed files with 97 additions and 22 deletions

View File

@ -157,7 +157,6 @@ MP3TrackDemuxer::SeekPosition() const {
return pos; return pos;
} }
#ifdef ENABLE_TESTS
const FrameParser::Frame& const FrameParser::Frame&
MP3TrackDemuxer::LastFrame() const { MP3TrackDemuxer::LastFrame() const {
return mParser.PrevFrame(); return mParser.PrevFrame();
@ -167,7 +166,6 @@ RefPtr<MediaRawData>
MP3TrackDemuxer::DemuxSample() { MP3TrackDemuxer::DemuxSample() {
return GetNextFrame(FindNextFrame()); return GetNextFrame(FindNextFrame());
} }
#endif
const ID3Parser::ID3Header& const ID3Parser::ID3Header&
MP3TrackDemuxer::ID3Header() const { MP3TrackDemuxer::ID3Header() const {
@ -204,7 +202,7 @@ MP3TrackDemuxer::FastSeek(const TimeUnit& aTime) {
if (!aTime.ToMicroseconds()) { if (!aTime.ToMicroseconds()) {
// Quick seek to the beginning of the stream. // Quick seek to the beginning of the stream.
mFrameIndex = 0; mFrameIndex = 0;
} else if (vbr.IsTOCPresent()) { } else if (vbr.IsTOCPresent() && Duration().ToMicroseconds() > 0) {
// Use TOC for more precise seeking. // Use TOC for more precise seeking.
const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) / const float durationFrac = static_cast<float>(aTime.ToMicroseconds()) /
Duration().ToMicroseconds(); Duration().ToMicroseconds();
@ -347,7 +345,7 @@ MP3TrackDemuxer::Duration() const {
int64_t numFrames = 0; int64_t numFrames = 0;
const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames(); const auto numAudioFrames = mParser.VBRInfo().NumAudioFrames();
if (numAudioFrames) { if (mParser.VBRInfo().IsValid() && numAudioFrames.valueOr(0) + 1 > 1) {
// VBR headers don't include the VBR header frame. // VBR headers don't include the VBR header frame.
numFrames = numAudioFrames.value() + 1; numFrames = numAudioFrames.value() + 1;
} else { } else {
@ -356,7 +354,9 @@ MP3TrackDemuxer::Duration() const {
// Unknown length, we can't estimate duration. // Unknown length, we can't estimate duration.
return TimeUnit::FromMicroseconds(-1); return TimeUnit::FromMicroseconds(-1);
} }
numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength(); if (AverageFrameLength() > 0) {
numFrames = (streamLen - mFirstFrameOffset) / AverageFrameLength();
}
} }
return Duration(numFrames); return Duration(numFrames);
} }
@ -371,6 +371,55 @@ MP3TrackDemuxer::Duration(int64_t aNumFrames) const {
return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame); return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
} }
MediaByteRange
MP3TrackDemuxer::FindFirstFrame() {
// Get engough successive frames to avoid invalid frame from cut stream.
// However, some website use very short mp3 file so using the same value as Chrome.
static const int MIN_SUCCESSIVE_FRAMES = 3;
MediaByteRange candidateFrame = FindNextFrame();
int numSuccFrames = candidateFrame.Length() > 0;
MediaByteRange currentFrame = candidateFrame;
MP3LOGV("FindFirst() first candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64,
candidateFrame.mStart, candidateFrame.Length());
while (candidateFrame.Length() && numSuccFrames < MIN_SUCCESSIVE_FRAMES) {
mParser.EndFrameSession();
mOffset = currentFrame.mEnd;
const MediaByteRange prevFrame = currentFrame;
// FindNextFrame() here will only return frames consistent with our candidate frame.
currentFrame = FindNextFrame();
numSuccFrames += currentFrame.Length() > 0;
// Multiple successive false positives, which wouldn't be caught by the consistency
// checks alone, can be detected by wrong alignment (non-zero gap between frames).
const int64_t frameSeparation = currentFrame.mStart - prevFrame.mEnd;
if (!currentFrame.Length() || frameSeparation != 0) {
MP3LOGV("FindFirst() not enough successive frames detected, "
"rejecting candidate frame: successiveFrames=%d, last Length()=%" PRIu64
", last frameSeparation=%" PRId64, numSuccFrames, currentFrame.Length(),
frameSeparation);
mParser.ResetFrameData();
mOffset = candidateFrame.mStart + 1;
candidateFrame = FindNextFrame();
numSuccFrames = candidateFrame.Length() > 0;
currentFrame = candidateFrame;
MP3LOGV("FindFirst() new candidate frame: mOffset=%" PRIu64 " Length()=%" PRIu64,
candidateFrame.mStart, candidateFrame.Length());
}
}
if (numSuccFrames >= MIN_SUCCESSIVE_FRAMES) {
MP3LOG("FindFirst() accepting candidate frame: "
"successiveFrames=%d", numSuccFrames);
} else {
MP3LOG("FindFirst() no suitable first frame found");
}
return candidateFrame;
}
static bool static bool
VerifyFrameConsistency( VerifyFrameConsistency(
const FrameParser::Frame& aFrame1, const FrameParser::Frame& aFrame2) { const FrameParser::Frame& aFrame1, const FrameParser::Frame& aFrame2) {
@ -511,7 +560,6 @@ MP3TrackDemuxer::GetNextFrame(const MediaByteRange& aRange) {
if (mNumParsedFrames == 1) { if (mNumParsedFrames == 1) {
// First frame parsed, let's read VBR info if available. // First frame parsed, let's read VBR info if available.
// TODO: read info that helps with seeking (bug 1163667).
ByteReader reader(frame->Data(), frame->Size()); ByteReader reader(frame->Data(), frame->Size());
mParser.ParseVBRHeader(&reader); mParser.ParseVBRHeader(&reader);
reader.DiscardRemaining(); reader.DiscardRemaining();
@ -532,7 +580,7 @@ MP3TrackDemuxer::OffsetFromFrameIndex(int64_t aFrameIndex) const {
int64_t offset = 0; int64_t offset = 0;
const auto& vbr = mParser.VBRInfo(); const auto& vbr = mParser.VBRInfo();
if (vbr.NumBytes() && vbr.NumAudioFrames()) { if (vbr.IsComplete()) {
offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() / offset = mFirstFrameOffset + aFrameIndex * vbr.NumBytes().value() /
vbr.NumAudioFrames().value(); vbr.NumAudioFrames().value();
} else if (AverageFrameLength() > 0) { } else if (AverageFrameLength() > 0) {
@ -548,7 +596,7 @@ MP3TrackDemuxer::FrameIndexFromOffset(int64_t aOffset) const {
int64_t frameIndex = 0; int64_t frameIndex = 0;
const auto& vbr = mParser.VBRInfo(); const auto& vbr = mParser.VBRInfo();
if (vbr.NumBytes() && vbr.NumAudioFrames()) { if (vbr.IsComplete()) {
frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) / frameIndex = static_cast<float>(aOffset - mFirstFrameOffset) /
vbr.NumBytes().value() * vbr.NumAudioFrames().value(); vbr.NumBytes().value() * vbr.NumAudioFrames().value();
frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex); frameIndex = std::min<int64_t>(vbr.NumAudioFrames().value(), frameIndex);
@ -624,7 +672,7 @@ MP3TrackDemuxer::AverageFrameLength() const {
return static_cast<double>(mTotalFrameLen) / mNumParsedFrames; return static_cast<double>(mTotalFrameLen) / mNumParsedFrames;
} }
const auto& vbr = mParser.VBRInfo(); const auto& vbr = mParser.VBRInfo();
if (vbr.NumBytes() && vbr.NumAudioFrames()) { if (vbr.IsComplete() && vbr.NumAudioFrames().value() + 1) {
return static_cast<double>(vbr.NumBytes().value()) / return static_cast<double>(vbr.NumBytes().value()) /
(vbr.NumAudioFrames().value() + 1); (vbr.NumAudioFrames().value() + 1);
} }
@ -651,15 +699,20 @@ FrameParser::Reset() {
mFrame.Reset(); mFrame.Reset();
} }
void
FrameParser::ResetFrameData() {
mFrame.Reset();
mFirstFrame.Reset();
mPrevFrame.Reset();
}
void void
FrameParser::EndFrameSession() { FrameParser::EndFrameSession() {
if (!mID3Parser.Header().IsValid()) { if (!mID3Parser.Header().IsValid()) {
// Reset ID3 tags only if we have not parsed a valid ID3 header yet. // Reset ID3 tags only if we have not parsed a valid ID3 header yet.
mID3Parser.Reset(); mID3Parser.Reset();
} }
#ifdef ENABLE_TESTS
mPrevFrame = mFrame; mPrevFrame = mFrame;
#endif
mFrame.Reset(); mFrame.Reset();
} }
@ -668,12 +721,10 @@ FrameParser::CurrentFrame() const {
return mFrame; return mFrame;
} }
#ifdef ENABLE_TESTS
const FrameParser::Frame& const FrameParser::Frame&
FrameParser::PrevFrame() const { FrameParser::PrevFrame() const {
return mPrevFrame; return mPrevFrame;
} }
#endif
const FrameParser::Frame& const FrameParser::Frame&
FrameParser::FirstFrame() const { FrameParser::FirstFrame() const {
@ -960,6 +1011,21 @@ FrameParser::VBRHeader::IsTOCPresent() const {
return mTOC.size() == vbr_header::TOC_SIZE; return mTOC.size() == vbr_header::TOC_SIZE;
} }
bool
FrameParser::VBRHeader::IsValid() const {
return mType != NONE;
}
bool
FrameParser::VBRHeader::IsComplete() const {
return IsValid() &&
mNumAudioFrames.valueOr(0) > 0 &&
mNumBytes.valueOr(0) > 0 &&
// We don't care about the scale for any computations here.
// mScale < 101 &&
true;
}
int64_t int64_t
FrameParser::VBRHeader::Offset(float aDurationFac) const { FrameParser::VBRHeader::Offset(float aDurationFac) const {
if (!IsTOCPresent()) { if (!IsTOCPresent()) {
@ -1047,7 +1113,7 @@ FrameParser::VBRHeader::ParseVBRI(ByteReader* aReader) {
MOZ_ASSERT(aReader); MOZ_ASSERT(aReader);
// ParseVBRI assumes that the ByteReader offset points to the beginning of a frame, // ParseVBRI assumes that the ByteReader offset points to the beginning of a frame,
// therefore as a simple check, we look for the presence of a frame sync at that position. // therefore as a simple check, we look for the presence of a frame sync at that position.
MOZ_ASSERT(aReader->PeekU16() & 0xFFE0); MOZ_ASSERT((aReader->PeekU16() & 0xFFE0) == 0xFFE0);
const size_t prevReaderOffset = aReader->Offset(); const size_t prevReaderOffset = aReader->Offset();
// VBRI have a fixed relative position, so let's check for it there. // VBRI have a fixed relative position, so let's check for it there.

View File

@ -232,6 +232,12 @@ public:
// Returns true iff Xing/Info TOC (table of contents) is present. // Returns true iff Xing/Info TOC (table of contents) is present.
bool IsTOCPresent() const; bool IsTOCPresent() const;
// Returns whether the header is valid (type XING or VBRI).
bool IsValid() const;
// Returns whether the header is valid and contains reasonable non-zero field values.
bool IsComplete() const;
// Returns the byte offset for the given duration percentage as a factor // Returns the byte offset for the given duration percentage as a factor
// (0: begin, 1.0: end). // (0: begin, 1.0: end).
int64_t Offset(float aDurationFac) const; int64_t Offset(float aDurationFac) const;
@ -298,10 +304,8 @@ public:
// Returns the currently parsed frame. Reset via Reset or EndFrameSession. // Returns the currently parsed frame. Reset via Reset or EndFrameSession.
const Frame& CurrentFrame() const; const Frame& CurrentFrame() const;
#ifdef ENABLE_TESTS
// Returns the previously parsed frame. Reset via Reset. // Returns the previously parsed frame. Reset via Reset.
const Frame& PrevFrame() const; const Frame& PrevFrame() const;
#endif
// Returns the first parsed frame. Reset via Reset. // Returns the first parsed frame. Reset via Reset.
const Frame& FirstFrame() const; const Frame& FirstFrame() const;
@ -312,9 +316,13 @@ public:
// Returns the parsed VBR header info. Note: check for validity by type. // Returns the parsed VBR header info. Note: check for validity by type.
const VBRHeader& VBRInfo() const; const VBRHeader& VBRInfo() const;
// Resets the parser. Don't use between frames as first frame data is reset. // Resets the parser.
void Reset(); void Reset();
// Resets all frame data, but not the ID3Header.
// Don't use between frames as first frame data is reset.
void ResetFrameData();
// Clear the last parsed frame to allow for next frame parsing, i.e.: // Clear the last parsed frame to allow for next frame parsing, i.e.:
// - sets PrevFrame to CurrentFrame // - sets PrevFrame to CurrentFrame
// - resets the CurrentFrame // - resets the CurrentFrame
@ -344,9 +352,7 @@ private:
// previously parsed frame for debugging and the currently parsed frame. // previously parsed frame for debugging and the currently parsed frame.
Frame mFirstFrame; Frame mFirstFrame;
Frame mFrame; Frame mFrame;
#ifdef ENABLE_TESTS
Frame mPrevFrame; Frame mPrevFrame;
#endif
}; };
// The MP3 demuxer used to extract MPEG frames and side information out of // The MP3 demuxer used to extract MPEG frames and side information out of
@ -373,10 +379,8 @@ public:
// Returns the estimated current seek position time. // Returns the estimated current seek position time.
media::TimeUnit SeekPosition() const; media::TimeUnit SeekPosition() const;
#ifdef ENABLE_TESTS
const FrameParser::Frame& LastFrame() const; const FrameParser::Frame& LastFrame() const;
RefPtr<MediaRawData> DemuxSample(); RefPtr<MediaRawData> DemuxSample();
#endif
const ID3Parser::ID3Header& ID3Header() const; const ID3Parser::ID3Header& ID3Header() const;
const FrameParser::VBRHeader& VBRInfo() const; const FrameParser::VBRHeader& VBRInfo() const;
@ -401,7 +405,12 @@ private:
// Seeks by scanning the stream up to the given time for more accurate results. // Seeks by scanning the stream up to the given time for more accurate results.
media::TimeUnit ScanUntil(const media::TimeUnit& aTime); media::TimeUnit ScanUntil(const media::TimeUnit& aTime);
// Finds the next valid frame and returns its byte range. // Finds the first valid frame and returns its byte range if found
// or a null-byte range otherwise.
MediaByteRange FindFirstFrame();
// Finds the next valid frame and returns its byte range if found
// or a null-byte range otherwise.
MediaByteRange FindNextFrame(); MediaByteRange FindNextFrame();
// Skips the next frame given the provided byte range. // Skips the next frame given the provided byte range.