Bug 1256590 - Part 1 - Try detecting the actual frame start even when parsing starts mid-stream. r=esawin a=ritu
authorJan Henning <jh+bugzilla@buttercookie.de>
Tue, 22 Mar 2016 21:39:05 +0100
changeset 323685 a25f2deb2d1c375e073886b035a4b0e2c08a514f
parent 323684 37642befed5959b63febdfaeb41e8b95c4aa3022
child 323686 b7c702322cfd0e26df173656165447eb21f4c852
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersesawin, ritu
bugs1256590
milestone47.0a2
Bug 1256590 - Part 1 - Try detecting the actual frame start even when parsing starts mid-stream. r=esawin a=ritu So far we've simply assumed that the first MPEG Layer 3 frame sync we find is automatically valid. However if the audio data has been improperly cut, parsing might start somewhere in mid-stream, so the first frame sync we hit upon might be a false positive. This naturally leads to problems if we try to check later frame syncs for consistency (same MPEG version, sample rate, etc.) with that first frame sync. Therefore, this patch changes demuxer initialisation to only accept a frame sync if it is followed by a number of further frame syncs consistent with the initial frame. MozReview-Commit-ID: jVNGodIDEY
dom/media/MP3Demuxer.cpp
dom/media/MP3Demuxer.h
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -114,17 +114,17 @@ MP3TrackDemuxer::MP3TrackDemuxer(MediaRe
   Reset();
 }
 
 bool
 MP3TrackDemuxer::Init() {
   Reset();
   FastSeek(TimeUnit());
   // Read the first frame to fetch sample rate and other meta data.
-  RefPtr<MediaRawData> frame(GetNextFrame(FindNextFrame()));
+  RefPtr<MediaRawData> frame(GetNextFrame(FindFirstFrame()));
 
   MP3LOG("Init StreamLength()=%" PRId64 " first-frame-found=%d",
          StreamLength(), !!frame);
 
   if (!frame) {
     return false;
   }
 
@@ -368,16 +368,63 @@ MP3TrackDemuxer::Duration(int64_t aNumFr
   if (!mSamplesPerSecond) {
     return TimeUnit::FromMicroseconds(-1);
   }
 
   const double usPerFrame = USECS_PER_S * mSamplesPerFrame / mSamplesPerSecond;
   return TimeUnit::FromMicroseconds(aNumFrames * usPerFrame);
 }
 
+MediaByteRange
+MP3TrackDemuxer::FindFirstFrame() {
+  static const int MIN_SUCCESSIVE_FRAMES = 4;
+
+  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
 VerifyFrameConsistency(
     const FrameParser::Frame& aFrame1, const FrameParser::Frame& aFrame2) {
   const auto& h1 = aFrame1.Header();
   const auto& h2 = aFrame2.Header();
 
   return h1.IsValid() && h2.IsValid() &&
          h1.Layer() == h2.Layer() &&
@@ -649,16 +696,23 @@ FrameParser::FrameParser()
 
 void
 FrameParser::Reset() {
   mID3Parser.Reset();
   mFrame.Reset();
 }
 
 void
+FrameParser::ResetFrameData() {
+  mFrame.Reset();
+  mFirstFrame.Reset();
+  mPrevFrame.Reset();
+}
+
+void
 FrameParser::EndFrameSession() {
   if (!mID3Parser.Header().IsValid()) {
     // Reset ID3 tags only if we have not parsed a valid ID3 header yet.
     mID3Parser.Reset();
   }
   mPrevFrame = mFrame;
   mFrame.Reset();
 }
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -304,19 +304,23 @@ public:
   const Frame& FirstFrame() const;
 
   // Returns the parsed ID3 header. Note: check for validity.
   const ID3Parser::ID3Header& ID3Header() const;
 
   // Returns the parsed VBR header info. Note: check for validity by type.
   const VBRHeader& VBRInfo() const;
 
-  // Resets the parser. Don't use between frames as first frame data is reset.
+  // Resets the parser.
   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.:
   // - sets PrevFrame to CurrentFrame
   // - resets the CurrentFrame
   // - resets ID3Header if no valid header was parsed yet
   void EndFrameSession();
 
   // Parses contents of given ByteReader for a valid frame header and returns true
   // if one was found. After returning, the variable passed to 'aBytesToSkip' holds
@@ -389,17 +393,22 @@ private:
   ~MP3TrackDemuxer() {}
 
   // Fast approximate seeking to given time.
   media::TimeUnit FastSeek(const media::TimeUnit& aTime);
 
   // Seeks by scanning the stream up to the given time for more accurate results.
   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();
 
   // Skips the next frame given the provided byte range.
   bool SkipNextFrame(const MediaByteRange& aRange);
 
   // Returns the next MPEG frame, if available.
   already_AddRefed<MediaRawData> GetNextFrame(const MediaByteRange& aRange);