Bug 938686 - Refactor Opus header parsing so it can be reused. r=kinetik
authorJan Gerber <j@mailb.org>
Fri, 22 Nov 2013 14:06:00 -0800
changeset 174048 8c2dbfc2463f05723bde9100ecb87a3b2b84fea3
parent 174047 f1cca0891dbe0aae4f597c5b3db83d789aa7bf57
child 174049 0575a10042e27f518b45c0f52471c56596bab94c
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs938686
milestone28.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 938686 - Refactor Opus header parsing so it can be reused. r=kinetik We want to be able to use the same header parser for both the WebM and Ogg readers.
content/media/ogg/OggCodecState.cpp
content/media/ogg/OggCodecState.h
content/media/ogg/OpusParser.cpp
content/media/ogg/OpusParser.h
content/media/ogg/moz.build
--- a/content/media/ogg/OggCodecState.cpp
+++ b/content/media/ogg/OggCodecState.cpp
@@ -821,28 +821,17 @@ nsresult VorbisState::ReconstructVorbisG
   mGranulepos = last->granulepos;
 
   return NS_OK;
 }
 
 #ifdef MOZ_OPUS
 OpusState::OpusState(ogg_page* aBosPage) :
   OggCodecState(aBosPage, true),
-  mRate(0),
-  mNominalRate(0),
-  mChannels(0),
-  mPreSkip(0),
-#ifdef MOZ_SAMPLE_TYPE_FLOAT32
-  mGain(1.0f),
-#else
-  mGain_Q16(65536),
-#endif
-  mChannelMapping(0),
-  mStreams(0),
-  mCoupledStreams(0),
+  mParser(nullptr),
   mDecoder(nullptr),
   mSkip(0),
   mPrevPacketGranulepos(0),
   mPrevPageGranulepos(0)
 {
   MOZ_COUNT_CTOR(OpusState);
 }
 
