Bug 778053 - Read wave metadata. r=kinetik
authorChangZhuo Chen <czchen@gmail.com>
Sat, 06 Oct 2012 19:33:11 +0800
changeset 114445 87223b686e8556291642062ebd9da4f24906a69f
parent 114444 1a19b191539219f5d991ab844c1b52ccbc5a430f
child 114446 706259475ba65a06160683f7d4003f0bf844cc31
push id23917
push useremorley@mozilla.com
push dateThu, 29 Nov 2012 14:20:29 +0000
treeherdermozilla-central@c72d38e7a212 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs778053
milestone20.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 778053 - Read wave metadata. r=kinetik
content/media/wave/WaveReader.cpp
content/media/wave/WaveReader.h
--- a/content/media/wave/WaveReader.cpp
+++ b/content/media/wave/WaveReader.cpp
@@ -7,16 +7,18 @@
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #include "WaveReader.h"
 #include "nsTimeRanges.h"
 #include "MediaDecoderStateMachine.h"
 #include "VideoUtils.h"
 
 #include "mozilla/StandardInteger.h"
+#include "mozilla/Util.h"
+#include "mozilla/CheckedInt.h"
 
 namespace mozilla {
 
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gMediaDecoderLog;
@@ -26,27 +28,34 @@ extern PRLogModuleInfo* gMediaDecoderLog
 #else
 #define SEEK_LOG(type, msg)
 #endif
 #else
 #define LOG(type, msg)
 #define SEEK_LOG(type, msg)
 #endif
 
+struct waveIdToName {
+  uint32_t id;
+  nsCString name;
+};
+
+
 // Magic values that identify RIFF chunks we're interested in.
 static const uint32_t RIFF_CHUNK_MAGIC = 0x52494646;
 static const uint32_t WAVE_CHUNK_MAGIC = 0x57415645;
 static const uint32_t FRMT_CHUNK_MAGIC = 0x666d7420;
 static const uint32_t DATA_CHUNK_MAGIC = 0x64617461;
+static const uint32_t LIST_CHUNK_MAGIC = 0x4c495354;
 
-// Size of RIFF chunk header.  4 byte chunk header type and 4 byte size field.
-static const uint16_t RIFF_CHUNK_HEADER_SIZE = 8;
+// Size of chunk header.  4 byte chunk header type and 4 byte size field.
+static const uint16_t CHUNK_HEADER_SIZE = 8;
 
 // Size of RIFF header.  RIFF chunk and 4 byte RIFF type.
-static const uint16_t RIFF_INITIAL_SIZE = RIFF_CHUNK_HEADER_SIZE + 4;
+static const uint16_t RIFF_INITIAL_SIZE = CHUNK_HEADER_SIZE + 4;
 
 // Size of required part of format chunk.  Actual format chunks may be
 // extended (for non-PCM encodings), but we skip any extended data.
 static const uint16_t WAVE_FORMAT_CHUNK_SIZE = 16;
 
 // PCM encoding type from format chunk.  Linear PCM is the only encoding
 // supported by AudioStream.
 static const uint16_t WAVE_FORMAT_ENCODING_PCM = 1;
@@ -120,29 +129,36 @@ nsresult WaveReader::Init(MediaDecoderRe
   return NS_OK;
 }
 
 nsresult WaveReader::ReadMetadata(VideoInfo* aInfo,
                                     MetadataTags** aTags)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
-  bool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset();
+  bool loaded = LoadRIFFChunk();
   if (!loaded) {
     return NS_ERROR_FAILURE;
   }
 
+  nsAutoPtr<nsHTMLMediaElement::MetadataTags> tags;
+
+  bool loadAllChunks = LoadAllChunks(tags);
+  if (!loadAllChunks) {
+    return NS_ERROR_FAILURE;
+  }
+
   mInfo.mHasAudio = true;
   mInfo.mHasVideo = false;
   mInfo.mAudioRate = mSampleRate;
   mInfo.mAudioChannels = mChannels;
 
   *aInfo = mInfo;
 
-  *aTags = nullptr;
+  *aTags = tags.forget();
 
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   mDecoder->SetMediaDuration(
     static_cast<int64_t>(BytesToTime(GetDataLength()) * USECS_PER_S));
 
   return NS_OK;
 }
