Bug 1195723: [ogg/flac] P3. Add flac support in ogg. r?kamidphish draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Thu, 04 Aug 2016 17:21:53 +1000
changeset 404112 4b81c49f90233885c494df0e3c6bcf244532a1b6
parent 404111 d88bfebd53174ad8cd95ed78e9da52cc7828153e
child 404113 4bb44f35b2f6f6792464bdcfac917c920682985c
push id27118
push userbmo:jyavenard@mozilla.com
push dateMon, 22 Aug 2016 22:58:57 +0000
reviewerskamidphish
bugs1195723
milestone51.0a1
Bug 1195723: [ogg/flac] P3. Add flac support in ogg. r?kamidphish This feature is intended to debug the flac parser only and is behind a hidden pref. There's lots of redundant code in OggCodecState, there's need for a serious cleanup there. MozReview-Commit-ID: 9H4efd2cfuE
dom/media/ogg/OggCodecState.cpp
dom/media/ogg/OggCodecState.h
dom/media/ogg/OggDecoder.cpp
dom/media/ogg/OggDemuxer.cpp
dom/media/ogg/OggDemuxer.h
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -44,20 +44,22 @@ OggCodecState::Create(ogg_page* aPage)
   if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
     codecState = new TheoraState(aPage);
   } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
     codecState = new VorbisState(aPage);
   } else if (aPage->body_len > 8 && memcmp(aPage->body, "OpusHead", 8) == 0) {
     codecState = new OpusState(aPage);
   } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
     codecState = new SkeletonState(aPage);
+  } else if (aPage->body_len > 5 && memcmp(aPage->body, "\177FLAC", 5) == 0) {
+    codecState = new FlacState(aPage);
   } else {
     codecState = new OggCodecState(aPage, false);
   }
