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::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize) { MOZ_ASSERT(aChunk, "Must have aChunk"); MOZ_ASSERT(aChunkSize, "Must have aChunkSize"); MOZ_ASSERT(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; } static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE, "Reads would overflow chunkHeader buffer."); *aChunk = ReadUint32BE(&p); *aChunkSize = ReadUint32LE(&p); return true; }
bool WaveReader::ScanForwardUntil(uint32_t aWantedChunk, 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::LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags) { // Chunks are always word (two byte) aligned. MOZ_ASSERT(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; } static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE, "Reads would overflow chunkHeader buffer."); 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; static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char), "MAX_CHUNK_SIZE too large for enumerator."); nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]); while (forward.value() > 0) { int64_t size = std::min(forward.value(), MAX_CHUNK_SIZE); if (!ReadAll(chunk.get(), size)) { return false; } forward -= size; } } return false; }
bool WaveReader::LoadListChunk(uint32_t aChunkSize, nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags) { // List chunks are always word (two byte) aligned. MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0, "LoadListChunk called with unaligned resource"); static const unsigned int MAX_CHUNK_SIZE = 1 << 16; static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char), "MAX_CHUNK_SIZE too large for enumerator."); if (aChunkSize > MAX_CHUNK_SIZE || aChunkSize < 4) { 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 dom::HTMLMediaElement::MetadataTags; 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 (uint32_t(end - p) < length) { break; } // Wrap the string, adjusting length to account for optional // null termination in the chunk. nsCString val(p, length); if (length > 0 && 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::LoadFormatChunk(uint32_t aChunkSize) { uint32_t rate, channels, frameSize, sampleFormat; char waveFormat[WAVE_FORMAT_CHUNK_SIZE]; const char* p = waveFormat; // RIFF chunks are always word (two byte) aligned. MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0, "LoadFormatChunk called with unaligned resource"); if (!ReadAll(waveFormat, sizeof(waveFormat))) { return false; } static_assert(sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + 4 + sizeof(uint16_t) + sizeof(uint16_t) <= sizeof(waveFormat), "Reads would overflow waveFormat buffer."); if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) { NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported"); return false; } channels = ReadUint16LE(&p); rate = ReadUint32LE(&p); // Skip over average bytes per second field. p += 4; frameSize = ReadUint16LE(&p); 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 (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) { char extLength[2]; const char* p = extLength; if (!ReadAll(extLength, sizeof(extLength))) { return false; } static_assert(sizeof(uint16_t) <= sizeof(extLength), "Reads would overflow extLength buffer."); uint16_t extra = ReadUint16LE(&p); if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) { NS_WARNING("Invalid extended format chunk size"); return false; } extra += extra % 2; if (extra > 0) { static_assert(UINT16_MAX + (UINT16_MAX % 2) < UINT_MAX / sizeof(char), "chunkExtension array too large for iterator."); nsAutoArrayPtr<char> chunkExtension(new char[extra]); if (!ReadAll(chunkExtension.get(), extra)) { return false; } } } // RIFF chunks are always word (two byte) aligned. MOZ_ASSERT(mDecoder->GetResource()->Tell() % 2 == 0, "LoadFormatChunk left resource unaligned"); // Make sure metadata is fairly sane. The rate check is fairly arbitrary, // but the channels check is intentionally limited to mono or stereo // when the media is intended for direct playback because that's what the // audio backend currently supports. unsigned int actualFrameSize = (sampleFormat == 8 ? 1 : 2) * channels; if (rate < 100 || rate > 96000 || (((channels < 1 || channels > MAX_CHANNELS) || (frameSize != 1 && frameSize != 2 && frameSize != 4)) && !mIgnoreAudioOutputFormat) || (sampleFormat != 8 && sampleFormat != 16) || frameSize != actualFrameSize) { NS_WARNING("Invalid WAVE metadata"); return false; } ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor()); mSampleRate = rate; mChannels = channels; mFrameSize = frameSize; if (sampleFormat == 8) { mSampleFormat = FORMAT_U8; } else { mSampleFormat = FORMAT_S16; } return true; }
bool WaveReader::LoadFormatChunk() { uint32_t fmtSize, 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 + sizeof(uint16_t) + sizeof(uint16_t) <= sizeof(waveFormat)); if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) { NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported"); return false; } channels = ReadUint16LE(&p); rate = ReadUint32LE(&p); // Skip over average bytes per second field. p += 4; frameSize = ReadUint16LE(&p); 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) { 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) { 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]); if (!ReadAll(chunkExtension.get(), extra)) { return false; } } } // RIFF chunks are always word (two byte) aligned. NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, "LoadFormatChunk left resource unaligned"); // Make sure metadata is fairly sane. The rate check is fairly arbitrary, // but the channels check is intentionally limited to mono or stereo // because that's what the audio backend currently supports. unsigned int actualFrameSize = sampleFormat == 8 ? 1 : 2 * channels; if (rate < 100 || rate > 96000 || channels < 1 || channels > MAX_CHANNELS || (frameSize != 1 && frameSize != 2 && frameSize != 4) || (sampleFormat != 8 && sampleFormat != 16) || frameSize != actualFrameSize) { NS_WARNING("Invalid WAVE metadata"); return false; } ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor()); mSampleRate = rate; mChannels = channels; mFrameSize = frameSize; if (sampleFormat == 8) { mSampleFormat = FORMAT_U8; } else { mSampleFormat = FORMAT_S16; } return true; }