1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/wave/WaveReader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,685 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +#include "nsError.h" 1.10 +#include "AbstractMediaDecoder.h" 1.11 +#include "MediaResource.h" 1.12 +#include "WaveReader.h" 1.13 +#include "mozilla/dom/TimeRanges.h" 1.14 +#include "MediaDecoderStateMachine.h" 1.15 +#include "VideoUtils.h" 1.16 +#include "nsISeekableStream.h" 1.17 + 1.18 +#include <stdint.h> 1.19 +#include "mozilla/ArrayUtils.h" 1.20 +#include "mozilla/CheckedInt.h" 1.21 +#include "mozilla/Endian.h" 1.22 +#include <algorithm> 1.23 + 1.24 +namespace mozilla { 1.25 + 1.26 +// Un-comment to enable logging of seek bisections. 1.27 +//#define SEEK_LOGGING 1.28 + 1.29 +#ifdef PR_LOGGING 1.30 +extern PRLogModuleInfo* gMediaDecoderLog; 1.31 +#define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) 1.32 +#ifdef SEEK_LOGGING 1.33 +#define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg) 1.34 +#else 1.35 +#define SEEK_LOG(type, msg) 1.36 +#endif 1.37 +#else 1.38 +#define LOG(type, msg) 1.39 +#define SEEK_LOG(type, msg) 1.40 +#endif 1.41 + 1.42 +struct waveIdToName { 1.43 + uint32_t id; 1.44 + nsCString name; 1.45 +}; 1.46 + 1.47 + 1.48 +// Magic values that identify RIFF chunks we're interested in. 1.49 +static const uint32_t RIFF_CHUNK_MAGIC = 0x52494646; 1.50 +static const uint32_t WAVE_CHUNK_MAGIC = 0x57415645; 1.51 +static const uint32_t FRMT_CHUNK_MAGIC = 0x666d7420; 1.52 +static const uint32_t DATA_CHUNK_MAGIC = 0x64617461; 1.53 +static const uint32_t LIST_CHUNK_MAGIC = 0x4c495354; 1.54 + 1.55 +// Size of chunk header. 4 byte chunk header type and 4 byte size field. 1.56 +static const uint16_t CHUNK_HEADER_SIZE = 8; 1.57 + 1.58 +// Size of RIFF header. RIFF chunk and 4 byte RIFF type. 1.59 +static const uint16_t RIFF_INITIAL_SIZE = CHUNK_HEADER_SIZE + 4; 1.60 + 1.61 +// Size of required part of format chunk. Actual format chunks may be 1.62 +// extended (for non-PCM encodings), but we skip any extended data. 1.63 +static const uint16_t WAVE_FORMAT_CHUNK_SIZE = 16; 1.64 + 1.65 +// PCM encoding type from format chunk. Linear PCM is the only encoding 1.66 +// supported by AudioStream. 1.67 +static const uint16_t WAVE_FORMAT_ENCODING_PCM = 1; 1.68 + 1.69 +// We reject files with more than this number of channels if we're decoding for 1.70 +// playback. 1.71 +static const uint8_t MAX_CHANNELS = 2; 1.72 + 1.73 +namespace { 1.74 + uint32_t 1.75 + ReadUint32BE(const char** aBuffer) 1.76 + { 1.77 + uint32_t result = BigEndian::readUint32(*aBuffer); 1.78 + *aBuffer += sizeof(uint32_t); 1.79 + return result; 1.80 + } 1.81 + 1.82 + uint32_t 1.83 + ReadUint32LE(const char** aBuffer) 1.84 + { 1.85 + uint32_t result = LittleEndian::readUint32(*aBuffer); 1.86 + *aBuffer += sizeof(uint32_t); 1.87 + return result; 1.88 + } 1.89 + 1.90 + uint16_t 1.91 + ReadUint16LE(const char** aBuffer) 1.92 + { 1.93 + uint16_t result = LittleEndian::readUint16(*aBuffer); 1.94 + *aBuffer += sizeof(uint16_t); 1.95 + return result; 1.96 + } 1.97 + 1.98 + int16_t 1.99 + ReadInt16LE(const char** aBuffer) 1.100 + { 1.101 + uint16_t result = LittleEndian::readInt16(*aBuffer); 1.102 + *aBuffer += sizeof(int16_t); 1.103 + return result; 1.104 + } 1.105 + 1.106 + uint8_t 1.107 + ReadUint8(const char** aBuffer) 1.108 + { 1.109 + uint8_t result = uint8_t((*aBuffer)[0]); 1.110 + *aBuffer += sizeof(uint8_t); 1.111 + return result; 1.112 + } 1.113 +} 1.114 + 1.115 +WaveReader::WaveReader(AbstractMediaDecoder* aDecoder) 1.116 + : MediaDecoderReader(aDecoder) 1.117 +{ 1.118 + MOZ_COUNT_CTOR(WaveReader); 1.119 +} 1.120 + 1.121 +WaveReader::~WaveReader() 1.122 +{ 1.123 + MOZ_COUNT_DTOR(WaveReader); 1.124 +} 1.125 + 1.126 +nsresult WaveReader::Init(MediaDecoderReader* aCloneDonor) 1.127 +{ 1.128 + return NS_OK; 1.129 +} 1.130 + 1.131 +nsresult WaveReader::ReadMetadata(MediaInfo* aInfo, 1.132 + MetadataTags** aTags) 1.133 +{ 1.134 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.135 + 1.136 + bool loaded = LoadRIFFChunk(); 1.137 + if (!loaded) { 1.138 + return NS_ERROR_FAILURE; 1.139 + } 1.140 + 1.141 + nsAutoPtr<dom::HTMLMediaElement::MetadataTags> tags; 1.142 + 1.143 + bool loadAllChunks = LoadAllChunks(tags); 1.144 + if (!loadAllChunks) { 1.145 + return NS_ERROR_FAILURE; 1.146 + } 1.147 + 1.148 + mInfo.mAudio.mHasAudio = true; 1.149 + mInfo.mAudio.mRate = mSampleRate; 1.150 + mInfo.mAudio.mChannels = mChannels; 1.151 + 1.152 + *aInfo = mInfo; 1.153 + 1.154 + *aTags = tags.forget(); 1.155 + 1.156 + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); 1.157 + 1.158 + mDecoder->SetMediaDuration( 1.159 + static_cast<int64_t>(BytesToTime(GetDataLength()) * USECS_PER_S)); 1.160 + 1.161 + return NS_OK; 1.162 +} 1.163 + 1.164 +template <typename T> T UnsignedByteToAudioSample(uint8_t aValue); 1.165 +template <typename T> T SignedShortToAudioSample(int16_t aValue); 1.166 + 1.167 +template <> inline float 1.168 +UnsignedByteToAudioSample<float>(uint8_t aValue) 1.169 +{ 1.170 + return aValue * (2.0f / UINT8_MAX) - 1.0f; 1.171 +} 1.172 +template <> inline int16_t 1.173 +UnsignedByteToAudioSample<int16_t>(uint8_t aValue) 1.174 +{ 1.175 + return int16_t(aValue * UINT16_MAX / UINT8_MAX + INT16_MIN); 1.176 +} 1.177 + 1.178 +template <> inline float 1.179 +SignedShortToAudioSample<float>(int16_t aValue) 1.180 +{ 1.181 + return AudioSampleToFloat(aValue); 1.182 +} 1.183 +template <> inline int16_t 1.184 +SignedShortToAudioSample<int16_t>(int16_t aValue) 1.185 +{ 1.186 + return aValue; 1.187 +} 1.188 + 1.189 +bool WaveReader::DecodeAudioData() 1.190 +{ 1.191 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.192 + 1.193 + int64_t pos = GetPosition() - mWavePCMOffset; 1.194 + int64_t len = GetDataLength(); 1.195 + int64_t remaining = len - pos; 1.196 + NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length"); 1.197 + 1.198 + static const int64_t BLOCK_SIZE = 4096; 1.199 + int64_t readSize = std::min(BLOCK_SIZE, remaining); 1.200 + int64_t frames = readSize / mFrameSize; 1.201 + 1.202 + static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX / 1.203 + sizeof(AudioDataValue) / MAX_CHANNELS, 1.204 + "bufferSize calculation could overflow."); 1.205 + const size_t bufferSize = static_cast<size_t>(frames * mChannels); 1.206 + nsAutoArrayPtr<AudioDataValue> sampleBuffer(new AudioDataValue[bufferSize]); 1.207 + 1.208 + static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX / sizeof(char), 1.209 + "BLOCK_SIZE too large for enumerator."); 1.210 + nsAutoArrayPtr<char> dataBuffer(new char[static_cast<size_t>(readSize)]); 1.211 + 1.212 + if (!ReadAll(dataBuffer, readSize)) { 1.213 + return false; 1.214 + } 1.215 + 1.216 + // convert data to samples 1.217 + const char* d = dataBuffer.get(); 1.218 + AudioDataValue* s = sampleBuffer.get(); 1.219 + for (int i = 0; i < frames; ++i) { 1.220 + for (unsigned int j = 0; j < mChannels; ++j) { 1.221 + if (mSampleFormat == FORMAT_U8) { 1.222 + uint8_t v = ReadUint8(&d); 1.223 + *s++ = UnsignedByteToAudioSample<AudioDataValue>(v); 1.224 + } else if (mSampleFormat == FORMAT_S16) { 1.225 + int16_t v = ReadInt16LE(&d); 1.226 + *s++ = SignedShortToAudioSample<AudioDataValue>(v); 1.227 + } 1.228 + } 1.229 + } 1.230 + 1.231 + double posTime = BytesToTime(pos); 1.232 + double readSizeTime = BytesToTime(readSize); 1.233 + NS_ASSERTION(posTime <= INT64_MAX / USECS_PER_S, "posTime overflow"); 1.234 + NS_ASSERTION(readSizeTime <= INT64_MAX / USECS_PER_S, "readSizeTime overflow"); 1.235 + NS_ASSERTION(frames < INT32_MAX, "frames overflow"); 1.236 + 1.237 + mAudioQueue.Push(new AudioData(pos, 1.238 + static_cast<int64_t>(posTime * USECS_PER_S), 1.239 + static_cast<int64_t>(readSizeTime * USECS_PER_S), 1.240 + static_cast<int32_t>(frames), 1.241 + sampleBuffer.forget(), 1.242 + mChannels)); 1.243 + 1.244 + return true; 1.245 +} 1.246 + 1.247 +bool WaveReader::DecodeVideoFrame(bool &aKeyframeSkip, 1.248 + int64_t aTimeThreshold) 1.249 +{ 1.250 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.251 + 1.252 + return false; 1.253 +} 1.254 + 1.255 +nsresult WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) 1.256 +{ 1.257 + NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); 1.258 + LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget)); 1.259 + if (NS_FAILED(ResetDecode())) { 1.260 + return NS_ERROR_FAILURE; 1.261 + } 1.262 + double d = BytesToTime(GetDataLength()); 1.263 + NS_ASSERTION(d < INT64_MAX / USECS_PER_S, "Duration overflow"); 1.264 + int64_t duration = static_cast<int64_t>(d * USECS_PER_S); 1.265 + double seekTime = std::min(aTarget, duration) / static_cast<double>(USECS_PER_S); 1.266 + int64_t position = RoundDownToFrame(static_cast<int64_t>(TimeToBytes(seekTime))); 1.267 + NS_ASSERTION(INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek"); 1.268 + position += mWavePCMOffset; 1.269 + return mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position); 1.270 +} 1.271 + 1.272 +static double RoundToUsecs(double aSeconds) { 1.273 + return floor(aSeconds * USECS_PER_S) / USECS_PER_S; 1.274 +} 1.275 + 1.276 +nsresult WaveReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime) 1.277 +{ 1.278 + if (!mInfo.HasAudio()) { 1.279 + return NS_OK; 1.280 + } 1.281 + int64_t startOffset = mDecoder->GetResource()->GetNextCachedData(mWavePCMOffset); 1.282 + while (startOffset >= 0) { 1.283 + int64_t endOffset = mDecoder->GetResource()->GetCachedDataEnd(startOffset); 1.284 + // Bytes [startOffset..endOffset] are cached. 1.285 + NS_ASSERTION(startOffset >= mWavePCMOffset, "Integer underflow in GetBuffered"); 1.286 + NS_ASSERTION(endOffset >= mWavePCMOffset, "Integer underflow in GetBuffered"); 1.287 + 1.288 + // We need to round the buffered ranges' times to microseconds so that they 1.289 + // have the same precision as the currentTime and duration attribute on 1.290 + // the media element. 1.291 + aBuffered->Add(RoundToUsecs(BytesToTime(startOffset - mWavePCMOffset)), 1.292 + RoundToUsecs(BytesToTime(endOffset - mWavePCMOffset))); 1.293 + startOffset = mDecoder->GetResource()->GetNextCachedData(endOffset); 1.294 + } 1.295 + return NS_OK; 1.296 +} 1.297 + 1.298 +bool 1.299 +WaveReader::ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead) 1.300 +{ 1.301 + uint32_t got = 0; 1.302 + if (aBytesRead) { 1.303 + *aBytesRead = 0; 1.304 + } 1.305 + do { 1.306 + uint32_t read = 0; 1.307 + if (NS_FAILED(mDecoder->GetResource()->Read(aBuf + got, uint32_t(aSize - got), &read))) { 1.308 + NS_WARNING("Resource read failed"); 1.309 + return false; 1.310 + } 1.311 + if (read == 0) { 1.312 + return false; 1.313 + } 1.314 + got += read; 1.315 + if (aBytesRead) { 1.316 + *aBytesRead = got; 1.317 + } 1.318 + } while (got != aSize); 1.319 + return true; 1.320 +} 1.321 + 1.322 +bool 1.323 +WaveReader::LoadRIFFChunk() 1.324 +{ 1.325 + char riffHeader[RIFF_INITIAL_SIZE]; 1.326 + const char* p = riffHeader; 1.327 + 1.328 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() == 0, 1.329 + "LoadRIFFChunk called when resource in invalid state"); 1.330 + 1.331 + if (!ReadAll(riffHeader, sizeof(riffHeader))) { 1.332 + return false; 1.333 + } 1.334 + 1.335 + static_assert(sizeof(uint32_t) * 3 <= RIFF_INITIAL_SIZE, 1.336 + "Reads would overflow riffHeader buffer."); 1.337 + if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) { 1.338 + NS_WARNING("resource data not in RIFF format"); 1.339 + return false; 1.340 + } 1.341 + 1.342 + // Skip over RIFF size field. 1.343 + p += sizeof(uint32_t); 1.344 + 1.345 + if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) { 1.346 + NS_WARNING("Expected WAVE chunk"); 1.347 + return false; 1.348 + } 1.349 + 1.350 + return true; 1.351 +} 1.352 + 1.353 +bool 1.354 +WaveReader::LoadFormatChunk(uint32_t aChunkSize) 1.355 +{ 1.356 + uint32_t rate, channels, frameSize, sampleFormat; 1.357 + char waveFormat[WAVE_FORMAT_CHUNK_SIZE]; 1.358 + const char* p = waveFormat; 1.359 + 1.360 + // RIFF chunks are always word (two byte) aligned. 1.361 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.362 + "LoadFormatChunk called with unaligned resource"); 1.363 + 1.364 + if (!ReadAll(waveFormat, sizeof(waveFormat))) { 1.365 + return false; 1.366 + } 1.367 + 1.368 + static_assert(sizeof(uint16_t) + 1.369 + sizeof(uint16_t) + 1.370 + sizeof(uint32_t) + 1.371 + 4 + 1.372 + sizeof(uint16_t) + 1.373 + sizeof(uint16_t) <= sizeof(waveFormat), 1.374 + "Reads would overflow waveFormat buffer."); 1.375 + if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) { 1.376 + NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported"); 1.377 + return false; 1.378 + } 1.379 + 1.380 + channels = ReadUint16LE(&p); 1.381 + rate = ReadUint32LE(&p); 1.382 + 1.383 + // Skip over average bytes per second field. 1.384 + p += 4; 1.385 + 1.386 + frameSize = ReadUint16LE(&p); 1.387 + 1.388 + sampleFormat = ReadUint16LE(&p); 1.389 + 1.390 + // PCM encoded WAVEs are not expected to have an extended "format" chunk, 1.391 + // but I have found WAVEs that have a extended "format" chunk with an 1.392 + // extension size of 0 bytes. Be polite and handle this rather than 1.393 + // considering the file invalid. This code skips any extension of the 1.394 + // "format" chunk. 1.395 + if (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) { 1.396 + char extLength[2]; 1.397 + const char* p = extLength; 1.398 + 1.399 + if (!ReadAll(extLength, sizeof(extLength))) { 1.400 + return false; 1.401 + } 1.402 + 1.403 + static_assert(sizeof(uint16_t) <= sizeof(extLength), 1.404 + "Reads would overflow extLength buffer."); 1.405 + uint16_t extra = ReadUint16LE(&p); 1.406 + if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) { 1.407 + NS_WARNING("Invalid extended format chunk size"); 1.408 + return false; 1.409 + } 1.410 + extra += extra % 2; 1.411 + 1.412 + if (extra > 0) { 1.413 + static_assert(UINT16_MAX + (UINT16_MAX % 2) < UINT_MAX / sizeof(char), 1.414 + "chunkExtension array too large for iterator."); 1.415 + nsAutoArrayPtr<char> chunkExtension(new char[extra]); 1.416 + if (!ReadAll(chunkExtension.get(), extra)) { 1.417 + return false; 1.418 + } 1.419 + } 1.420 + } 1.421 + 1.422 + // RIFF chunks are always word (two byte) aligned. 1.423 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.424 + "LoadFormatChunk left resource unaligned"); 1.425 + 1.426 + // Make sure metadata is fairly sane. The rate check is fairly arbitrary, 1.427 + // but the channels check is intentionally limited to mono or stereo 1.428 + // when the media is intended for direct playback because that's what the 1.429 + // audio backend currently supports. 1.430 + unsigned int actualFrameSize = (sampleFormat == 8 ? 1 : 2) * channels; 1.431 + if (rate < 100 || rate > 96000 || 1.432 + (((channels < 1 || channels > MAX_CHANNELS) || 1.433 + (frameSize != 1 && frameSize != 2 && frameSize != 4)) && 1.434 + !mIgnoreAudioOutputFormat) || 1.435 + (sampleFormat != 8 && sampleFormat != 16) || 1.436 + frameSize != actualFrameSize) { 1.437 + NS_WARNING("Invalid WAVE metadata"); 1.438 + return false; 1.439 + } 1.440 + 1.441 + ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor()); 1.442 + mSampleRate = rate; 1.443 + mChannels = channels; 1.444 + mFrameSize = frameSize; 1.445 + if (sampleFormat == 8) { 1.446 + mSampleFormat = FORMAT_U8; 1.447 + } else { 1.448 + mSampleFormat = FORMAT_S16; 1.449 + } 1.450 + return true; 1.451 +} 1.452 + 1.453 +bool 1.454 +WaveReader::FindDataOffset(uint32_t aChunkSize) 1.455 +{ 1.456 + // RIFF chunks are always word (two byte) aligned. 1.457 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.458 + "FindDataOffset called with unaligned resource"); 1.459 + 1.460 + int64_t offset = mDecoder->GetResource()->Tell(); 1.461 + if (offset <= 0 || offset > UINT32_MAX) { 1.462 + NS_WARNING("PCM data offset out of range"); 1.463 + return false; 1.464 + } 1.465 + 1.466 + ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor()); 1.467 + mWaveLength = aChunkSize; 1.468 + mWavePCMOffset = uint32_t(offset); 1.469 + return true; 1.470 +} 1.471 + 1.472 +double 1.473 +WaveReader::BytesToTime(int64_t aBytes) const 1.474 +{ 1.475 + NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0"); 1.476 + return float(aBytes) / mSampleRate / mFrameSize; 1.477 +} 1.478 + 1.479 +int64_t 1.480 +WaveReader::TimeToBytes(double aTime) const 1.481 +{ 1.482 + NS_ABORT_IF_FALSE(aTime >= 0.0f, "Must be >= 0"); 1.483 + return RoundDownToFrame(int64_t(aTime * mSampleRate * mFrameSize)); 1.484 +} 1.485 + 1.486 +int64_t 1.487 +WaveReader::RoundDownToFrame(int64_t aBytes) const 1.488 +{ 1.489 + NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0"); 1.490 + return aBytes - (aBytes % mFrameSize); 1.491 +} 1.492 + 1.493 +int64_t 1.494 +WaveReader::GetDataLength() 1.495 +{ 1.496 + int64_t length = mWaveLength; 1.497 + // If the decoder has a valid content length, and it's shorter than the 1.498 + // expected length of the PCM data, calculate the playback duration from 1.499 + // the content length rather than the expected PCM data length. 1.500 + int64_t streamLength = mDecoder->GetResource()->GetLength(); 1.501 + if (streamLength >= 0) { 1.502 + int64_t dataLength = std::max<int64_t>(0, streamLength - mWavePCMOffset); 1.503 + length = std::min(dataLength, length); 1.504 + } 1.505 + return length; 1.506 +} 1.507 + 1.508 +int64_t 1.509 +WaveReader::GetPosition() 1.510 +{ 1.511 + return mDecoder->GetResource()->Tell(); 1.512 +} 1.513 + 1.514 +bool 1.515 +WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize) 1.516 +{ 1.517 + NS_ABORT_IF_FALSE(aChunk, "Must have aChunk"); 1.518 + NS_ABORT_IF_FALSE(aChunkSize, "Must have aChunkSize"); 1.519 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.520 + "GetNextChunk called with unaligned resource"); 1.521 + 1.522 + char chunkHeader[CHUNK_HEADER_SIZE]; 1.523 + const char* p = chunkHeader; 1.524 + 1.525 + if (!ReadAll(chunkHeader, sizeof(chunkHeader))) { 1.526 + return false; 1.527 + } 1.528 + 1.529 + static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE, 1.530 + "Reads would overflow chunkHeader buffer."); 1.531 + *aChunk = ReadUint32BE(&p); 1.532 + *aChunkSize = ReadUint32LE(&p); 1.533 + 1.534 + return true; 1.535 +} 1.536 + 1.537 +bool 1.538 +WaveReader::LoadListChunk(uint32_t aChunkSize, 1.539 + nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags) 1.540 +{ 1.541 + // List chunks are always word (two byte) aligned. 1.542 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.543 + "LoadListChunk called with unaligned resource"); 1.544 + 1.545 + static const unsigned int MAX_CHUNK_SIZE = 1 << 16; 1.546 + static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char), 1.547 + "MAX_CHUNK_SIZE too large for enumerator."); 1.548 + 1.549 + if (aChunkSize > MAX_CHUNK_SIZE) { 1.550 + return false; 1.551 + } 1.552 + 1.553 + nsAutoArrayPtr<char> chunk(new char[aChunkSize]); 1.554 + if (!ReadAll(chunk.get(), aChunkSize)) { 1.555 + return false; 1.556 + } 1.557 + 1.558 + static const uint32_t INFO_LIST_MAGIC = 0x494e464f; 1.559 + const char *p = chunk.get(); 1.560 + if (ReadUint32BE(&p) != INFO_LIST_MAGIC) { 1.561 + return false; 1.562 + } 1.563 + 1.564 + const waveIdToName ID_TO_NAME[] = { 1.565 + { 0x49415254, NS_LITERAL_CSTRING("artist") }, // IART 1.566 + { 0x49434d54, NS_LITERAL_CSTRING("comments") }, // ICMT 1.567 + { 0x49474e52, NS_LITERAL_CSTRING("genre") }, // IGNR 1.568 + { 0x494e414d, NS_LITERAL_CSTRING("name") }, // INAM 1.569 + }; 1.570 + 1.571 + const char* const end = chunk.get() + aChunkSize; 1.572 + 1.573 + aTags = new dom::HTMLMediaElement::MetadataTags; 1.574 + 1.575 + while (p + 8 < end) { 1.576 + uint32_t id = ReadUint32BE(&p); 1.577 + // Uppercase tag id, inspired by GStreamer's Wave parser. 1.578 + id &= 0xDFDFDFDF; 1.579 + 1.580 + uint32_t length = ReadUint32LE(&p); 1.581 + 1.582 + // Subchunk shall not exceed parent chunk. 1.583 + if (p + length > end) { 1.584 + break; 1.585 + } 1.586 + 1.587 + // Wrap the string, adjusting length to account for optional 1.588 + // null termination in the chunk. 1.589 + nsCString val(p, length); 1.590 + if (length > 0 && val[length - 1] == '\0') { 1.591 + val.SetLength(length - 1); 1.592 + } 1.593 + 1.594 + // Chunks in List::INFO are always word (two byte) aligned. So round up if 1.595 + // necessary. 1.596 + length += length % 2; 1.597 + p += length; 1.598 + 1.599 + if (!IsUTF8(val)) { 1.600 + continue; 1.601 + } 1.602 + 1.603 + for (size_t i = 0; i < mozilla::ArrayLength(ID_TO_NAME); ++i) { 1.604 + if (id == ID_TO_NAME[i].id) { 1.605 + aTags->Put(ID_TO_NAME[i].name, val); 1.606 + break; 1.607 + } 1.608 + } 1.609 + } 1.610 + 1.611 + return true; 1.612 +} 1.613 + 1.614 +bool 1.615 +WaveReader::LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags) 1.616 +{ 1.617 + // Chunks are always word (two byte) aligned. 1.618 + NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0, 1.619 + "LoadAllChunks called with unaligned resource"); 1.620 + 1.621 + bool loadFormatChunk = false; 1.622 + bool findDataOffset = false; 1.623 + 1.624 + for (;;) { 1.625 + static const unsigned int CHUNK_HEADER_SIZE = 8; 1.626 + char chunkHeader[CHUNK_HEADER_SIZE]; 1.627 + const char* p = chunkHeader; 1.628 + 1.629 + if (!ReadAll(chunkHeader, sizeof(chunkHeader))) { 1.630 + return false; 1.631 + } 1.632 + 1.633 + static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE, 1.634 + "Reads would overflow chunkHeader buffer."); 1.635 + 1.636 + uint32_t magic = ReadUint32BE(&p); 1.637 + uint32_t chunkSize = ReadUint32LE(&p); 1.638 + int64_t chunkStart = GetPosition(); 1.639 + 1.640 + switch (magic) { 1.641 + case FRMT_CHUNK_MAGIC: 1.642 + loadFormatChunk = LoadFormatChunk(chunkSize); 1.643 + if (!loadFormatChunk) { 1.644 + return false; 1.645 + } 1.646 + break; 1.647 + 1.648 + case LIST_CHUNK_MAGIC: 1.649 + if (!aTags) { 1.650 + LoadListChunk(chunkSize, aTags); 1.651 + } 1.652 + break; 1.653 + 1.654 + case DATA_CHUNK_MAGIC: 1.655 + findDataOffset = FindDataOffset(chunkSize); 1.656 + return loadFormatChunk && findDataOffset; 1.657 + 1.658 + default: 1.659 + break; 1.660 + } 1.661 + 1.662 + // RIFF chunks are two-byte aligned, so round up if necessary. 1.663 + chunkSize += chunkSize % 2; 1.664 + 1.665 + // Move forward to next chunk 1.666 + CheckedInt64 forward = CheckedInt64(chunkStart) + chunkSize - GetPosition(); 1.667 + 1.668 + if (!forward.isValid() || forward.value() < 0) { 1.669 + return false; 1.670 + } 1.671 + 1.672 + static const int64_t MAX_CHUNK_SIZE = 1 << 16; 1.673 + static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char), 1.674 + "MAX_CHUNK_SIZE too large for enumerator."); 1.675 + nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]); 1.676 + while (forward.value() > 0) { 1.677 + int64_t size = std::min(forward.value(), MAX_CHUNK_SIZE); 1.678 + if (!ReadAll(chunk.get(), size)) { 1.679 + return false; 1.680 + } 1.681 + forward -= size; 1.682 + } 1.683 + } 1.684 + 1.685 + return false; 1.686 +} 1.687 + 1.688 +} // namespace mozilla