Bug 1195723: [flac] P12. Add sniffer for streaming flac. r?kamidphish draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 18 Aug 2016 13:07:11 +1000
changeset 404121 ff6ff170d337b1480afac235fb05a947d73711e4
parent 404120 b0925b772086520ab9fbb5dbea75b35ac98f101d
child 404122 4d082163dcf6b96dd12ea70a250a88d612726fac
push id27118
push userbmo:jyavenard@mozilla.com
push dateMon, 22 Aug 2016 22:58:57 +0000
reviewerskamidphish
bugs1195723
milestone51.0a1
Bug 1195723: [flac] P12. Add sniffer for streaming flac. r?kamidphish MozReview-Commit-ID: P62v6vsXzs
dom/media/flac/FlacDemuxer.cpp
dom/media/flac/FlacDemuxer.h
toolkit/components/mediasniffer/nsMediaSniffer.cpp
--- a/dom/media/flac/FlacDemuxer.cpp
+++ b/dom/media/flac/FlacDemuxer.cpp
@@ -231,22 +231,52 @@ const uint8_t FrameHeader::CRC8Table[256
   0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
 };
 
 // flac::Frame - Frame meta container used to parse and hold a frame
 // header and side info.
 class Frame {
 public:
 
-  // Find the next frame start in the current resource.
-  // On exit return true, offset is set and resource points to the frame found.
   // The FLAC signature is made of 14 bits set to 1; however the 15th bit is
   // mandatorily set to 0, so we need to find either of 0xfffc or 0xfffd 2-bytes
   // signature. We first use a bitmask to see if 0xfc or 0xfd is present. And if
   // so we check for the whole signature.
+  // aData must be pointing to a buffer at least
+  // aLength + FLAC_MAX_FRAME_HEADER_SIZE bytes.
+  int64_t FindNext(const uint8_t* aData, const uint32_t aLength)
+  {
+    uint32_t modOffset = aLength % 4;
+    uint32_t i, j;
+
+    for (i = 0; i < modOffset; i++) {
+      if ((BigEndian::readUint16(aData + i) & 0xfffe) == 0xfff8) {
+        if (mHeader.Parse(aData + i)) {
+          return i;
+        }
+      }
+    }
+
+    for (; i < aLength; i += 4) {
+      uint32_t x = BigEndian::readUint32(aData + i);
+      if (((x & ~(x + 0x01010101)) & 0x80808080)) {
+        for (j = 0; j < 4; j++) {
+          if ((BigEndian::readUint16(aData + i + j) & 0xfffe) == 0xfff8) {
+            if (mHeader.Parse(aData + i + j)) {
+              return i + j;
+            }
+          }
+        }
+      }
+    }
+    return -1;
+  }
+
+  // Find the next frame start in the current resource.
+  // On exit return true, offset is set and resource points to the frame found.
   bool FindNext(MediaResourceIndex& aResource)
   {
     static const int BUFFER_SIZE = 4096;
 
     Reset();
 
     nsTArray<char> buffer;
     int64_t originalOffset = aResource.Tell();
@@ -256,51 +286,32 @@ public:
     do {
       uint32_t read = 0;
       buffer.SetLength(BUFFER_SIZE + innerOffset);
       nsresult rv =
         aResource.Read(buffer.Elements() + innerOffset, BUFFER_SIZE, &read);
       if (NS_FAILED(rv)) {
         return false;
       }
+
       if (read < FLAC_MAX_FRAME_HEADER_SIZE) {
         // Assume that we can't have a valid frame in such small content, we
         // must have reached EOS.
         // So we're done.
         mEOS = true;
         return false;
       }
 
       const size_t bufSize = read + innerOffset - FLAC_MAX_FRAME_HEADER_SIZE;
-
-      const uint8_t* buf = reinterpret_cast<uint8_t*>(buffer.Elements());
-      uint32_t modOffset = bufSize % 4;
-      uint32_t i, j;
+      int64_t foundOffset =
+        FindNext(reinterpret_cast<uint8_t*>(buffer.Elements()), bufSize);
 
-      for (i = 0; i < modOffset; i++) {
-        if ((BigEndian::readUint16(buf + i) & 0xfffe) == 0xfff8) {
-          if (mHeader.Parse(buf + i)) {
-            SetOffset(aResource, offset + i);
-            return true;
-          }
-        }
-      }
-
-      for (; i < bufSize; i += 4) {
-        uint32_t x = BigEndian::readUint32(buf + i);
-        if (((x & ~(x + 0x01010101)) & 0x80808080)) {
-          for (j = 0; j < 4; j++) {
-            if ((BigEndian::readUint16(buf + i + j) & 0xfffe) == 0xfff8) {
-              if (mHeader.Parse(buf + i + j)) {
-                SetOffset(aResource, offset + i + j);
-                return true;
-              }
-            }
-          }
-        }
+      if (foundOffset >= 0) {
+        SetOffset(aResource, foundOffset + offset);
+        return true;
       }
 
       // Scan the next block;
       offset += bufSize;
       buffer.RemoveElementsAt(0, bufSize);
       innerOffset = buffer.Length();
     } while (offset - originalOffset < FLAC_MAX_FRAME_SIZE);
 
@@ -1034,9 +1045,20 @@ FlacTrackDemuxer::TimeAtEnd()
   // Update our current progress stats.
   mParsedFramesDuration =
     previousTime + previousDuration - mParser->FirstFrame().Time();
   mTotalFrameLen = streamLen - mParser->FirstFrame().Offset();
 
   return mParsedFramesDuration;
 }
 
+/* static */ bool
+FlacDemuxer::FlacSniffer(const uint8_t* aData, const uint32_t aLength)
+{
+  if (aLength < FLAC_MAX_FRAME_HEADER_SIZE) {
+    return false;
+  }
+
+  flac::Frame frame;
+  return frame.FindNext(aData, aLength - FLAC_MAX_FRAME_HEADER_SIZE) >= 0;
+}
+
 } // namespace mozilla
--- a/dom/media/flac/FlacDemuxer.h
+++ b/dom/media/flac/FlacDemuxer.h
@@ -25,16 +25,19 @@ public:
   explicit FlacDemuxer(MediaResource* aSource);
   RefPtr<InitPromise> Init() override;
   bool HasTrackType(TrackInfo::TrackType aType) const override;
   uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
   already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
     TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
   bool IsSeekable() const override;
 
+  // Return true if a valid flac frame header could be found.
+  static bool FlacSniffer(const uint8_t* aData, const uint32_t aLength);
+
 private:
   bool InitInternal();
 
   RefPtr<MediaResource> mSource;
   RefPtr<FlacTrackDemuxer> mTrackDemuxer;
 };
 
 class FlacTrackDemuxer : public MediaTrackDemuxer {
--- a/toolkit/components/mediasniffer/nsMediaSniffer.cpp
+++ b/toolkit/components/mediasniffer/nsMediaSniffer.cpp
@@ -7,16 +7,17 @@
 #include "nsMediaSniffer.h"
 #include "nsIHttpChannel.h"
 #include "nsString.h"
 #include "nsMimeTypes.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ModuleUtils.h"
 #include "mp3sniff.h"
 #include "nestegg/nestegg.h"
+#include "FlacDemuxer.h"
 
 #include "nsIClassInfoImpl.h"
 #include <algorithm>
 
 // The minimum number of bytes that are needed to attempt to sniff an mp4 file.
 static const unsigned MP4_MIN_BYTES_COUNT = 12;
 // The maximum number of bytes to consider when attempting to sniff a file.
 static const uint32_t MAX_BYTES_SNIFFED = 512;
@@ -115,16 +116,21 @@ static bool MatchesWebM(const uint8_t* a
 
 // This function implements mp3 sniffing based on parsing
 // packet headers and looking for expected boundaries.
 static bool MatchesMP3(const uint8_t* aData, const uint32_t aLength)
 {
   return mp3_sniff(aData, (long)aLength);
 }
 
+static bool MatchesFLAC(const uint8_t* aData, const uint32_t aLength)
+{
+  return mozilla::FlacDemuxer::FlacSniffer(aData, aLength);
+}
+
 NS_IMETHODIMP
 nsMediaSniffer::GetMIMETypeFromContent(nsIRequest* aRequest,
                                        const uint8_t* aData,
                                        const uint32_t aLength,
                                        nsACString& aSniffedType)
 {
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   if (channel) {
@@ -174,13 +180,21 @@ nsMediaSniffer::GetMIMETypeFromContent(n
   }
 
   // Bug 950023: 512 bytes are often not enough to sniff for mp3.
   if (MatchesMP3(aData, std::min(aLength, MAX_BYTES_SNIFFED_MP3))) {
     aSniffedType.AssignLiteral(AUDIO_MP3);
     return NS_OK;
   }
 
+  // Flac frames are generally big, often in excess of 24kB.
+  // Using a size of MAX_BYTES_SNIFFED effectively means that we will only
+  // recognize flac content if it starts with a frame.
+  if (MatchesFLAC(aData, clampedLength)) {
+    aSniffedType.AssignLiteral(AUDIO_FLAC);
+    return NS_OK;
+  }
+
   // Could not sniff the media type, we are required to set it to
   // application/octet-stream.
   aSniffedType.AssignLiteral(APPLICATION_OCTET_STREAM);
   return NS_ERROR_NOT_AVAILABLE;
 }