-  return codecState->OggCodecState::Init() ? codecState.forget() : nullptr;
+  return codecState->OggCodecState::InternalInit() ? codecState.forget() : nullptr;
 }
 
 OggCodecState::OggCodecState(ogg_page* aBosPage, bool aActive)
   : mPacketCount(0)
   , mSerial(ogg_page_serialno(aBosPage))
   , mActive(aActive)
   , mDoneReadingHeaders(!aActive)
 {
@@ -92,17 +94,17 @@ OggCodecState::ClearUnstamped()
 {
   for (uint32_t i = 0; i < mUnstamped.Length(); ++i) {
     OggCodecState::ReleasePacket(mUnstamped[i]);
   }
   mUnstamped.Clear();
 }
 
 bool
-OggCodecState::Init()
+OggCodecState::InternalInit()
 {
   int ret = ogg_stream_init(&mState, mSerial);
   return ret == 0;
 }
 
 bool
 OggCodecState::IsValidVorbisTagName(nsCString& aName)
 {
@@ -825,17 +827,17 @@ VorbisState::ReconstructVorbisGranulepos
   // (for example if the stream was truncated).
   //
   // We validate our prediction of the number of samples decoded when
   // VALIDATE_VORBIS_SAMPLE_CALCULATION is defined by recording the predicted
   // number of samples, and verifing we extract that many when decoding
   // each packet.
 
   NS_ASSERTION(mUnstamped.Length() > 0, "Length must be > 0");
-  ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
+  ogg_packet* last = mUnstamped.LastElement();
   NS_ASSERTION(last->e_o_s || last->granulepos >= 0,
     "Must know last granulepos!");
   if (mUnstamped.Length() == 1) {
     ogg_packet* packet = mUnstamped[0];
     long blockSize = vorbis_packet_blocksize(&mInfo, packet);
     if (blockSize < 0) {
       // On failure vorbis_packet_blocksize returns < 0. If we've got
       // a bad packet, we just assume that decode will have to skip this
@@ -1139,17 +1141,17 @@ OpusState::PacketDuration(ogg_packet* aP
   CheckedInt64 t = SaferMultDiv(GetOpusDeltaGP(aPacket), USECS_PER_S, 48000);
   return t.isValid() ? t.value() : -1;
 }
 
 bool
 OpusState::ReconstructOpusGranulepos(void)
 {
   NS_ASSERTION(mUnstamped.Length() > 0, "Must have unstamped packets");
-  ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
+  ogg_packet* last = mUnstamped.LastElement();
   NS_ASSERTION(last->e_o_s || last->granulepos > 0,
       "Must know last granulepos!");
   int64_t gp;
   // If this is the last page, and we've seen at least one previous page (or
   // this is the first page)...
   if (last->e_o_s) {
     if (mPrevPageGranulepos != -1) {
       // If this file only has one page and the final granule position is
@@ -1225,16 +1227,144 @@ OpusState::ReconstructOpusGranulepos(voi
   // We MUST reject such streams.
   if (!mDoneReadingHeaders && GetOpusDeltaGP(mUnstamped[0]) > gp) {
     return false;
   }
   mPrevPageGranulepos = last->granulepos;
   return true;
 }
 
+FlacState::FlacState(ogg_page* aBosPage)
+  : OggCodecState(aBosPage, true)
+{
+}
+
+bool
+FlacState::DecodeHeader(ogg_packet* aPacket)
+{
+  nsAutoRef<ogg_packet> autoRelease(aPacket);
+
+  if (!mParser.DecodeHeaderBlock(aPacket->packet, aPacket->bytes)) {
+    return false;
+  }
+  if (mParser.HasFullMetadata()) {
+    mDoneReadingHeaders = true;
+  }
+  return true;
+}
+
+int64_t
+FlacState::Time(int64_t granulepos)
+{
+  if (!mParser.mInfo.IsValid()) {
+    return -1;
+  }
+  CheckedInt64 t =
+      SaferMultDiv(granulepos, USECS_PER_S, mParser.mInfo.mRate);
+  if (!t.isValid()) {
+    return -1;
+  }
+  return t.value();
+}
+
+int64_t
+FlacState::PacketDuration(ogg_packet* aPacket)
+{
+  return mParser.BlockDuration(aPacket->packet, aPacket->bytes);
+}
+
+bool
+FlacState::IsHeader(ogg_packet* aPacket)
+{
+  return mParser.IsHeaderBlock(aPacket->packet, aPacket->bytes);
+}
+
+nsresult
+FlacState::PageIn(ogg_page* aPage)
+{
+  if (!mActive) {
+    return NS_OK;
+  }
+  NS_ASSERTION(static_cast<uint32_t>(ogg_page_serialno(aPage)) == mSerial,
+               "Page must be for this stream!");
+  if (ogg_stream_pagein(&mState, aPage) == -1)
+    return NS_ERROR_FAILURE;
+  bool foundGp;
+  nsresult res = PacketOutUntilGranulepos(foundGp);
+  if (NS_FAILED(res)) {
+    return res;
+  }
+  if (foundGp && mDoneReadingHeaders) {
+    // We've found a packet with a granulepos, and we've loaded our metadata
+    // and initialized our decoder. Determine granulepos of buffered packets.
+    ReconstructFlacGranulepos();
+    for (uint32_t i = 0; i < mUnstamped.Length(); ++i) {
+      ogg_packet* packet = mUnstamped[i];
+      NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
+      NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
+      mPackets.Append(packet);
+    }
+    mUnstamped.Clear();
+  }
+  return NS_OK;
+}
+
+// Return a hash table with tag metadata.
+MetadataTags*
+FlacState::GetTags()
+{
+  return mParser.GetTags();
+}
+
+const AudioInfo&
+FlacState::Info()
+{
+  return mParser.mInfo;
+}
+
+bool
+FlacState::ReconstructFlacGranulepos(void)
+{
+  NS_ASSERTION(mUnstamped.Length() > 0, "Must have unstamped packets");
+  ogg_packet* last = mUnstamped.LastElement();
+  NS_ASSERTION(last->e_o_s || last->granulepos > 0,
+      "Must know last granulepos!");
+  int64_t gp;
+
+  gp = last->granulepos;
+  // Loop through the packets backwards, subtracting the next
+  // packet's duration from its granulepos to get the value
+  // for the current packet.
+  for (uint32_t i = mUnstamped.Length() - 1; i > 0; i--) {
+    int offset =
+        mParser.BlockDuration(mUnstamped[i]->packet, mUnstamped[i]->bytes);
+    // Check for error (negative offset) and overflow.
+    if (offset >= 0) {
+      if (offset <= gp) {
+        gp -= offset;
+      } else {
+        // If the granule position of the first data page is smaller than the
+        // number of decodable audio samples on that page, then we MUST reject
+        // the stream.
+        if (!mDoneReadingHeaders) {
+          return false;
+        }
+        // It's too late to reject the stream.
+        // If we get here, this almost certainly means the file has screwed-up
+        // timestamps somewhere after the first page.
+        NS_WARNING("Clamping negative granulepos to zero.");
+        gp = 0;
+      }
+    }
+    mUnstamped[i - 1]->granulepos = gp;
+  }
+
+  return true;
+}
+
 SkeletonState::SkeletonState(ogg_page* aBosPage)
   : OggCodecState(aBosPage, true)
   , mVersion(0)
   , mPresentationTime(0)
   , mLength(0)
 {
   MOZ_COUNT_CTOR(SkeletonState);
 }
@@ -1661,11 +1791,10 @@ SkeletonState::DecodeHeader(ogg_packet* 
     return DecodeFisbone(aPacket);
   } else if (aPacket->e_o_s) {
     mDoneReadingHeaders = true;
     return true;
   }
   return true;
 }
 
-
 } // namespace mozilla
 
--- a/dom/media/ogg/OggCodecState.h
+++ b/dom/media/ogg/OggCodecState.h
@@ -20,16 +20,17 @@
 #include "MediaDecoderStateMachine.h"
 #include "MediaDecoderReader.h"
 #include <nsAutoPtr.h>
 #include <nsAutoRef.h>
 #include <nsDeque.h>
 #include <nsTArray.h>
 #include <nsClassHashtable.h>
 #include "VideoUtils.h"
+#include "FlacFrameParser.h"
 
 #include <stdint.h>
 
 // Uncomment the following to validate that we're predicting the number
 // of Vorbis samples in each packet correctly.
 #define VALIDATE_VORBIS_SAMPLE_CALCULATION
 #ifdef  VALIDATE_VORBIS_SAMPLE_CALCULATION
 #include <map>
@@ -80,20 +81,21 @@ public:
 class OggCodecState
 {
 public:
   typedef mozilla::MetadataTags MetadataTags;
   // Ogg types we know about
   enum CodecType
   {
     TYPE_VORBIS=0,
-    TYPE_THEORA=1,
-    TYPE_OPUS=2,
-    TYPE_SKELETON=3,
-    TYPE_UNKNOWN=4
+    TYPE_THEORA,
+    TYPE_OPUS,
+    TYPE_SKELETON,
+    TYPE_FLAC,
+    TYPE_UNKNOWN
   };
 
   virtual ~OggCodecState();
   
   // Factory for creating nsCodecStates. Use instead of constructor.
   // aPage should be a beginning-of-stream page.
   static OggCodecState* Create(ogg_page* aPage);
   
@@ -136,17 +138,17 @@ public:
       // Audio preskip may eat a whole packet or more.
       return 0;
     } else {
       return endTime - duration;
     }
   }
 
   // Initializes the codec state.
-  virtual bool Init();
+  virtual bool Init() { return true; }
 
   // Returns true when this bitstream has finished reading all its
   // header packets.
   bool DoneReadingHeaders() { return mDoneReadingHeaders; }
 
   // Deactivates the bitstream. Only the primary video and audio bitstreams
   // should be active.
   void Deactivate()
@@ -221,16 +223,26 @@ public:
   OggPacketQueue mPackets;
 
   // Is the bitstream active; whether we're decoding and playing this bitstream.
   bool mActive;
   
   // True when all headers packets have been read.
   bool mDoneReadingHeaders;
 
+  // Validation utility for vorbis-style tag names.
+  static bool IsValidVorbisTagName(nsCString& aName);
+
+  // Utility method to parse and add a vorbis-style comment
+  // to a metadata hash table. Most Ogg-encapsulated codecs
+  // use the vorbis comment format for metadata.
+  static bool AddVorbisComment(MetadataTags* aTags,
+                        const char* aComment,
+                        uint32_t aLength);
+
 protected:
   // Constructs a new OggCodecState. aActive denotes whether the stream is
   // active. For streams of unsupported or unknown types, aActive should be
   // false.
   OggCodecState(ogg_page* aBosPage, bool aActive);
 
   // Deallocates all packets stored in mUnstamped, and clears the array.
   void ClearUnstamped();
@@ -243,25 +255,18 @@ protected:
   // can be pushed over to mPackets. Used by PageIn() implementations in
   // subclasses.
   nsresult PacketOutUntilGranulepos(bool& aFoundGranulepos);
 
   // Temporary buffer in which to store packets while we're reading packets
   // in order to capture granulepos.
   nsTArray<ogg_packet*> mUnstamped;
 
-  // Validation utility for vorbis-style tag names.
-  static bool IsValidVorbisTagName(nsCString& aName);
-
-  // Utility method to parse and add a vorbis-style comment
-  // to a metadata hash table. Most Ogg-encapsulated codecs
-  // use the vorbis comment format for metadata.
-  static bool AddVorbisComment(MetadataTags* aTags,
-                        const char* aComment,
-                        uint32_t aLength);
+private:
+  bool InternalInit();
 };
 
 class VorbisState : public OggCodecState
 {
 public:
   explicit VorbisState(ogg_page* aBosPage);
   virtual ~VorbisState();
 
@@ -465,17 +470,16 @@ public:
   explicit SkeletonState(ogg_page* aBosPage);
   ~SkeletonState();
 
   nsClassHashtable<nsUint32HashKey, MessageField> mMsgFieldStore;
 
   CodecType GetType() override { return TYPE_SKELETON; }
   bool DecodeHeader(ogg_packet* aPacket) override;
   int64_t Time(int64_t granulepos) override { return -1; }
-  bool Init() override { return true; }
   bool IsHeader(ogg_packet* aPacket) override { return true; }
 
   // Return true if the given time (in milliseconds) is within
   // the presentation time defined in the skeleton track.
   bool IsPresentable(int64_t aTime) { return aTime >= mPresentationTime; }
 
   // Stores the offset of the page on which a keyframe starts,
   // and its presentation time.
@@ -598,16 +602,39 @@ private:
   private:
     nsTArray<nsKeyPoint> mKeyPoints;
   };
 
   // Maps Ogg serialnos to the index-keypoint list.
   nsClassHashtable<nsUint32HashKey, nsKeyFrameIndex> mIndex;
 };
 
+class FlacState : public OggCodecState
+{
+public:
+  explicit FlacState(ogg_page* aBosPage);
+
+  CodecType GetType() override { return TYPE_FLAC; }
+  bool DecodeHeader(ogg_packet* aPacket) override;
+  int64_t Time(int64_t granulepos) override;
+  int64_t PacketDuration(ogg_packet* aPacket) override;
+  bool IsHeader(ogg_packet* aPacket) override;
+  nsresult PageIn(ogg_page* aPage) override;
+
+  // Return a hash table with tag metadata.
+  MetadataTags* GetTags() override;
+
+  const AudioInfo& Info();
+
+private:
+  bool ReconstructFlacGranulepos(void);
+
+  FlacFrameParser mParser;
+};
+
 } // namespace mozilla
 
 // This allows the use of nsAutoRefs for an ogg_packet that properly free the
 // contents of the packet.
 template <>
 class nsAutoRefTraits<ogg_packet> : public nsPointerRefTraits<ogg_packet>
 {
 public:
--- a/dom/media/ogg/OggDecoder.cpp
+++ b/dom/media/ogg/OggDecoder.cpp
@@ -62,25 +62,24 @@ OggDecoder::CanHandleMediaType(const nsA
   // Verify that all the codecs specified are ones that we expect that
   // we can play.
   nsTArray<nsString> codecs;
   if (!ParseCodecsString(aCodecs, codecs)) {
     return false;
   }
   for (const nsString& codec : codecs) {
     if ((IsOpusEnabled() && codec.EqualsLiteral("opus")) ||
-        codec.EqualsLiteral("vorbis")) {
+        codec.EqualsLiteral("vorbis") ||
+        (MediaPrefs::FlacInOgg() && codec.EqualsLiteral("flac"))) {
       continue;
     }
     // Note: Only accept Theora in a video content type, not in an audio
     // content type.
     if (isOggVideo && codec.EqualsLiteral("theora")) {
       continue;
     }
     // Some unsupported codec.
     return false;
   }
   return true;
 }
 
-
-
 } // namespace mozilla
--- a/dom/media/ogg/OggDemuxer.cpp
+++ b/dom/media/ogg/OggDemuxer.cpp
@@ -5,23 +5,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "OggDemuxer.h"
 #include "OggCodecState.h"
 #include "mozilla/PodOperations.h"
-#include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "MediaDataDemuxer.h"
 #include "nsAutoRef.h"
 #include "XiphExtradata.h"
+#include "MediaPrefs.h"
 
 #include <algorithm>
 
 extern mozilla::LazyLogModule gMediaDemuxerLog;
 #define OGG_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("OggDemuxer(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
@@ -122,23 +122,25 @@ OggDemuxer::InitTrack(MessageField* aMsg
               sLanguage? NS_ConvertUTF8toUTF16(*sLanguage):EmptyString(),
               aEnable);
 }
 
 OggDemuxer::OggDemuxer(MediaResource* aResource)
   : mTheoraState(nullptr)
   , mVorbisState(nullptr)
   , mOpusState(nullptr)