@@ -330,72 +346,26 @@ WaveReader::LoadRIFFChunk()
     NS_WARNING("Expected WAVE chunk");
     return false;
   }
 
   return true;
 }
 
 bool
-WaveReader::ScanForwardUntil(uint32_t aWantedChunk, uint32_t* aChunkSize)
+WaveReader::LoadFormatChunk(uint32_t aChunkSize)
 {
-  NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument");
-  *aChunkSize = 0;
-
-  for (;;) {
-    static const unsigned int CHUNK_HEADER_SIZE = 8;
-    char chunkHeader[CHUNK_HEADER_SIZE];
-    const char* p = chunkHeader;
-
-    if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
-      return false;
-    }
-
-    PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
-    uint32_t magic = ReadUint32BE(&p);
-    uint32_t chunkSize = ReadUint32LE(&p);
-
-    if (magic == aWantedChunk) {
-      *aChunkSize = chunkSize;
-      return true;
-    }
-
-    // RIFF chunks are two-byte aligned, so round up if necessary.
-    chunkSize += chunkSize % 2;
-
-    static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
-    PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char));
-    nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
-    while (chunkSize > 0) {
-      uint32_t size = NS_MIN(chunkSize, MAX_CHUNK_SIZE);
-      if (!ReadAll(chunk.get(), size)) {
-        return false;
-      }
-      chunkSize -= size;
-    }
-  }
-}
-
-bool
-WaveReader::LoadFormatChunk()
-{
-  uint32_t fmtSize, rate, channels, frameSize, sampleFormat;
+  uint32_t rate, channels, frameSize, sampleFormat;
   char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
   const char* p = waveFormat;
 
   // RIFF chunks are always word (two byte) aligned.
   NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
                     "LoadFormatChunk called with unaligned resource");
 
-  // The "format" chunk may not directly follow the "riff" chunk, so skip
-  // over any intermediate chunks.
-  if (!ScanForwardUntil(FRMT_CHUNK_MAGIC, &fmtSize)) {
-    return false;
-  }
-
   if (!ReadAll(waveFormat, sizeof(waveFormat))) {
     return false;
   }
 
   PR_STATIC_ASSERT(sizeof(uint16_t) +
                    sizeof(uint16_t) +
                    sizeof(uint32_t) +
                    4 +
@@ -416,27 +386,27 @@ WaveReader::LoadFormatChunk()
 
   sampleFormat = ReadUint16LE(&p);
 
   // PCM encoded WAVEs are not expected to have an extended "format" chunk,
   // but I have found WAVEs that have a extended "format" chunk with an
   // extension size of 0 bytes.  Be polite and handle this rather than
   // considering the file invalid.  This code skips any extension of the
   // "format" chunk.
-  if (fmtSize > WAVE_FORMAT_CHUNK_SIZE) {
+  if (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) {
     char extLength[2];
     const char* p = extLength;
 
     if (!ReadAll(extLength, sizeof(extLength))) {
       return false;
     }
 
     PR_STATIC_ASSERT(sizeof(uint16_t) <= sizeof(extLength));
     uint16_t extra = ReadUint16LE(&p);
-    if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
+    if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
       NS_WARNING("Invalid extended format chunk size");
       return false;
     }
     extra += extra % 2;
 
     if (extra > 0) {
       PR_STATIC_ASSERT(UINT16_MAX + (UINT16_MAX % 2) < UINT_MAX / sizeof(char));
       nsAutoArrayPtr<char> chunkExtension(new char[extra]);
@@ -471,37 +441,30 @@ WaveReader::LoadFormatChunk()
     mSampleFormat = FORMAT_U8;
   } else {
     mSampleFormat = FORMAT_S16;
   }
   return true;
 }
 
 bool
-WaveReader::FindDataOffset()
+WaveReader::FindDataOffset(uint32_t aChunkSize)
 {
   // RIFF chunks are always word (two byte) aligned.
   NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
                     "FindDataOffset called with unaligned resource");
 
-  // The "data" chunk may not directly follow the "format" chunk, so skip
-  // over any intermediate chunks.
-  uint32_t length;
-  if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) {
-    return false;
-  }
-
   int64_t offset = mDecoder->GetResource()->Tell();
   if (offset <= 0 || offset > UINT32_MAX) {
     NS_WARNING("PCM data offset out of range");
     return false;
   }
 
   ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor());