@@ -864,17 +853,17 @@ nsresult OpusState::Reset()
 nsresult OpusState::Reset(bool aStart)
 {
   nsresult res = NS_OK;
 
   if (mActive && mDecoder) {
     // Reset the decoder.
     opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE);
     // Let the seek logic handle pre-roll if we're not seeking to the start.
-    mSkip = aStart ? mPreSkip : 0;
+    mSkip = aStart ? mParser->mPreSkip : 0;
     // This lets us distinguish the first page being the last page vs. just
     // not having processed the previous page when we encounter the last page.
     mPrevPageGranulepos = aStart ? 0 : -1;
     mPrevPacketGranulepos = aStart ? 0 : -1;
   }
 
   // Clear queued data.
   if (NS_FAILED(OggCodecState::Reset())) {
@@ -890,177 +879,56 @@ bool OpusState::Init(void)
 {
   if (!mActive)
     return false;
 
   int error;
 
   NS_ASSERTION(mDecoder == nullptr, "leaking OpusDecoder");
 
-  mDecoder = opus_multistream_decoder_create(mRate,
-                                             mChannels,
-                                             mStreams,
-                                             mCoupledStreams,
-                                             mMappingTable,
+  mDecoder = opus_multistream_decoder_create(mParser->mRate,
+                                             mParser->mChannels,
+                                             mParser->mStreams,
+                                             mParser->mCoupledStreams,
+                                             mParser->mMappingTable,
                                              &error);
 
-  mSkip = mPreSkip;
+  mSkip = mParser->mPreSkip;
 
   LOG(PR_LOG_DEBUG, ("Opus decoder init, to skip %d", mSkip));
 
   return error == OPUS_OK;
 }
 
 bool OpusState::DecodeHeader(ogg_packet* aPacket)
 {
   nsAutoRef<ogg_packet> autoRelease(aPacket);
   switch(mPacketCount++) {
     // Parse the id header.
     case 0: {
-      if (aPacket->bytes < 19 || memcmp(aPacket->packet, "OpusHead", 8)) {
-        LOG(PR_LOG_DEBUG, ("Invalid Opus file: unrecognized header"));
-        return false;
-      }
-
-      mRate = 48000; // The Opus decoder runs at 48 kHz regardless.
-
-      int version = aPacket->packet[8];
-      // Accept file format versions 0.x.
-      if ((version & 0xf0) != 0) {
-        LOG(PR_LOG_DEBUG, ("Rejecting unknown Opus file version %d", version));
-        return false;
-      }
-
-      mChannels = aPacket->packet[9];
-      if (mChannels<1) {
-        LOG(PR_LOG_DEBUG, ("Invalid Opus file: Number of channels %d", mChannels));
-        return false;
-      }
-      mPreSkip = LEUint16(aPacket->packet + 10);
-      mNominalRate = LEUint32(aPacket->packet + 12);
-      double gain_dB = LEInt16(aPacket->packet + 16) / 256.0;
-#ifdef MOZ_SAMPLE_TYPE_FLOAT32
-      mGain = static_cast<float>(pow(10,0.05*gain_dB));
-#else
-      mGain_Q16 = static_cast<int32_t>(std::min(65536*pow(10,0.05*gain_dB)+0.5,
-                                              static_cast<double>(INT32_MAX)));
-#endif
-      mChannelMapping = aPacket->packet[18];
-
-      if (mChannelMapping == 0) {
-        // Mapping family 0 only allows two channels
-        if (mChannels>2) {
-          LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for"
-                             " mapping family 0.", mChannels));
+        mParser = new OpusParser;
+        if(!mParser->DecodeHeader(aPacket->packet, aPacket->bytes)) {
           return false;
         }
-        mStreams = 1;
-        mCoupledStreams = mChannels - 1;
-        mMappingTable[0] = 0;
-        mMappingTable[1] = 1;
-      } else if (mChannelMapping == 1) {
-        // Currently only up to 8 channels are defined for mapping family 1
-        if (mChannels>8) {
-          LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for"
-                             " mapping family 1.", mChannels));
-          return false;
-        }
-        if (aPacket->bytes>20+mChannels) {
-          mStreams = aPacket->packet[19];
-          mCoupledStreams = aPacket->packet[20];
-          int i;
-          for (i=0; i<mChannels; i++)
-            mMappingTable[i] = aPacket->packet[21+i];
-        } else {
-          LOG(PR_LOG_DEBUG, ("Invalid Opus file: channel mapping %d,"
-                             " but no channel mapping table", mChannelMapping));
-          return false;
-        }
-      } else {
-        LOG(PR_LOG_DEBUG, ("Invalid Opus file: unsupported channel mapping "
-                           "family %d", mChannelMapping));
-        return false;
-      }
-      if (mStreams < 1) {
-        LOG(PR_LOG_DEBUG, ("Invalid Opus file: no streams"));
-        return false;
-      }
-      if (mCoupledStreams > mStreams) {
-        LOG(PR_LOG_DEBUG, ("Invalid Opus file: more coupled streams (%d) than "
-                           "total streams (%d)", mCoupledStreams, mStreams));
-        return false;
-      }
-
-#ifdef DEBUG
-      LOG(PR_LOG_DEBUG, ("Opus stream header:"));
-      LOG(PR_LOG_DEBUG, (" channels: %d", mChannels));
-      LOG(PR_LOG_DEBUG, ("  preskip: %d", mPreSkip));
-      LOG(PR_LOG_DEBUG, (" original: %d Hz", mNominalRate));
-      LOG(PR_LOG_DEBUG, ("     gain: %.2f dB", gain_dB));
-      LOG(PR_LOG_DEBUG, ("Channel Mapping:"));
-      LOG(PR_LOG_DEBUG, ("   family: %d", mChannelMapping));
-      LOG(PR_LOG_DEBUG, ("  streams: %d", mStreams));
+        mRate = mParser->mRate;
+        mChannels = mParser->mChannels;
+        mPreSkip = mParser->mPreSkip;
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+        mGain = mParser->mGain;
+#else
+        mGain_Q16 = mParser->mGain_Q16;
 #endif
     }
     break;
 
     // Parse the metadata header.
     case 1: {
-      if (aPacket->bytes < 16 || memcmp(aPacket->packet, "OpusTags", 8))
-        return false;
-
-      // Copy out the raw comment lines, but only do basic validation
-      // checks against the string packing: too little data, too many
-      // comments, or comments that are too long. Rejecting these cases
-      // helps reduce the propagation of broken files.
-      // We do not ensure they are valid UTF-8 here, nor do we validate
-      // the required ASCII_TAG=value format of the user comments.
-      const unsigned char* buf = aPacket->packet + 8;
-      uint32_t bytes = aPacket->bytes - 8;
-      uint32_t len;
-      // Read the vendor string.
-      len = LEUint32(buf);
-      buf += 4;
-      bytes -= 4;
-      if (len > bytes)
-        return false;
-      mVendorString = nsCString(reinterpret_cast<const char*>(buf), len);
-      buf += len;
-      bytes -= len;
-      // Read the user comments.
-      if (bytes < 4)
-        return false;
-      uint32_t ncomments = LEUint32(buf);
-      buf += 4;
-      bytes -= 4;
-      // If there are so many comments even their length fields
-      // won't fit in the packet, stop reading now.
-      if (ncomments > (bytes>>2))
-        return false;
-      uint32_t i;
-      for (i = 0; i < ncomments; i++) {
-        if (bytes < 4)
+        if(!mParser->DecodeTags(aPacket->packet, aPacket->bytes)) {
           return false;
-        len = LEUint32(buf);
-        buf += 4;
-        bytes -= 4;
-        if (len > bytes)
-          return false;
-        mTags.AppendElement(nsCString(reinterpret_cast<const char*>(buf), len));
-        buf += len;
-        bytes -= len;
-      }
-
-#ifdef DEBUG
-      LOG(PR_LOG_DEBUG, ("Opus metadata header:"));
-      LOG(PR_LOG_DEBUG, ("  vendor: %s", mVendorString.get()));
-      for (uint32_t i = 0; i < mTags.Length(); i++) {
-        LOG(PR_LOG_DEBUG, (" %s", mTags[i].get()));
-      }
-#endif
+        }
     }
     break;
 
     // We made it to the first data packet (which includes reconstructing
     // timestamps for it in PageIn). Success!
     default: {
       mDoneReadingHeaders = true;
       // Put it back on the queue so we can decode it.
@@ -1072,30 +940,30 @@ bool OpusState::DecodeHeader(ogg_packet*
 }
 
 /* Construct and return a tags hashmap from our internal array */
 MetadataTags* OpusState::GetTags()
 {
   MetadataTags* tags;
 
   tags = new MetadataTags;
-  for (uint32_t i = 0; i < mTags.Length(); i++) {
-    AddVorbisComment(tags, mTags[i].Data(), mTags[i].Length());
+  for (uint32_t i = 0; i < mParser->mTags.Length(); i++) {
+    AddVorbisComment(tags, mParser->mTags[i].Data(), mParser->mTags[i].Length());
   }
 
   return tags;
 }
 
 /* Return the timestamp (in microseconds) equivalent to a granulepos. */
 int64_t OpusState::Time(int64_t aGranulepos)
 {
   if (!mActive)
     return -1;
 
-  return Time(mPreSkip, aGranulepos);
+  return Time(mParser->mPreSkip, aGranulepos);
 }
 
 int64_t OpusState::Time(int aPreSkip, int64_t aGranulepos)
 {
   if (aGranulepos < 0)
     return -1;
 
   // Ogg Opus always runs at a granule rate of 48 kHz.
--- a/content/media/ogg/OggCodecState.h
+++ b/content/media/ogg/OggCodecState.h
@@ -31,16 +31,18 @@
 
 // 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>
 #endif
 
+#include "OpusParser.h"
+
 namespace mozilla {
 
 // Deallocates a packet, used in OggPacketQueue below.
 class OggPacketDeallocator : public nsDequeFunctor {
   virtual void* operator() (void* aPacket) {
     ogg_packet* p = static_cast<ogg_packet*>(aPacket);
     delete [] p->packet;
     delete p;
@@ -336,44 +338,37 @@ public:
   bool IsHeader(ogg_packet* aPacket);
   nsresult PageIn(ogg_page* aPage);
 
   // Returns the end time that a granulepos represents.
   static int64_t Time(int aPreSkip, int64_t aGranulepos);
 
   // Various fields from the Ogg Opus header.
   int mRate;        // Sample rate the decoder uses (always 48 kHz).
-  uint32_t mNominalRate; // Original sample rate of the data (informational).
   int mChannels;    // Number of channels the stream encodes.
   uint16_t mPreSkip; // Number of samples to strip after decoder reset.
 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
   float mGain;      // Gain to apply to decoder output.
 #else
   int32_t mGain_Q16; // Gain to apply to the decoder output.
 #endif
-  int mChannelMapping; // Channel mapping family.
-  int mStreams;     // Number of packed streams in each packet.
-  int mCoupledStreams; // Number of packed coupled streams in each packet.
-  unsigned char mMappingTable[255]; // Channel mapping table.
 
+  nsAutoPtr<OpusParser> mParser;
   OpusMSDecoder *mDecoder;
 
   int mSkip;        // Number of samples left to trim before playback.
   // Granule position (end sample) of the last decoded Opus packet. This is
   // used to calculate the amount we should trim from the last packet.
   int64_t mPrevPacketGranulepos;
 
   // Construct and return a table of tags from the metadata header.
   MetadataTags* GetTags();
 
 private:
 
-  nsCString mVendorString;   // Encoder vendor string from the header.
-  nsTArray<nsCString> mTags; // Unparsed comment strings from the header.
-
   // Reconstructs the granulepos of Opus packets stored in the
   // mUnstamped array. mUnstamped must be filled with consecutive packets from
   // the stream, with the last packet having a known granulepos. Using this
   // known granulepos, and the known frame numbers, we recover the granulepos
   // of all frames in the array. This enables us to determine their timestamps.
   bool ReconstructOpusGranulepos();
 
   // Granule position (end sample) of the last decoded Opus page. This is
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/OpusParser.cpp
@@ -0,0 +1,196 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "mozilla/DebugOnly.h"
+#include <stdint.h>
+
+#include "OpusParser.h"
+
+#include "nsDebug.h"
+#include "MediaDecoderReader.h"
+#include "VideoUtils.h"
+#include <algorithm>
+
+#include "opus/opus.h"
+extern "C" {
+#include "opus/opus_multistream.h"
+}
+
+namespace mozilla {
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gMediaDecoderLog;
+#define OPUS_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
+#else
+#define OPUS_LOG(type, msg)
+#endif
+
+OpusParser::OpusParser():
+  mRate(0),
+  mNominalRate(0),
+  mChannels(0),
+  mPreSkip(0),
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+  mGain(1.0f),
+#else
+  mGain_Q16(65536),
+#endif
+  mChannelMapping(0),
+  mStreams(0),
+  mCoupledStreams(0)
+{ }
+
+bool OpusParser::DecodeHeader(unsigned char* aData, size_t aLength)
+{
+    if (aLength < 19 || memcmp(aData, "OpusHead", 8)) {
+      OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: unrecognized header"));
+      return false;
+    }
+
+    mRate = 48000; // The Opus decoder runs at 48 kHz regardless.
+
+    int version = aData[8];
+    // Accept file format versions 0.x.
+    if ((version & 0xf0) != 0) {
+      OPUS_LOG(PR_LOG_DEBUG, ("Rejecting unknown Opus file version %d", version));
+      return false;
+    }
+
+    mChannels = aData[9];
+    if (mChannels<1) {
+      OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: Number of channels %d", mChannels));
+      return false;
+    }
+
+    mPreSkip = LEUint16(aData + 10);
+    mNominalRate = LEUint32(aData + 12);
+    double gain_dB = LEInt16(aData + 16) / 256.0;
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+    mGain = static_cast<float>(pow(10,0.05*gain_dB));
+#else
+    mGain_Q16 = static_cast<int32_t>(std::min(65536*pow(10,0.05*gain_dB)+0.5,
+                                            static_cast<double>(INT32_MAX)));
+#endif
+    mChannelMapping = aData[18];
+
+    if (mChannelMapping == 0) {
+      // Mapping family 0 only allows two channels
+      if (mChannels>2) {
+        OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for"
+                           " mapping family 0.", mChannels));
+        return false;
+      }
+      mStreams = 1;
+      mCoupledStreams = mChannels - 1;
+      mMappingTable[0] = 0;
+      mMappingTable[1] = 1;
+    } else if (mChannelMapping == 1) {
+      // Currently only up to 8 channels are defined for mapping family 1
+      if (mChannels>8) {
+        OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: too many channels (%d) for"
+                           " mapping family 1.", mChannels));
+        return false;
+      }
+      if (aLength>20+mChannels) {
+        mStreams = aData[19];
+        mCoupledStreams = aData[20];
+        int i;
+        for (i=0; i<mChannels; i++)
+          mMappingTable[i] = aData[21+i];
+      } else {
+        OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: channel mapping %d,"
+                           " but no channel mapping table", mChannelMapping));
+        return false;
+      }
+    } else {
+      OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: unsupported channel mapping "
+                         "family %d", mChannelMapping));
+      return false;
+    }
+    if (mStreams < 1) {
+      OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: no streams"));
+      return false;
+    }
+    if (mCoupledStreams > mStreams) {
+      OPUS_LOG(PR_LOG_DEBUG, ("Invalid Opus file: more coupled streams (%d) than "
+                         "total streams (%d)", mCoupledStreams, mStreams));
+      return false;
+    }
+
+#ifdef DEBUG
+    OPUS_LOG(PR_LOG_DEBUG, ("Opus stream header:"));
+    OPUS_LOG(PR_LOG_DEBUG, (" channels: %d", mChannels));
+    OPUS_LOG(PR_LOG_DEBUG, ("  preskip: %d", mPreSkip));
+    OPUS_LOG(PR_LOG_DEBUG, (" original: %d Hz", mNominalRate));
+    OPUS_LOG(PR_LOG_DEBUG, ("     gain: %.2f dB", gain_dB));
+    OPUS_LOG(PR_LOG_DEBUG, ("Channel Mapping:"));
+    OPUS_LOG(PR_LOG_DEBUG, ("   family: %d", mChannelMapping));
+    OPUS_LOG(PR_LOG_DEBUG, ("  streams: %d", mStreams));
+#endif
+  return true;
+}
+
+bool OpusParser::DecodeTags(unsigned char* aData, size_t aLength)
+{
+  if (aLength < 16 || memcmp(aData, "OpusTags", 8))
+    return false;
+
+  // Copy out the raw comment lines, but only do basic validation
+  // checks against the string packing: too little data, too many
+  // comments, or comments that are too long. Rejecting these cases
+  // helps reduce the propagation of broken files.
+  // We do not ensure they are valid UTF-8 here, nor do we validate
+  // the required ASCII_TAG=value format of the user comments.
+  const unsigned char* buf = aData + 8;
+  uint32_t bytes = aLength - 8;
+  uint32_t len;
+  // Read the vendor string.
+  len = LEUint32(buf);
+  buf += 4;
+  bytes -= 4;
+  if (len > bytes)
+    return false;
+  mVendorString = nsCString(reinterpret_cast<const char*>(buf), len);
+  buf += len;
+  bytes -= len;
+  // Read the user comments.
+  if (bytes < 4)
+    return false;
+  uint32_t ncomments = LEUint32(buf);
+  buf += 4;
+  bytes -= 4;
+  // If there are so many comments even their length fields
+  // won't fit in the packet, stop reading now.
+  if (ncomments > (bytes>>2))
+    return false;
+  uint32_t i;
+  for (i = 0; i < ncomments; i++) {
+    if (bytes < 4)
+      return false;
+    len = LEUint32(buf);
+    buf += 4;
+    bytes -= 4;
+    if (len > bytes)
+      return false;
+    mTags.AppendElement(nsCString(reinterpret_cast<const char*>(buf), len));
+    buf += len;
+    bytes -= len;
+  }
+
+#ifdef DEBUG
+  OPUS_LOG(PR_LOG_DEBUG, ("Opus metadata header:"));
+  OPUS_LOG(PR_LOG_DEBUG, ("  vendor: %s", mVendorString.get()));
+  for (uint32_t i = 0; i < mTags.Length(); i++) {
+    OPUS_LOG(PR_LOG_DEBUG, (" %s", mTags[i].get()));
+  }
+#endif
+  return true;
+}
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/OpusParser.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#if !defined(OpusParser_h_)
+#define OpusParser_h_
+
+#include <stdint.h>
+
+#include <opus/opus.h>
+#include "opus/opus_multistream.h"
+
+namespace mozilla {
+
+class OpusParser
+{
+public:
+  OpusParser();
+
+  bool DecodeHeader(unsigned char* aData, size_t aLength);
+  bool DecodeTags(unsigned char* aData, size_t aLength);
+
+  // Various fields from the Ogg Opus header.
+  int mRate;        // Sample rate the decoder uses (always 48 kHz).
+  uint32_t mNominalRate; // Original sample rate of the data (informational).
+  int mChannels;    // Number of channels the stream encodes.
+  uint16_t mPreSkip; // Number of samples to strip after decoder reset.
+#ifdef MOZ_SAMPLE_TYPE_FLOAT32
+  float mGain;      // Gain to apply to decoder output.
+#else
+  int32_t mGain_Q16; // Gain to apply to the decoder output.
+#endif
+  int mChannelMapping; // Channel mapping family.
+  int mStreams;     // Number of packed streams in each packet.
+  int mCoupledStreams; // Number of packed coupled streams in each packet.
+  unsigned char mMappingTable[255]; // Channel mapping table.
+
+  // Granule position (end sample) of the last decoded Opus packet. This is
+  // used to calculate the amount we should trim from the last packet.
+  int64_t mPrevPacketGranulepos;
+
+  nsTArray<nsCString> mTags; // Unparsed comment strings from the header.
+
+  nsCString mVendorString;   // Encoder vendor string from the header.
+
+};
+
+} // namespace mozilla
+
+#endif
--- a/content/media/ogg/moz.build
+++ b/content/media/ogg/moz.build
@@ -4,20 +4,22 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'OggCodecState.h',
     'OggDecoder.h',
     'OggReader.h',
     'OggWriter.h',
+    'OpusParser.h',
 ]
 
 UNIFIED_SOURCES += [
     'OggCodecState.cpp',
     'OggDecoder.cpp',
     'OggReader.cpp',
     'OggWriter.cpp',
+    'OpusParser.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'gklayout'