+  , mFlacState(nullptr)
   , mOpusEnabled(MediaDecoder::IsOpusEnabled())
   , mSkeletonState(nullptr)
   , mAudioOggState(aResource)
   , mVideoOggState(aResource)
   , mVorbisSerial(0)
   , mOpusSerial(0)
   , mTheoraSerial(0)
+  , mFlacSerial(0)
   , mOpusPreSkip(0)
   , mIsChained(false)
   , mTimedMetadataEvent(nullptr)
   , mOnSeekableEvent(nullptr)
 {
   MOZ_COUNT_CTOR(OggDemuxer);
   PodZero(&mTheoraInfo);
 }
@@ -168,17 +170,17 @@ OggDemuxer::SetChainingEvents(TimedMetad
   mOnSeekableEvent = aOnSeekableEvent;
 }
 
 
 bool
 OggDemuxer::HasAudio()
 const
 {
-  return mVorbisState || mOpusState;
+  return mVorbisState || mOpusState || mFlacState;
 }
 
 bool
 OggDemuxer::HasVideo()
 const
 {
   return mTheoraState;
 }
@@ -244,34 +246,37 @@ OggDemuxer::HasTrackType(TrackInfo::Trac
 
 OggCodecState*
 OggDemuxer::GetTrackCodecState(TrackInfo::TrackType aType) const
 {
   switch(aType) {
     case TrackInfo::kAudioTrack:
       if (mVorbisState) {
         return mVorbisState;
+      } else if (mOpusState) {
+        return mOpusState;
       } else {
-        return mOpusState;
+        return mFlacState;
       }
     case TrackInfo::kVideoTrack:
       return mTheoraState;
     default:
       return 0;
   }
 }
 
 TrackInfo::TrackType
 OggDemuxer::GetCodecStateType(OggCodecState* aState) const
 {
   switch (aState->GetType()) {
     case OggCodecState::TYPE_THEORA:
       return TrackInfo::kVideoTrack;
     case OggCodecState::TYPE_OPUS:
     case OggCodecState::TYPE_VORBIS:
+    case OggCodecState::TYPE_FLAC:
       return TrackInfo::kAudioTrack;
     default:
       return TrackInfo::kUndefinedTrack;
   }
 }
 
 uint32_t
 OggDemuxer::GetNumberTracks(TrackInfo::TrackType aType) const
@@ -457,16 +462,28 @@ OggDemuxer::SetupTargetOpus(OpusState* a
                                                     aHeaders.mHeaderLens[0]);
 
   mOpusState = aOpusState;
   mOpusSerial = aOpusState->mSerial;
   mOpusPreSkip = aOpusState->mPreSkip;
 }
 
 void