-  mWaveLength = length;
+  mWaveLength = aChunkSize;
   mWavePCMOffset = uint32_t(offset);
   return true;
 }
 
 double
 WaveReader::BytesToTime(int64_t aBytes) const
 {
   NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
@@ -538,9 +501,178 @@ WaveReader::GetDataLength()
 }
 
 int64_t
 WaveReader::GetPosition()
 {
   return mDecoder->GetResource()->Tell();
 }
 
+bool
+WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize)
+{
+  NS_ABORT_IF_FALSE(aChunk, "Must have aChunk");
+  NS_ABORT_IF_FALSE(aChunkSize, "Must have aChunkSize");
+  NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
+                    "GetNextChunk called with unaligned resource");
+
+  char chunkHeader[CHUNK_HEADER_SIZE];
+  const char* p = chunkHeader;
+
+  if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
+    return false;
+  }
+
+  PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
+  *aChunk = ReadUint32BE(&p);
+  *aChunkSize = ReadUint32LE(&p);
+
+  return true;
+}
+
+bool
+WaveReader::LoadListChunk(uint32_t aChunkSize,
+    nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags)
+{
+  // List chunks are always word (two byte) aligned.
+  NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
+                    "LoadListChunk called with unaligned resource");
+
+  static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
+  PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char));
+
+  if (aChunkSize > MAX_CHUNK_SIZE) {
+    return false;
+  }
+
+  nsAutoArrayPtr<char> chunk(new char[aChunkSize]);
+  if (!ReadAll(chunk.get(), aChunkSize)) {
+    return false;
+  }
+
+  static const uint32_t INFO_LIST_MAGIC = 0x494e464f;
+  const char *p = chunk.get();
+  if (ReadUint32BE(&p) != INFO_LIST_MAGIC) {
+    return false;
+  }
+
+  const waveIdToName ID_TO_NAME[] = {
+    { 0x49415254, NS_LITERAL_CSTRING("artist") },   // IART
+    { 0x49434d54, NS_LITERAL_CSTRING("comments") }, // ICMT
+    { 0x49474e52, NS_LITERAL_CSTRING("genre") },    // IGNR
+    { 0x494e414d, NS_LITERAL_CSTRING("name") },     // INAM
+  };
+
+  const char* const end = chunk.get() + aChunkSize;
+
+  aTags = new nsHTMLMediaElement::MetadataTags;
+  aTags->Init();
+
+  while (p + 8 < end) {
+    uint32_t id = ReadUint32BE(&p);
+    // Uppercase tag id, inspired by GStreamer's Wave parser.
+    id &= 0xDFDFDFDF;
+
+    uint32_t length = ReadUint32LE(&p);
+
+    // Subchunk shall not exceed parent chunk.
+    if (p + length > end) {
+      break;
+    }
+
+    nsCString val(p, length);
+    if (val[length - 1] == '\0') {
+      val.SetLength(length - 1);
+    }
+
+    // Chunks in List::INFO are always word (two byte) aligned. So round up if
+    // necessary.
+    length += length % 2;
+    p += length;
+
+    if (!IsUTF8(val)) {
+      continue;
+    }
+
+    for (size_t i = 0; i < mozilla::ArrayLength(ID_TO_NAME); ++i) {
+      if (id == ID_TO_NAME[i].id) {
+        aTags->Put(ID_TO_NAME[i].name, val);
+        break;
+      }
+    }
+  }
+
+  return true;
+}
+
+bool
+WaveReader::LoadAllChunks(nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags)
+{
+  // Chunks are always word (two byte) aligned.
+  NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
+                    "LoadAllChunks called with unaligned resource");
+
+  bool loadFormatChunk = false;
+  bool findDataOffset = false;
+
+  for (;;) {
+    static const unsigned int CHUNK_HEADER_SIZE = 8;
+    char chunkHeader[CHUNK_HEADER_SIZE];
+    const char* p = chunkHeader;
+
+    if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
+      return false;
+    }
+
+    PR_STATIC_ASSERT(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE);
+
+    uint32_t magic = ReadUint32BE(&p);
+    uint32_t chunkSize = ReadUint32LE(&p);
+    int64_t chunkStart = GetPosition();
+
+    switch (magic) {
+      case FRMT_CHUNK_MAGIC:
+        loadFormatChunk = LoadFormatChunk(chunkSize);
+        if (!loadFormatChunk) {
+          return false;
+        }
+        break;
+
+      case LIST_CHUNK_MAGIC:
+        if (!aTags) {
+          LoadListChunk(chunkSize, aTags);
+        }
+        break;
+
+      case DATA_CHUNK_MAGIC:
+        findDataOffset = FindDataOffset(chunkSize);
+        return loadFormatChunk && findDataOffset;
+
+      default:
+        break;
+    }
+
+    // RIFF chunks are two-byte aligned, so round up if necessary.
+    chunkSize += chunkSize % 2;
+
+    // Move forward to next chunk
+    CheckedInt64 forward = CheckedInt64(chunkStart) + chunkSize - GetPosition();
+
+    if (!forward.isValid() || forward.value() < 0) {
+      return false;
+    }
+
+    static const int64_t MAX_CHUNK_SIZE = 1 << 16;
+    PR_STATIC_ASSERT(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char));
+    nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
+    while (forward.value() > 0) {
+      int64_t size = NS_MIN(forward.value(), MAX_CHUNK_SIZE);
+      if (!ReadAll(chunk.get(), size)) {
+        return false;
+      }
+      forward -= size;
+    }
+  }
+
+  return false;
+}
+
 } // namespace mozilla
