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