+OggDemuxer::SetupTargetFlac(FlacState* aFlacState, OggHeaders& aHeaders)
+{
+  if (mFlacState) {
+    mFlacState->Reset();
+  }
+
+  mInfo.mAudio = aFlacState->Info();
+  mFlacState = aFlacState;
+  mFlacSerial = aFlacState->mSerial;
+}
+
+void
 OggDemuxer::SetupTargetSkeleton()
 {
   // Setup skeleton related information after mVorbisState & mTheroState
   // being set (if they exist).
   if (mSkeletonState) {
     OggHeaders headers;
     if (!HasAudio() && !HasVideo()) {
       // We have a skeleton track, but no audio or video, may as well disable
@@ -552,16 +569,28 @@ OggDemuxer::SetupMediaTracksInfo(const n
 
       if (msgInfo) {
         InitTrack(msgInfo, &mInfo.mAudio, mOpusState == opusState);
       }
 
       mInfo.mAudio.mRate = opusState->mRate;
       mInfo.mAudio.mChannels = opusState->mChannels;
       FillTags(&mInfo.mAudio, opusState->GetTags());
+    } else if (codecState->GetType() == OggCodecState::TYPE_FLAC) {
+      FlacState* flacState = static_cast<FlacState*>(codecState);
+      if (!(mFlacState && mFlacState->mSerial == flacState->mSerial)) {
+        continue;
+      }
+
+      if (msgInfo) {
+        InitTrack(msgInfo, &mInfo.mAudio, mFlacState == flacState);
+      }
+
+      mInfo.mAudio = flacState->Info();
+      FillTags(&mInfo.mAudio, flacState->GetTags());
     }
   }
 }
 
 void
 OggDemuxer::FillTags(TrackInfo* aInfo, MetadataTags* aTags)
 {
   if (!aTags) {
@@ -657,16 +686,25 @@ OggDemuxer::ReadMetadata()
             SetupTargetOpus(opusState, headers);
           } else {
             s->Deactivate();
           }
         } else {
           NS_WARNING("Opus decoding disabled."
                      " See media.opus.enabled in about:config");
         }
+      } else if (MediaPrefs::FlacInOgg() &&
+                 s->GetType() == OggCodecState::TYPE_FLAC &&
+                 ReadHeaders(TrackInfo::kAudioTrack, s, headers)) {
+        if (!mFlacState) {
+          FlacState* flacState = static_cast<FlacState*>(s);
+          SetupTargetFlac(flacState, headers);
+        } else {
+          s->Deactivate();
+        }
       } else if (s->GetType() == OggCodecState::TYPE_SKELETON && !mSkeletonState) {
         mSkeletonState = static_cast<SkeletonState*>(s);
       } else {
         // Deactivate any non-primary bitstreams.
         s->Deactivate();
       }
     }
   }
