Bug 1188150 - ADTSContainerParser. r=jya
authorRalph Giles <giles@mozilla.com>
Tue, 21 Jul 2015 14:38:09 -0700
changeset 287197 6b8bc93d561b574770dfc6b1ce6ae5836ffc441d
parent 287196 d7ee5d8841d0f0e039a8cc87e0a203cd80f46189
child 287198 032bfd7a5e7508876be2b7441fba291af99a1653
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya
bugs1188150
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1188150 - ADTSContainerParser. r=jya Parse an ADTS header and use it to recognize MSE 'initialization segments' i.e. those with a header. For 'media segments' we look for a header followed by the declared amount of data. The implementation treats 'media headers' the same as 'media segments'. ADTS has no embedded timestamps, so we don't need to do anything there; the demuxer will generate them based on packet lengths.
dom/media/mediasource/ContainerParser.cpp
dom/media/mediasource/ContainerParser.h
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -419,17 +419,163 @@ public:
     return 35000;
   }
 
 private:
   nsRefPtr<MP4Stream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
   Monitor mMonitor;
 };
-#endif
+#endif // MOZ_FMP4
+
+#ifdef MOZ_FMP4
+class ADTSContainerParser : public ContainerParser {
+public:
+  explicit ADTSContainerParser(const nsACString& aType)
+    : ContainerParser(aType)
+  {}
+
+  typedef struct {
+    size_t header_length; // Length of just the initialization data.
+    size_t frame_length;  // Includes header_length;
+    uint8_t aac_frames;   // Number of AAC frames in the ADTS frame.
+    bool have_crc;
+  } Header;
+
+  /// Helper to parse the ADTS header, returning data we care about.
+  /// Returns true if the header is parsed successfully.
+  /// Returns false if the header is invalid or incomplete,
+  /// without modifying the passed-in Header object.
+  bool Parse(MediaByteBuffer* aData, Header& header)
+  {
+    MOZ_ASSERT(aData);
+
+    // ADTS initialization segments are just the packet header.
+    if (aData->Length() < 7) {
+      MSE_DEBUG(ADTSContainerParser, "buffer too short for header.");
+      return false;
+    }
+    // Check 0xfffx sync word plus layer 0.
+    if (((*aData)[0] != 0xff) || (((*aData)[1] & 0xf6) != 0xf0)) {
+      MSE_DEBUG(ADTSContainerParser, "no syncword.");
+      return false;
+    }
+    bool have_crc = !((*aData)[1] & 0x01);
+    if (have_crc && aData->Length() < 9) {
+      MSE_DEBUG(ADTSContainerParser, "buffer too short for header with crc.");
+      return false;
+    }
+    uint8_t frequency_index = ((*aData)[2] & 0x3c) >> 2;
+    MOZ_ASSERT(frequency_index < 16);
+    if (frequency_index == 15) {
+      MSE_DEBUG(ADTSContainerParser, "explicit frequency disallowed.");
+      return false;
+    }
+    size_t header_length = have_crc ? 9 : 7;
+    size_t data_length = (((*aData)[3] & 0x03) << 11) ||
+                         (((*aData)[4] & 0xff) << 3) ||
+                         (((*aData)[5] & 0xe0) >> 5);
+    uint8_t frames = ((*aData)[6] & 0x03) + 1;
+    MOZ_ASSERT(frames > 0);
+    MOZ_ASSERT(frames < 4);
+
+    // Return successfully parsed data.
+    header.header_length = header_length;
+    header.frame_length = header_length + data_length;
+    header.aac_frames = frames;
+    header.have_crc = have_crc;
+    return true;
+  }
+
+  bool IsInitSegmentPresent(MediaByteBuffer* aData) override
+  {
+    // Call superclass for logging.
+    ContainerParser::IsInitSegmentPresent(aData);
+
+    Header header;
+    if (!Parse(aData, header)) {
+      return false;
+    }
+
+    MSE_DEBUGV(ADTSContainerParser, "%llu byte frame %d aac frames%s",
+        (unsigned long long)header.frame_length, (int)header.aac_frames,
+        header.have_crc ? " crc" : "");
+
+    return true;
+  }
+
+  bool IsMediaSegmentPresent(MediaByteBuffer* aData) override
+  {
+    // Call superclass for logging.
+    ContainerParser::IsMediaSegmentPresent(aData);
+
+    // Make sure we have a header so we know how long the frame is.
+    // NB this assumes the media segment buffer starts with an
+    // initialization segment. Since every frame has an ADTS header
+    // this is a normal place to divide packets, but we can re-parse
+    // mInitData if we need to handle separate media segments.
+    Header header;
+    if (!Parse(aData, header)) {
+      return false;
+    }
+    // We're supposed to return true as long as aData contains the
+    // start of a media segment, whether or not it's complete. So
+    // return true if we have any data beyond the header.
+    if (aData->Length() <= header.header_length) {
+      return false;
+    }
+
+    // We should have at least a partial frame.
+    return true;
+  }
+
+  bool ParseStartAndEndTimestamps(MediaByteBuffer* aData,
+                                  int64_t& aStart, int64_t& aEnd) override
+  {
+    // ADTS header.
+    Header header;
+    if (!Parse(aData, header)) {
+      return false;
+    }
+    mHasInitData = true;
+    mCompleteInitSegmentRange = MediaByteRange(0, header.header_length);
+
+    // Cache raw header in case the caller wants a copy.
+    mInitData = new MediaByteBuffer(header.header_length);
+    mInitData->AppendElements(aData->Elements(), header.header_length);
+
+    // Check that we have enough data for the frame body.
+    if (aData->Length() < header.frame_length) {
+      MSE_DEBUGV(ADTSContainerParser, "Not enough data for %llu byte frame"
+          " in %llu byte buffer.",
+          (unsigned long long)header.frame_length,
+          (unsigned long long)(aData->Length()));
+      return false;
+    }
+    mCompleteMediaSegmentRange = MediaByteRange(header.header_length,
+                                                header.frame_length);
+    // The ADTS MediaSource Byte Stream Format document doesn't
+    // define media header. Just treat it the same as the whole
+    // media segment.
+    mCompleteMediaHeaderRange = mCompleteMediaSegmentRange;
+
+    MSE_DEBUG(ADTSContainerParser, "[%lld, %lld]",
+              aStart, aEnd);
+    // We don't update timestamps, regardless.
+    return false;
+  }
+
+  // Audio shouldn't have gaps.
+  // Especially when we generate the timestamps ourselves.
+  int64_t GetRoundingError() override
+  {
+    return 0;
+  }
+};
+#endif // MOZ_FMP4
 
 /*static*/ ContainerParser*
 ContainerParser::CreateForMIMEType(const nsACString& aType)
 {
   if (aType.LowerCaseEqualsLiteral("video/webm") || aType.LowerCaseEqualsLiteral("audio/webm")) {
     return new WebMContainerParser(aType);
   }
 
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -46,16 +46,19 @@ public:
 
   MediaByteBuffer* InitData();
 
   bool HasInitData()
   {
     return mHasInitData;
   }
 
+  // Return true if a complete initialization segment has been passed
+  // to ParseStartAndEndTimestamps(). The calls below to retrieve
+  // MediaByteRanges will be valid from when this call first succeeds.
   bool HasCompleteInitData();
   // Returns the byte range of the first complete init segment, or an empty
   // range if not complete.
   MediaByteRange InitSegmentRange();
   // Returns the byte range of the first complete media segment header,
   // or an empty range if not complete.
   MediaByteRange MediaHeaderRange();
   // Returns the byte range of the first complete media segment or an empty