content/media/wave/WaveReader.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6 #include "nsError.h"
michael@0 7 #include "AbstractMediaDecoder.h"
michael@0 8 #include "MediaResource.h"
michael@0 9 #include "WaveReader.h"
michael@0 10 #include "mozilla/dom/TimeRanges.h"
michael@0 11 #include "MediaDecoderStateMachine.h"
michael@0 12 #include "VideoUtils.h"
michael@0 13 #include "nsISeekableStream.h"
michael@0 14
michael@0 15 #include <stdint.h>
michael@0 16 #include "mozilla/ArrayUtils.h"
michael@0 17 #include "mozilla/CheckedInt.h"
michael@0 18 #include "mozilla/Endian.h"
michael@0 19 #include <algorithm>
michael@0 20
michael@0 21 namespace mozilla {
michael@0 22
michael@0 23 // Un-comment to enable logging of seek bisections.
michael@0 24 //#define SEEK_LOGGING
michael@0 25
michael@0 26 #ifdef PR_LOGGING
michael@0 27 extern PRLogModuleInfo* gMediaDecoderLog;
michael@0 28 #define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
michael@0 29 #ifdef SEEK_LOGGING
michael@0 30 #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
michael@0 31 #else
michael@0 32 #define SEEK_LOG(type, msg)
michael@0 33 #endif
michael@0 34 #else
michael@0 35 #define LOG(type, msg)
michael@0 36 #define SEEK_LOG(type, msg)
michael@0 37 #endif
michael@0 38
michael@0 39 struct waveIdToName {
michael@0 40 uint32_t id;
michael@0 41 nsCString name;
michael@0 42 };
michael@0 43
michael@0 44
michael@0 45 // Magic values that identify RIFF chunks we're interested in.
michael@0 46 static const uint32_t RIFF_CHUNK_MAGIC = 0x52494646;
michael@0 47 static const uint32_t WAVE_CHUNK_MAGIC = 0x57415645;
michael@0 48 static const uint32_t FRMT_CHUNK_MAGIC = 0x666d7420;
michael@0 49 static const uint32_t DATA_CHUNK_MAGIC = 0x64617461;
michael@0 50 static const uint32_t LIST_CHUNK_MAGIC = 0x4c495354;
michael@0 51
michael@0 52 // Size of chunk header. 4 byte chunk header type and 4 byte size field.
michael@0 53 static const uint16_t CHUNK_HEADER_SIZE = 8;
michael@0 54
michael@0 55 // Size of RIFF header. RIFF chunk and 4 byte RIFF type.
michael@0 56 static const uint16_t RIFF_INITIAL_SIZE = CHUNK_HEADER_SIZE + 4;
michael@0 57
michael@0 58 // Size of required part of format chunk. Actual format chunks may be
michael@0 59 // extended (for non-PCM encodings), but we skip any extended data.
michael@0 60 static const uint16_t WAVE_FORMAT_CHUNK_SIZE = 16;
michael@0 61
michael@0 62 // PCM encoding type from format chunk. Linear PCM is the only encoding
michael@0 63 // supported by AudioStream.
michael@0 64 static const uint16_t WAVE_FORMAT_ENCODING_PCM = 1;
michael@0 65
michael@0 66 // We reject files with more than this number of channels if we're decoding for
michael@0 67 // playback.
michael@0 68 static const uint8_t MAX_CHANNELS = 2;
michael@0 69
michael@0 70 namespace {
michael@0 71 uint32_t
michael@0 72 ReadUint32BE(const char** aBuffer)
michael@0 73 {
michael@0 74 uint32_t result = BigEndian::readUint32(*aBuffer);
michael@0 75 *aBuffer += sizeof(uint32_t);
michael@0 76 return result;
michael@0 77 }
michael@0 78
michael@0 79 uint32_t
michael@0 80 ReadUint32LE(const char** aBuffer)
michael@0 81 {
michael@0 82 uint32_t result = LittleEndian::readUint32(*aBuffer);
michael@0 83 *aBuffer += sizeof(uint32_t);
michael@0 84 return result;
michael@0 85 }
michael@0 86
michael@0 87 uint16_t
michael@0 88 ReadUint16LE(const char** aBuffer)
michael@0 89 {
michael@0 90 uint16_t result = LittleEndian::readUint16(*aBuffer);
michael@0 91 *aBuffer += sizeof(uint16_t);
michael@0 92 return result;
michael@0 93 }
michael@0 94
michael@0 95 int16_t
michael@0 96 ReadInt16LE(const char** aBuffer)
michael@0 97 {
michael@0 98 uint16_t result = LittleEndian::readInt16(*aBuffer);
michael@0 99 *aBuffer += sizeof(int16_t);
michael@0 100 return result;
michael@0 101 }
michael@0 102
michael@0 103 uint8_t
michael@0 104 ReadUint8(const char** aBuffer)
michael@0 105 {
michael@0 106 uint8_t result = uint8_t((*aBuffer)[0]);
michael@0 107 *aBuffer += sizeof(uint8_t);
michael@0 108 return result;
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 WaveReader::WaveReader(AbstractMediaDecoder* aDecoder)
michael@0 113 : MediaDecoderReader(aDecoder)
michael@0 114 {
michael@0 115 MOZ_COUNT_CTOR(WaveReader);
michael@0 116 }
michael@0 117
michael@0 118 WaveReader::~WaveReader()
michael@0 119 {
michael@0 120 MOZ_COUNT_DTOR(WaveReader);
michael@0 121 }
michael@0 122
michael@0 123 nsresult WaveReader::Init(MediaDecoderReader* aCloneDonor)
michael@0 124 {
michael@0 125 return NS_OK;
michael@0 126 }
michael@0 127
michael@0 128 nsresult WaveReader::ReadMetadata(MediaInfo* aInfo,
michael@0 129 MetadataTags** aTags)
michael@0 130 {
michael@0 131 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 132
michael@0 133 bool loaded = LoadRIFFChunk();
michael@0 134 if (!loaded) {
michael@0 135 return NS_ERROR_FAILURE;
michael@0 136 }
michael@0 137
michael@0 138 nsAutoPtr<dom::HTMLMediaElement::MetadataTags> tags;
michael@0 139
michael@0 140 bool loadAllChunks = LoadAllChunks(tags);
michael@0 141 if (!loadAllChunks) {
michael@0 142 return NS_ERROR_FAILURE;
michael@0 143 }
michael@0 144
michael@0 145 mInfo.mAudio.mHasAudio = true;
michael@0 146 mInfo.mAudio.mRate = mSampleRate;
michael@0 147 mInfo.mAudio.mChannels = mChannels;
michael@0 148
michael@0 149 *aInfo = mInfo;
michael@0 150
michael@0 151 *aTags = tags.forget();
michael@0 152
michael@0 153 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 154
michael@0 155 mDecoder->SetMediaDuration(
michael@0 156 static_cast<int64_t>(BytesToTime(GetDataLength()) * USECS_PER_S));
michael@0 157
michael@0 158 return NS_OK;
michael@0 159 }
michael@0 160
michael@0 161 template <typename T> T UnsignedByteToAudioSample(uint8_t aValue);
michael@0 162 template <typename T> T SignedShortToAudioSample(int16_t aValue);
michael@0 163
michael@0 164 template <> inline float
michael@0 165 UnsignedByteToAudioSample<float>(uint8_t aValue)
michael@0 166 {
michael@0 167 return aValue * (2.0f / UINT8_MAX) - 1.0f;
michael@0 168 }
michael@0 169 template <> inline int16_t
michael@0 170 UnsignedByteToAudioSample<int16_t>(uint8_t aValue)
michael@0 171 {
michael@0 172 return int16_t(aValue * UINT16_MAX / UINT8_MAX + INT16_MIN);
michael@0 173 }
michael@0 174
michael@0 175 template <> inline float
michael@0 176 SignedShortToAudioSample<float>(int16_t aValue)
michael@0 177 {
michael@0 178 return AudioSampleToFloat(aValue);
michael@0 179 }
michael@0 180 template <> inline int16_t
michael@0 181 SignedShortToAudioSample<int16_t>(int16_t aValue)
michael@0 182 {
michael@0 183 return aValue;
michael@0 184 }
michael@0 185
michael@0 186 bool WaveReader::DecodeAudioData()
michael@0 187 {
michael@0 188 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 189
michael@0 190 int64_t pos = GetPosition() - mWavePCMOffset;
michael@0 191 int64_t len = GetDataLength();
michael@0 192 int64_t remaining = len - pos;
michael@0 193 NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length");
michael@0 194
michael@0 195 static const int64_t BLOCK_SIZE = 4096;
michael@0 196 int64_t readSize = std::min(BLOCK_SIZE, remaining);
michael@0 197 int64_t frames = readSize / mFrameSize;
michael@0 198
michael@0 199 static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX /
michael@0 200 sizeof(AudioDataValue) / MAX_CHANNELS,
michael@0 201 "bufferSize calculation could overflow.");
michael@0 202 const size_t bufferSize = static_cast<size_t>(frames * mChannels);
michael@0 203 nsAutoArrayPtr<AudioDataValue> sampleBuffer(new AudioDataValue[bufferSize]);
michael@0 204
michael@0 205 static_assert(uint64_t(BLOCK_SIZE) < UINT_MAX / sizeof(char),
michael@0 206 "BLOCK_SIZE too large for enumerator.");
michael@0 207 nsAutoArrayPtr<char> dataBuffer(new char[static_cast<size_t>(readSize)]);
michael@0 208
michael@0 209 if (!ReadAll(dataBuffer, readSize)) {
michael@0 210 return false;
michael@0 211 }
michael@0 212
michael@0 213 // convert data to samples
michael@0 214 const char* d = dataBuffer.get();
michael@0 215 AudioDataValue* s = sampleBuffer.get();
michael@0 216 for (int i = 0; i < frames; ++i) {
michael@0 217 for (unsigned int j = 0; j < mChannels; ++j) {
michael@0 218 if (mSampleFormat == FORMAT_U8) {
michael@0 219 uint8_t v = ReadUint8(&d);
michael@0 220 *s++ = UnsignedByteToAudioSample<AudioDataValue>(v);
michael@0 221 } else if (mSampleFormat == FORMAT_S16) {
michael@0 222 int16_t v = ReadInt16LE(&d);
michael@0 223 *s++ = SignedShortToAudioSample<AudioDataValue>(v);
michael@0 224 }
michael@0 225 }
michael@0 226 }
michael@0 227
michael@0 228 double posTime = BytesToTime(pos);
michael@0 229 double readSizeTime = BytesToTime(readSize);
michael@0 230 NS_ASSERTION(posTime <= INT64_MAX / USECS_PER_S, "posTime overflow");
michael@0 231 NS_ASSERTION(readSizeTime <= INT64_MAX / USECS_PER_S, "readSizeTime overflow");
michael@0 232 NS_ASSERTION(frames < INT32_MAX, "frames overflow");
michael@0 233
michael@0 234 mAudioQueue.Push(new AudioData(pos,
michael@0 235 static_cast<int64_t>(posTime * USECS_PER_S),
michael@0 236 static_cast<int64_t>(readSizeTime * USECS_PER_S),
michael@0 237 static_cast<int32_t>(frames),
michael@0 238 sampleBuffer.forget(),
michael@0 239 mChannels));
michael@0 240
michael@0 241 return true;
michael@0 242 }
michael@0 243
michael@0 244 bool WaveReader::DecodeVideoFrame(bool &aKeyframeSkip,
michael@0 245 int64_t aTimeThreshold)
michael@0 246 {
michael@0 247 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 248
michael@0 249 return false;
michael@0 250 }
michael@0 251
michael@0 252 nsresult WaveReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime)
michael@0 253 {
michael@0 254 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 255 LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
michael@0 256 if (NS_FAILED(ResetDecode())) {
michael@0 257 return NS_ERROR_FAILURE;
michael@0 258 }
michael@0 259 double d = BytesToTime(GetDataLength());
michael@0 260 NS_ASSERTION(d < INT64_MAX / USECS_PER_S, "Duration overflow");
michael@0 261 int64_t duration = static_cast<int64_t>(d * USECS_PER_S);
michael@0 262 double seekTime = std::min(aTarget, duration) / static_cast<double>(USECS_PER_S);
michael@0 263 int64_t position = RoundDownToFrame(static_cast<int64_t>(TimeToBytes(seekTime)));
michael@0 264 NS_ASSERTION(INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek");
michael@0 265 position += mWavePCMOffset;
michael@0 266 return mDecoder->GetResource()->Seek(nsISeekableStream::NS_SEEK_SET, position);
michael@0 267 }
michael@0 268
michael@0 269 static double RoundToUsecs(double aSeconds) {
michael@0 270 return floor(aSeconds * USECS_PER_S) / USECS_PER_S;
michael@0 271 }
michael@0 272
michael@0 273 nsresult WaveReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
michael@0 274 {
michael@0 275 if (!mInfo.HasAudio()) {
michael@0 276 return NS_OK;
michael@0 277 }
michael@0 278 int64_t startOffset = mDecoder->GetResource()->GetNextCachedData(mWavePCMOffset);
michael@0 279 while (startOffset >= 0) {
michael@0 280 int64_t endOffset = mDecoder->GetResource()->GetCachedDataEnd(startOffset);
michael@0 281 // Bytes [startOffset..endOffset] are cached.
michael@0 282 NS_ASSERTION(startOffset >= mWavePCMOffset, "Integer underflow in GetBuffered");
michael@0 283 NS_ASSERTION(endOffset >= mWavePCMOffset, "Integer underflow in GetBuffered");
michael@0 284
michael@0 285 // We need to round the buffered ranges' times to microseconds so that they
michael@0 286 // have the same precision as the currentTime and duration attribute on
michael@0 287 // the media element.
michael@0 288 aBuffered->Add(RoundToUsecs(BytesToTime(startOffset - mWavePCMOffset)),
michael@0 289 RoundToUsecs(BytesToTime(endOffset - mWavePCMOffset)));
michael@0 290 startOffset = mDecoder->GetResource()->GetNextCachedData(endOffset);
michael@0 291 }
michael@0 292 return NS_OK;
michael@0 293 }
michael@0 294
michael@0 295 bool
michael@0 296 WaveReader::ReadAll(char* aBuf, int64_t aSize, int64_t* aBytesRead)
michael@0 297 {
michael@0 298 uint32_t got = 0;
michael@0 299 if (aBytesRead) {
michael@0 300 *aBytesRead = 0;
michael@0 301 }
michael@0 302 do {
michael@0 303 uint32_t read = 0;
michael@0 304 if (NS_FAILED(mDecoder->GetResource()->Read(aBuf + got, uint32_t(aSize - got), &read))) {
michael@0 305 NS_WARNING("Resource read failed");
michael@0 306 return false;
michael@0 307 }
michael@0 308 if (read == 0) {
michael@0 309 return false;
michael@0 310 }
michael@0 311 got += read;
michael@0 312 if (aBytesRead) {
michael@0 313 *aBytesRead = got;
michael@0 314 }
michael@0 315 } while (got != aSize);
michael@0 316 return true;
michael@0 317 }
michael@0 318
michael@0 319 bool
michael@0 320 WaveReader::LoadRIFFChunk()
michael@0 321 {
michael@0 322 char riffHeader[RIFF_INITIAL_SIZE];
michael@0 323 const char* p = riffHeader;
michael@0 324
michael@0 325 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() == 0,
michael@0 326 "LoadRIFFChunk called when resource in invalid state");
michael@0 327
michael@0 328 if (!ReadAll(riffHeader, sizeof(riffHeader))) {
michael@0 329 return false;
michael@0 330 }
michael@0 331
michael@0 332 static_assert(sizeof(uint32_t) * 3 <= RIFF_INITIAL_SIZE,
michael@0 333 "Reads would overflow riffHeader buffer.");
michael@0 334 if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) {
michael@0 335 NS_WARNING("resource data not in RIFF format");
michael@0 336 return false;
michael@0 337 }
michael@0 338
michael@0 339 // Skip over RIFF size field.
michael@0 340 p += sizeof(uint32_t);
michael@0 341
michael@0 342 if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) {
michael@0 343 NS_WARNING("Expected WAVE chunk");
michael@0 344 return false;
michael@0 345 }
michael@0 346
michael@0 347 return true;
michael@0 348 }
michael@0 349
michael@0 350 bool
michael@0 351 WaveReader::LoadFormatChunk(uint32_t aChunkSize)
michael@0 352 {
michael@0 353 uint32_t rate, channels, frameSize, sampleFormat;
michael@0 354 char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
michael@0 355 const char* p = waveFormat;
michael@0 356
michael@0 357 // RIFF chunks are always word (two byte) aligned.
michael@0 358 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 359 "LoadFormatChunk called with unaligned resource");
michael@0 360
michael@0 361 if (!ReadAll(waveFormat, sizeof(waveFormat))) {
michael@0 362 return false;
michael@0 363 }
michael@0 364
michael@0 365 static_assert(sizeof(uint16_t) +
michael@0 366 sizeof(uint16_t) +
michael@0 367 sizeof(uint32_t) +
michael@0 368 4 +
michael@0 369 sizeof(uint16_t) +
michael@0 370 sizeof(uint16_t) <= sizeof(waveFormat),
michael@0 371 "Reads would overflow waveFormat buffer.");
michael@0 372 if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) {
michael@0 373 NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported");
michael@0 374 return false;
michael@0 375 }
michael@0 376
michael@0 377 channels = ReadUint16LE(&p);
michael@0 378 rate = ReadUint32LE(&p);
michael@0 379
michael@0 380 // Skip over average bytes per second field.
michael@0 381 p += 4;
michael@0 382
michael@0 383 frameSize = ReadUint16LE(&p);
michael@0 384
michael@0 385 sampleFormat = ReadUint16LE(&p);
michael@0 386
michael@0 387 // PCM encoded WAVEs are not expected to have an extended "format" chunk,
michael@0 388 // but I have found WAVEs that have a extended "format" chunk with an
michael@0 389 // extension size of 0 bytes. Be polite and handle this rather than
michael@0 390 // considering the file invalid. This code skips any extension of the
michael@0 391 // "format" chunk.
michael@0 392 if (aChunkSize > WAVE_FORMAT_CHUNK_SIZE) {
michael@0 393 char extLength[2];
michael@0 394 const char* p = extLength;
michael@0 395
michael@0 396 if (!ReadAll(extLength, sizeof(extLength))) {
michael@0 397 return false;
michael@0 398 }
michael@0 399
michael@0 400 static_assert(sizeof(uint16_t) <= sizeof(extLength),
michael@0 401 "Reads would overflow extLength buffer.");
michael@0 402 uint16_t extra = ReadUint16LE(&p);
michael@0 403 if (aChunkSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
michael@0 404 NS_WARNING("Invalid extended format chunk size");
michael@0 405 return false;
michael@0 406 }
michael@0 407 extra += extra % 2;
michael@0 408
michael@0 409 if (extra > 0) {
michael@0 410 static_assert(UINT16_MAX + (UINT16_MAX % 2) < UINT_MAX / sizeof(char),
michael@0 411 "chunkExtension array too large for iterator.");
michael@0 412 nsAutoArrayPtr<char> chunkExtension(new char[extra]);
michael@0 413 if (!ReadAll(chunkExtension.get(), extra)) {
michael@0 414 return false;
michael@0 415 }
michael@0 416 }
michael@0 417 }
michael@0 418
michael@0 419 // RIFF chunks are always word (two byte) aligned.
michael@0 420 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 421 "LoadFormatChunk left resource unaligned");
michael@0 422
michael@0 423 // Make sure metadata is fairly sane. The rate check is fairly arbitrary,
michael@0 424 // but the channels check is intentionally limited to mono or stereo
michael@0 425 // when the media is intended for direct playback because that's what the
michael@0 426 // audio backend currently supports.
michael@0 427 unsigned int actualFrameSize = (sampleFormat == 8 ? 1 : 2) * channels;
michael@0 428 if (rate < 100 || rate > 96000 ||
michael@0 429 (((channels < 1 || channels > MAX_CHANNELS) ||
michael@0 430 (frameSize != 1 && frameSize != 2 && frameSize != 4)) &&
michael@0 431 !mIgnoreAudioOutputFormat) ||
michael@0 432 (sampleFormat != 8 && sampleFormat != 16) ||
michael@0 433 frameSize != actualFrameSize) {
michael@0 434 NS_WARNING("Invalid WAVE metadata");
michael@0 435 return false;
michael@0 436 }
michael@0 437
michael@0 438 ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor());
michael@0 439 mSampleRate = rate;
michael@0 440 mChannels = channels;
michael@0 441 mFrameSize = frameSize;
michael@0 442 if (sampleFormat == 8) {
michael@0 443 mSampleFormat = FORMAT_U8;
michael@0 444 } else {
michael@0 445 mSampleFormat = FORMAT_S16;
michael@0 446 }
michael@0 447 return true;
michael@0 448 }
michael@0 449
michael@0 450 bool
michael@0 451 WaveReader::FindDataOffset(uint32_t aChunkSize)
michael@0 452 {
michael@0 453 // RIFF chunks are always word (two byte) aligned.
michael@0 454 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 455 "FindDataOffset called with unaligned resource");
michael@0 456
michael@0 457 int64_t offset = mDecoder->GetResource()->Tell();
michael@0 458 if (offset <= 0 || offset > UINT32_MAX) {
michael@0 459 NS_WARNING("PCM data offset out of range");
michael@0 460 return false;
michael@0 461 }
michael@0 462
michael@0 463 ReentrantMonitorAutoEnter monitor(mDecoder->GetReentrantMonitor());
michael@0 464 mWaveLength = aChunkSize;
michael@0 465 mWavePCMOffset = uint32_t(offset);
michael@0 466 return true;
michael@0 467 }
michael@0 468
michael@0 469 double
michael@0 470 WaveReader::BytesToTime(int64_t aBytes) const
michael@0 471 {
michael@0 472 NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
michael@0 473 return float(aBytes) / mSampleRate / mFrameSize;
michael@0 474 }
michael@0 475
michael@0 476 int64_t
michael@0 477 WaveReader::TimeToBytes(double aTime) const
michael@0 478 {
michael@0 479 NS_ABORT_IF_FALSE(aTime >= 0.0f, "Must be >= 0");
michael@0 480 return RoundDownToFrame(int64_t(aTime * mSampleRate * mFrameSize));
michael@0 481 }
michael@0 482
michael@0 483 int64_t
michael@0 484 WaveReader::RoundDownToFrame(int64_t aBytes) const
michael@0 485 {
michael@0 486 NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
michael@0 487 return aBytes - (aBytes % mFrameSize);
michael@0 488 }
michael@0 489
michael@0 490 int64_t
michael@0 491 WaveReader::GetDataLength()
michael@0 492 {
michael@0 493 int64_t length = mWaveLength;
michael@0 494 // If the decoder has a valid content length, and it's shorter than the
michael@0 495 // expected length of the PCM data, calculate the playback duration from
michael@0 496 // the content length rather than the expected PCM data length.
michael@0 497 int64_t streamLength = mDecoder->GetResource()->GetLength();
michael@0 498 if (streamLength >= 0) {
michael@0 499 int64_t dataLength = std::max<int64_t>(0, streamLength - mWavePCMOffset);
michael@0 500 length = std::min(dataLength, length);
michael@0 501 }
michael@0 502 return length;
michael@0 503 }
michael@0 504
michael@0 505 int64_t
michael@0 506 WaveReader::GetPosition()
michael@0 507 {
michael@0 508 return mDecoder->GetResource()->Tell();
michael@0 509 }
michael@0 510
michael@0 511 bool
michael@0 512 WaveReader::GetNextChunk(uint32_t* aChunk, uint32_t* aChunkSize)
michael@0 513 {
michael@0 514 NS_ABORT_IF_FALSE(aChunk, "Must have aChunk");
michael@0 515 NS_ABORT_IF_FALSE(aChunkSize, "Must have aChunkSize");
michael@0 516 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 517 "GetNextChunk called with unaligned resource");
michael@0 518
michael@0 519 char chunkHeader[CHUNK_HEADER_SIZE];
michael@0 520 const char* p = chunkHeader;
michael@0 521
michael@0 522 if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
michael@0 523 return false;
michael@0 524 }
michael@0 525
michael@0 526 static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE,
michael@0 527 "Reads would overflow chunkHeader buffer.");
michael@0 528 *aChunk = ReadUint32BE(&p);
michael@0 529 *aChunkSize = ReadUint32LE(&p);
michael@0 530
michael@0 531 return true;
michael@0 532 }
michael@0 533
michael@0 534 bool
michael@0 535 WaveReader::LoadListChunk(uint32_t aChunkSize,
michael@0 536 nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
michael@0 537 {
michael@0 538 // List chunks are always word (two byte) aligned.
michael@0 539 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 540 "LoadListChunk called with unaligned resource");
michael@0 541
michael@0 542 static const unsigned int MAX_CHUNK_SIZE = 1 << 16;
michael@0 543 static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char),
michael@0 544 "MAX_CHUNK_SIZE too large for enumerator.");
michael@0 545
michael@0 546 if (aChunkSize > MAX_CHUNK_SIZE) {
michael@0 547 return false;
michael@0 548 }
michael@0 549
michael@0 550 nsAutoArrayPtr<char> chunk(new char[aChunkSize]);
michael@0 551 if (!ReadAll(chunk.get(), aChunkSize)) {
michael@0 552 return false;
michael@0 553 }
michael@0 554
michael@0 555 static const uint32_t INFO_LIST_MAGIC = 0x494e464f;
michael@0 556 const char *p = chunk.get();
michael@0 557 if (ReadUint32BE(&p) != INFO_LIST_MAGIC) {
michael@0 558 return false;
michael@0 559 }
michael@0 560
michael@0 561 const waveIdToName ID_TO_NAME[] = {
michael@0 562 { 0x49415254, NS_LITERAL_CSTRING("artist") }, // IART
michael@0 563 { 0x49434d54, NS_LITERAL_CSTRING("comments") }, // ICMT
michael@0 564 { 0x49474e52, NS_LITERAL_CSTRING("genre") }, // IGNR
michael@0 565 { 0x494e414d, NS_LITERAL_CSTRING("name") }, // INAM
michael@0 566 };
michael@0 567
michael@0 568 const char* const end = chunk.get() + aChunkSize;
michael@0 569
michael@0 570 aTags = new dom::HTMLMediaElement::MetadataTags;
michael@0 571
michael@0 572 while (p + 8 < end) {
michael@0 573 uint32_t id = ReadUint32BE(&p);
michael@0 574 // Uppercase tag id, inspired by GStreamer's Wave parser.
michael@0 575 id &= 0xDFDFDFDF;
michael@0 576
michael@0 577 uint32_t length = ReadUint32LE(&p);
michael@0 578
michael@0 579 // Subchunk shall not exceed parent chunk.
michael@0 580 if (p + length > end) {
michael@0 581 break;
michael@0 582 }
michael@0 583
michael@0 584 // Wrap the string, adjusting length to account for optional
michael@0 585 // null termination in the chunk.
michael@0 586 nsCString val(p, length);
michael@0 587 if (length > 0 && val[length - 1] == '\0') {
michael@0 588 val.SetLength(length - 1);
michael@0 589 }
michael@0 590
michael@0 591 // Chunks in List::INFO are always word (two byte) aligned. So round up if
michael@0 592 // necessary.
michael@0 593 length += length % 2;
michael@0 594 p += length;
michael@0 595
michael@0 596 if (!IsUTF8(val)) {
michael@0 597 continue;
michael@0 598 }
michael@0 599
michael@0 600 for (size_t i = 0; i < mozilla::ArrayLength(ID_TO_NAME); ++i) {
michael@0 601 if (id == ID_TO_NAME[i].id) {
michael@0 602 aTags->Put(ID_TO_NAME[i].name, val);
michael@0 603 break;
michael@0 604 }
michael@0 605 }
michael@0 606 }
michael@0 607
michael@0 608 return true;
michael@0 609 }
michael@0 610
michael@0 611 bool
michael@0 612 WaveReader::LoadAllChunks(nsAutoPtr<dom::HTMLMediaElement::MetadataTags> &aTags)
michael@0 613 {
michael@0 614 // Chunks are always word (two byte) aligned.
michael@0 615 NS_ABORT_IF_FALSE(mDecoder->GetResource()->Tell() % 2 == 0,
michael@0 616 "LoadAllChunks called with unaligned resource");
michael@0 617
michael@0 618 bool loadFormatChunk = false;
michael@0 619 bool findDataOffset = false;
michael@0 620
michael@0 621 for (;;) {
michael@0 622 static const unsigned int CHUNK_HEADER_SIZE = 8;
michael@0 623 char chunkHeader[CHUNK_HEADER_SIZE];
michael@0 624 const char* p = chunkHeader;
michael@0 625
michael@0 626 if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
michael@0 627 return false;
michael@0 628 }
michael@0 629
michael@0 630 static_assert(sizeof(uint32_t) * 2 <= CHUNK_HEADER_SIZE,
michael@0 631 "Reads would overflow chunkHeader buffer.");
michael@0 632
michael@0 633 uint32_t magic = ReadUint32BE(&p);
michael@0 634 uint32_t chunkSize = ReadUint32LE(&p);
michael@0 635 int64_t chunkStart = GetPosition();
michael@0 636
michael@0 637 switch (magic) {
michael@0 638 case FRMT_CHUNK_MAGIC:
michael@0 639 loadFormatChunk = LoadFormatChunk(chunkSize);
michael@0 640 if (!loadFormatChunk) {
michael@0 641 return false;
michael@0 642 }
michael@0 643 break;
michael@0 644
michael@0 645 case LIST_CHUNK_MAGIC:
michael@0 646 if (!aTags) {
michael@0 647 LoadListChunk(chunkSize, aTags);
michael@0 648 }
michael@0 649 break;
michael@0 650
michael@0 651 case DATA_CHUNK_MAGIC:
michael@0 652 findDataOffset = FindDataOffset(chunkSize);
michael@0 653 return loadFormatChunk && findDataOffset;
michael@0 654
michael@0 655 default:
michael@0 656 break;
michael@0 657 }
michael@0 658
michael@0 659 // RIFF chunks are two-byte aligned, so round up if necessary.
michael@0 660 chunkSize += chunkSize % 2;
michael@0 661
michael@0 662 // Move forward to next chunk
michael@0 663 CheckedInt64 forward = CheckedInt64(chunkStart) + chunkSize - GetPosition();
michael@0 664
michael@0 665 if (!forward.isValid() || forward.value() < 0) {
michael@0 666 return false;
michael@0 667 }
michael@0 668
michael@0 669 static const int64_t MAX_CHUNK_SIZE = 1 << 16;
michael@0 670 static_assert(uint64_t(MAX_CHUNK_SIZE) < UINT_MAX / sizeof(char),
michael@0 671 "MAX_CHUNK_SIZE too large for enumerator.");
michael@0 672 nsAutoArrayPtr<char> chunk(new char[MAX_CHUNK_SIZE]);
michael@0 673 while (forward.value() > 0) {
michael@0 674 int64_t size = std::min(forward.value(), MAX_CHUNK_SIZE);
michael@0 675 if (!ReadAll(chunk.get(), size)) {
michael@0 676 return false;
michael@0 677 }
michael@0 678 forward -= size;
michael@0 679 }
michael@0 680 }
michael@0 681
michael@0 682 return false;
michael@0 683 }
michael@0 684
michael@0 685 } // namespace mozilla

mercurial