@@ -730,16 +768,17 @@ OggDemuxer::SetChained() {
 }
 
 bool
 OggDemuxer::ReadOggChain(const media::TimeUnit& aLastEndTime)
 {
   bool chained = false;
   OpusState* newOpusState = nullptr;
   VorbisState* newVorbisState = nullptr;
+  FlacState* newFlacState = nullptr;
   nsAutoPtr<MetadataTags> tags;
 
   if (HasVideo() || HasSkeleton() || !HasAudio()) {
     return false;
   }
 
   ogg_page page;
   if (!ReadOggPage(TrackInfo::kAudioTrack, &page) || !ogg_page_bos(&page)) {
@@ -757,16 +796,18 @@ OggDemuxer::ReadOggChain(const media::Ti
   if (!codecState) {
     return false;
   }
 
   if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
     newVorbisState = static_cast<VorbisState*>(codecState.get());
   } else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
     newOpusState = static_cast<OpusState*>(codecState.get());
+  } else if (mFlacState && (codecState->GetType() == OggCodecState::TYPE_FLAC)) {
+    newFlacState = static_cast<FlacState*>(codecState.get());
   } else {
     return false;
   }
 
   OggCodecState* state;
 
   mCodecStore.Add(serial, codecState.forget());
   state = mCodecStore.Get(serial);
@@ -816,16 +857,34 @@ OggDemuxer::ReadOggChain(const media::Ti
     mInfo.mAudio.mMimeType = NS_LITERAL_CSTRING("audio/ogg; codec=opus");
     mInfo.mAudio.mRate = newOpusState->mRate;
     mInfo.mAudio.mChannels = newOpusState->mChannels;
 
     chained = true;
     tags = newOpusState->GetTags();
   }
 
+  OggHeaders flacHeaders;
+  if ((newFlacState &&
+       ReadHeaders(TrackInfo::kAudioTrack, newFlacState, flacHeaders)) &&
+      (mFlacState->Info().mRate == newFlacState->Info().mRate) &&
+      (mFlacState->Info().mChannels == newFlacState->Info().mChannels)) {
+
+    SetupTargetFlac(newFlacState, flacHeaders);
+    LOG(LogLevel::Debug, ("New flac ogg link, serial=%d\n", mFlacSerial));
+
+    if (msgInfo) {
+      InitTrack(msgInfo, &mInfo.mAudio, true);
+    }
+
+    mInfo.mAudio = newFlacState->Info();
+    chained = true;
+    tags = newFlacState->GetTags();
+  }
+
   if (chained) {
     SetChained();
     mInfo.mMediaSeekable = false;
     mDecodedAudioDuration += aLastEndTime;
     if (mTimedMetadataEvent) {
       mTimedMetadataEvent->Notify(
         TimedMetadata(mDecodedAudioDuration,
                       Move(tags),
@@ -1052,16 +1111,20 @@ OggDemuxer::GetBuffered(TrackInfo::Track
       if (aType == TrackInfo::kAudioTrack && mVorbisState &&
           serial == mVorbisSerial) {
         startTime = VorbisState::Time(&mVorbisInfo, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       } else if (aType == TrackInfo::kAudioTrack && mOpusState &&
                  serial == mOpusSerial) {
         startTime = OpusState::Time(mOpusPreSkip, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
+      } else if (aType == TrackInfo::kAudioTrack && mFlacState &&
+                 serial == mFlacSerial) {
+        startTime = mFlacState->Time(granulepos);
+        NS_ASSERTION(startTime > 0, "Must have positive start time");
       } else if (aType == TrackInfo::kVideoTrack && mTheoraState &&
                  serial == mTheoraSerial) {
         startTime = TheoraState::Time(&mTheoraInfo, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       } else if (mCodecStore.Contains(serial)) {
         // Stream is not the theora or vorbis stream we're playing,
         // but is one that we have header data for.
         startOffset += page.header_len + page.body_len;
@@ -2007,16 +2070,18 @@ OggDemuxer::SeekBisection(TrackInfo::Tra
           ogg_int64_t granulepos = ogg_page_granulepos(&page);
 
           if (aType == TrackInfo::kAudioTrack &&
               granulepos > 0 && audioTime == -1) {
             if (mVorbisState && serial == mVorbisState->mSerial) {
               audioTime = mVorbisState->Time(granulepos);
             } else if (mOpusState && serial == mOpusState->mSerial) {
               audioTime = mOpusState->Time(granulepos);
+            } else if (mFlacState && serial == mFlacState->mSerial) {
+              audioTime = mFlacState->Time(granulepos);
             }
           }
 
           if (aType == TrackInfo::kVideoTrack &&
               granulepos > 0 && serial == mTheoraState->mSerial &&
               videoTime == -1) {
             videoTime = mTheoraState->Time(granulepos);
           }
--- a/dom/media/ogg/OggDemuxer.h
+++ b/dom/media/ogg/OggDemuxer.h
@@ -215,16 +215,17 @@ private:
   // Fills aTracks with the serial numbers of each active stream, for use by
   // various SkeletonState functions.
   void BuildSerialList(nsTArray<uint32_t>& aTracks);
 
   // Setup target bitstreams for decoding.
   void SetupTargetTheora(TheoraState* aTheoraState, OggHeaders& aHeaders);
   void SetupTargetVorbis(VorbisState* aVorbisState, OggHeaders& aHeaders);
   void SetupTargetOpus(OpusState* aOpusState, OggHeaders& aHeaders);
+  void SetupTargetFlac(FlacState* aFlacState, OggHeaders& aHeaders);
   void SetupTargetSkeleton();
   void SetupMediaTracksInfo(const nsTArray<uint32_t>& aSerials);
   void FillTags(TrackInfo* aInfo, MetadataTags* aTags);
 
   // Compute an ogg page's checksum
   ogg_uint32_t GetPageChecksum(ogg_page* aPage);
 
   // Get the end time of aEndOffset. This is the playback position we'd reach
@@ -259,16 +260,19 @@ private:
 
   // Decode state of the Vorbis bitstream we're decoding, if we have audio.
   VorbisState* mVorbisState;
 
   // Decode state of the Opus bitstream we're decoding, if we have one.
   OpusState* mOpusState;
 
   // Get the bitstream decode state for the given track type
+  // Decode state of the Flac bitstream we're decoding, if we have one.
+  FlacState* mFlacState;
+
   OggCodecState* GetTrackCodecState(TrackInfo::TrackType aType) const;
   TrackInfo::TrackType GetCodecStateType(OggCodecState* aState) const;
 
   // Represents the user pref media.opus.enabled at the time our
   // contructor was called. We can't check it dynamically because
   // we're not on the main thread;
   bool mOpusEnabled;
 
@@ -297,16 +301,18 @@ private:
   // decoder thread and read on the main thread. All reading on the main
   // thread must be done after metadataloaded. We can't use the existing
   // data in the codec states due to threading issues. You must check the
   // associated mTheoraState or mVorbisState pointer is non-null before
   // using this codec data.
   uint32_t mVorbisSerial;
   uint32_t mOpusSerial;
   uint32_t mTheoraSerial;
+  uint32_t mFlacSerial;
+
   vorbis_info mVorbisInfo;
   int mOpusPreSkip;
   th_info mTheoraInfo;
 
   Maybe<int64_t> mStartTime;
 
   // Booleans to indicate if we have audio and/or video data
   bool HasVideo() const;