--- a/content/media/wave/WaveReader.h
+++ b/content/media/wave/WaveReader.h
@@ -41,19 +41,21 @@ public:
   // To seek in a buffered range, we just have to seek the stream.
   virtual bool IsSeekableInBufferedRanges() {
     return true;
   }
 
 private:
   bool ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead = nullptr);
   bool LoadRIFFChunk();
-  bool ScanForwardUntil(uint32_t aWantedChunk, uint32_t* aChunkSize);
-  bool LoadFormatChunk();
-  bool FindDataOffset();
+  bool GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize);
+  bool LoadFormatChunk(uint32_t aChunkSize);
+  bool FindDataOffset(uint32_t aChunkSize);
+  bool LoadListChunk(uint32_t aChunkSize, nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags);
+  bool LoadAllChunks(nsAutoPtr<nsHTMLMediaElement::MetadataTags> &aTags);
 
   // Returns the number of seconds that aBytes represents based on the
   // current audio parameters.  e.g.  176400 bytes is 1 second at 16-bit
   // stereo 44.1kHz. The time is rounded to the nearest microsecond.
   double BytesToTime(int64_t aBytes) const;
 
   // Returns the number of bytes that aTime represents based on the current
   // audio parameters.  e.g.  1 second is 176400 bytes at 16-bit stereo