Line data Source code
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 :
7 : #include "nsAlgorithm.h"
8 : #include "WebMBufferedParser.h"
9 : #include "nsThreadUtils.h"
10 : #include <algorithm>
11 :
12 : extern mozilla::LazyLogModule gMediaDemuxerLog;
13 : #define WEBM_DEBUG(arg, ...) MOZ_LOG(gMediaDemuxerLog, mozilla::LogLevel::Debug, ("WebMBufferedParser(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
14 :
15 : namespace mozilla {
16 :
17 : static uint32_t
18 0 : VIntLength(unsigned char aFirstByte, uint32_t* aMask)
19 : {
20 0 : uint32_t count = 1;
21 0 : uint32_t mask = 1 << 7;
22 0 : while (count < 8) {
23 0 : if ((aFirstByte & mask) != 0) {
24 0 : break;
25 : }
26 0 : mask >>= 1;
27 0 : count += 1;
28 : }
29 0 : if (aMask) {
30 0 : *aMask = mask;
31 : }
32 0 : NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
33 0 : return count;
34 : }
35 :
36 0 : bool WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
37 : nsTArray<WebMTimeDataOffset>& aMapping,
38 : ReentrantMonitor& aReentrantMonitor)
39 : {
40 : static const uint32_t EBML_ID = 0x1a45dfa3;
41 : static const uint32_t SEGMENT_ID = 0x18538067;
42 : static const uint32_t SEGINFO_ID = 0x1549a966;
43 : static const uint32_t TRACKS_ID = 0x1654AE6B;
44 : static const uint32_t CLUSTER_ID = 0x1f43b675;
45 : static const uint32_t TIMECODESCALE_ID = 0x2ad7b1;
46 : static const unsigned char TIMECODE_ID = 0xe7;
47 : static const unsigned char BLOCKGROUP_ID = 0xa0;
48 : static const unsigned char BLOCK_ID = 0xa1;
49 : static const unsigned char SIMPLEBLOCK_ID = 0xa3;
50 : static const uint32_t BLOCK_TIMECODE_LENGTH = 2;
51 :
52 : static const unsigned char CLUSTER_SYNC_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
53 :
54 0 : const unsigned char* p = aBuffer;
55 :
56 : // Parse each byte in aBuffer one-by-one, producing timecodes and updating
57 : // aMapping as we go. Parser pauses at end of stream (which may be at any
58 : // point within the parse) and resumes parsing the next time Append is
59 : // called with new data.
60 0 : while (p < aBuffer + aLength) {
61 0 : switch (mState) {
62 : case READ_ELEMENT_ID:
63 0 : mVIntRaw = true;
64 0 : mState = READ_VINT;
65 0 : mNextState = READ_ELEMENT_SIZE;
66 0 : break;
67 : case READ_ELEMENT_SIZE:
68 0 : mVIntRaw = false;
69 0 : mElement.mID = mVInt;
70 0 : mState = READ_VINT;
71 0 : mNextState = PARSE_ELEMENT;
72 0 : break;
73 : case FIND_CLUSTER_SYNC:
74 0 : if (*p++ == CLUSTER_SYNC_ID[mClusterSyncPos]) {
75 0 : mClusterSyncPos += 1;
76 : } else {
77 0 : mClusterSyncPos = 0;
78 : }
79 0 : if (mClusterSyncPos == sizeof(CLUSTER_SYNC_ID)) {
80 0 : mVInt.mValue = CLUSTER_ID;
81 0 : mVInt.mLength = sizeof(CLUSTER_SYNC_ID);
82 0 : mState = READ_ELEMENT_SIZE;
83 : }
84 0 : break;
85 : case PARSE_ELEMENT:
86 0 : mElement.mSize = mVInt;
87 0 : switch (mElement.mID.mValue) {
88 : case SEGMENT_ID:
89 0 : mState = READ_ELEMENT_ID;
90 0 : break;
91 : case SEGINFO_ID:
92 0 : mGotTimecodeScale = true;
93 0 : mState = READ_ELEMENT_ID;
94 0 : break;
95 : case TIMECODE_ID:
96 0 : mVInt = VInt();
97 0 : mVIntLeft = mElement.mSize.mValue;
98 0 : mState = READ_VINT_REST;
99 0 : mNextState = READ_CLUSTER_TIMECODE;
100 0 : break;
101 : case TIMECODESCALE_ID:
102 0 : mVInt = VInt();
103 0 : mVIntLeft = mElement.mSize.mValue;
104 0 : mState = READ_VINT_REST;
105 0 : mNextState = READ_TIMECODESCALE;
106 0 : break;
107 : case CLUSTER_ID:
108 0 : mClusterOffset = mCurrentOffset + (p - aBuffer) -
109 0 : (mElement.mID.mLength + mElement.mSize.mLength);
110 : // Handle "unknown" length;
111 0 : if (mElement.mSize.mValue + 1 != uint64_t(1) << (mElement.mSize.mLength * 7)) {
112 0 : mClusterEndOffset = mClusterOffset + mElement.mID.mLength + mElement.mSize.mLength + mElement.mSize.mValue;
113 : } else {
114 0 : mClusterEndOffset = -1;
115 : }
116 0 : mState = READ_ELEMENT_ID;
117 0 : break;
118 : case BLOCKGROUP_ID:
119 0 : mState = READ_ELEMENT_ID;
120 0 : break;
121 : case SIMPLEBLOCK_ID:
122 : /* FALLTHROUGH */
123 : case BLOCK_ID:
124 0 : mBlockSize = mElement.mSize.mValue;
125 0 : mBlockTimecode = 0;
126 0 : mBlockTimecodeLength = BLOCK_TIMECODE_LENGTH;
127 0 : mBlockOffset = mCurrentOffset + (p - aBuffer) -
128 0 : (mElement.mID.mLength + mElement.mSize.mLength);
129 0 : mState = READ_VINT;
130 0 : mNextState = READ_BLOCK_TIMECODE;
131 0 : break;
132 : case TRACKS_ID:
133 0 : mSkipBytes = mElement.mSize.mValue;
134 0 : mState = CHECK_INIT_FOUND;
135 0 : break;
136 : case EBML_ID:
137 0 : mLastInitStartOffset = mCurrentOffset + (p - aBuffer) -
138 0 : (mElement.mID.mLength + mElement.mSize.mLength);
139 : MOZ_FALLTHROUGH;
140 : default:
141 0 : mSkipBytes = mElement.mSize.mValue;
142 0 : mState = SKIP_DATA;
143 0 : mNextState = READ_ELEMENT_ID;
144 0 : break;
145 : }
146 0 : break;
147 : case READ_VINT: {
148 0 : unsigned char c = *p++;
149 : uint32_t mask;
150 0 : mVInt.mLength = VIntLength(c, &mask);
151 0 : mVIntLeft = mVInt.mLength - 1;
152 0 : mVInt.mValue = mVIntRaw ? c : c & ~mask;
153 0 : mState = READ_VINT_REST;
154 0 : break;
155 : }
156 : case READ_VINT_REST:
157 0 : if (mVIntLeft) {
158 0 : mVInt.mValue <<= 8;
159 0 : mVInt.mValue |= *p++;
160 0 : mVIntLeft -= 1;
161 : } else {
162 0 : mState = mNextState;
163 : }
164 0 : break;
165 : case READ_TIMECODESCALE:
166 0 : if (!mGotTimecodeScale) {
167 0 : return false;
168 : }
169 0 : mTimecodeScale = mVInt.mValue;
170 0 : mState = READ_ELEMENT_ID;
171 0 : break;
172 : case READ_CLUSTER_TIMECODE:
173 0 : mClusterTimecode = mVInt.mValue;
174 0 : mState = READ_ELEMENT_ID;
175 0 : break;
176 : case READ_BLOCK_TIMECODE:
177 0 : if (mBlockTimecodeLength) {
178 0 : mBlockTimecode <<= 8;
179 0 : mBlockTimecode |= *p++;
180 0 : mBlockTimecodeLength -= 1;
181 : } else {
182 : // It's possible we've parsed this data before, so avoid inserting
183 : // duplicate WebMTimeDataOffset entries.
184 : {
185 0 : ReentrantMonitorAutoEnter mon(aReentrantMonitor);
186 0 : int64_t endOffset = mBlockOffset + mBlockSize +
187 0 : mElement.mID.mLength + mElement.mSize.mLength;
188 0 : uint32_t idx = aMapping.IndexOfFirstElementGt(endOffset);
189 0 : if (idx == 0 || aMapping[idx - 1] != endOffset) {
190 : // Don't insert invalid negative timecodes.
191 0 : if (mBlockTimecode >= 0 || mClusterTimecode >= uint16_t(abs(mBlockTimecode))) {
192 0 : if (!mGotTimecodeScale) {
193 0 : return false;
194 : }
195 0 : uint64_t absTimecode = mClusterTimecode + mBlockTimecode;
196 0 : absTimecode *= mTimecodeScale;
197 : // Avoid creating an entry if the timecode is out of order
198 : // (invalid according to the WebM specification) so that
199 : // ordering invariants of aMapping are not violated.
200 0 : if (idx == 0 ||
201 0 : aMapping[idx - 1].mTimecode <= absTimecode ||
202 0 : (idx + 1 < aMapping.Length() &&
203 0 : aMapping[idx + 1].mTimecode >= absTimecode)) {
204 : WebMTimeDataOffset entry(endOffset, absTimecode, mLastInitStartOffset,
205 0 : mClusterOffset, mClusterEndOffset);
206 0 : aMapping.InsertElementAt(idx, entry);
207 : } else {
208 0 : WEBM_DEBUG("Out of order timecode %" PRIu64 " in Cluster at %" PRId64 " ignored",
209 : absTimecode, mClusterOffset);
210 : }
211 : }
212 : }
213 : }
214 :
215 : // Skip rest of block header and the block's payload.
216 0 : mBlockSize -= mVInt.mLength;
217 0 : mBlockSize -= BLOCK_TIMECODE_LENGTH;
218 0 : mSkipBytes = uint32_t(mBlockSize);
219 0 : mState = SKIP_DATA;
220 0 : mNextState = READ_ELEMENT_ID;
221 : }
222 0 : break;
223 : case SKIP_DATA:
224 0 : if (mSkipBytes) {
225 0 : uint32_t left = aLength - (p - aBuffer);
226 0 : left = std::min(left, mSkipBytes);
227 0 : p += left;
228 0 : mSkipBytes -= left;
229 : }
230 0 : if (!mSkipBytes) {
231 0 : mBlockEndOffset = mCurrentOffset + (p - aBuffer);
232 0 : mState = mNextState;
233 : }
234 0 : break;
235 : case CHECK_INIT_FOUND:
236 0 : if (mSkipBytes) {
237 0 : uint32_t left = aLength - (p - aBuffer);
238 0 : left = std::min(left, mSkipBytes);
239 0 : p += left;
240 0 : mSkipBytes -= left;
241 : }
242 0 : if (!mSkipBytes) {
243 0 : if (mInitEndOffset < 0) {
244 0 : mInitEndOffset = mCurrentOffset + (p - aBuffer);
245 0 : mBlockEndOffset = mCurrentOffset + (p - aBuffer);
246 : }
247 0 : mState = READ_ELEMENT_ID;
248 : }
249 0 : break;
250 : }
251 : }
252 :
253 0 : NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
254 0 : mCurrentOffset += aLength;
255 :
256 0 : return true;
257 : }
258 :
259 : int64_t
260 0 : WebMBufferedParser::EndSegmentOffset(int64_t aOffset)
261 : {
262 0 : if (mLastInitStartOffset > aOffset || mClusterOffset > aOffset) {
263 0 : return std::min(mLastInitStartOffset >= 0 ? mLastInitStartOffset : INT64_MAX,
264 0 : mClusterOffset >= 0 ? mClusterOffset : INT64_MAX);
265 : }
266 0 : return mBlockEndOffset;
267 : }
268 :
269 : // SyncOffsetComparator and TimeComparator are slightly confusing, in that
270 : // the nsTArray they're used with (mTimeMapping) is sorted by mEndOffset and
271 : // these comparators are used on the other fields of WebMTimeDataOffset.
272 : // This is only valid because timecodes are required to be monotonically
273 : // increasing within a file (thus establishing an ordering relationship with
274 : // mTimecode), and mEndOffset is derived from mSyncOffset.
275 : struct SyncOffsetComparator {
276 0 : bool Equals(const WebMTimeDataOffset& a, const int64_t& b) const {
277 0 : return a.mSyncOffset == b;
278 : }
279 :
280 0 : bool LessThan(const WebMTimeDataOffset& a, const int64_t& b) const {
281 0 : return a.mSyncOffset < b;
282 : }
283 : };
284 :
285 : struct TimeComparator {
286 0 : bool Equals(const WebMTimeDataOffset& a, const uint64_t& b) const {
287 0 : return a.mTimecode == b;
288 : }
289 :
290 0 : bool LessThan(const WebMTimeDataOffset& a, const uint64_t& b) const {
291 0 : return a.mTimecode < b;
292 : }
293 : };
294 :
295 0 : bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
296 : uint64_t* aStartTime, uint64_t* aEndTime)
297 : {
298 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
299 :
300 : // Find the first WebMTimeDataOffset at or after aStartOffset.
301 0 : uint32_t start = mTimeMapping.IndexOfFirstElementGt(aStartOffset - 1, SyncOffsetComparator());
302 0 : if (start == mTimeMapping.Length()) {
303 0 : return false;
304 : }
305 :
306 : // Find the first WebMTimeDataOffset at or before aEndOffset.
307 0 : uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset);
308 0 : if (end > 0) {
309 0 : end -= 1;
310 : }
311 :
312 : // Range is empty.
313 0 : if (end <= start) {
314 0 : return false;
315 : }
316 :
317 0 : NS_ASSERTION(mTimeMapping[start].mSyncOffset >= aStartOffset &&
318 : mTimeMapping[end].mEndOffset <= aEndOffset,
319 : "Computed time range must lie within data range.");
320 0 : if (start > 0) {
321 0 : NS_ASSERTION(mTimeMapping[start - 1].mSyncOffset < aStartOffset,
322 : "Must have found least WebMTimeDataOffset for start");
323 : }
324 0 : if (end < mTimeMapping.Length() - 1) {
325 0 : NS_ASSERTION(mTimeMapping[end + 1].mEndOffset > aEndOffset,
326 : "Must have found greatest WebMTimeDataOffset for end");
327 : }
328 :
329 0 : MOZ_ASSERT(mTimeMapping[end].mTimecode >= mTimeMapping[end - 1].mTimecode);
330 0 : uint64_t frameDuration = mTimeMapping[end].mTimecode - mTimeMapping[end - 1].mTimecode;
331 0 : *aStartTime = mTimeMapping[start].mTimecode;
332 0 : *aEndTime = mTimeMapping[end].mTimecode + frameDuration;
333 0 : return true;
334 : }
335 :
336 0 : bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset)
337 : {
338 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
339 :
340 0 : if(mTimeMapping.IsEmpty()) {
341 0 : return false;
342 : }
343 :
344 0 : uint64_t time = aTime;
345 0 : if (time > 0) {
346 0 : time = time - 1;
347 : }
348 0 : uint32_t idx = mTimeMapping.IndexOfFirstElementGt(time, TimeComparator());
349 0 : if (idx == mTimeMapping.Length()) {
350 : // Clamp to end
351 0 : *aOffset = mTimeMapping[mTimeMapping.Length() - 1].mSyncOffset;
352 : } else {
353 : // Idx is within array or has been clamped to start
354 0 : *aOffset = mTimeMapping[idx].mSyncOffset;
355 : }
356 0 : return true;
357 : }
358 :
359 0 : void WebMBufferedState::NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset)
360 : {
361 0 : uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1);
362 0 : if (idx == 0 || !(mRangeParsers[idx-1] == aOffset)) {
363 : // If the incoming data overlaps an already parsed range, adjust the
364 : // buffer so that we only reparse the new data. It's also possible to
365 : // have an overlap where the end of the incoming data is within an
366 : // already parsed range, but we don't bother handling that other than by
367 : // avoiding storing duplicate timecodes when the parser runs.
368 0 : if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
369 : // Complete overlap, skip parsing.
370 0 : if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
371 0 : return;
372 : }
373 :
374 : // Partial overlap, adjust the buffer to parse only the new data.
375 0 : int64_t adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
376 0 : NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
377 0 : aBuffer += adjust;
378 0 : aLength -= uint32_t(adjust);
379 : } else {
380 0 : mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset));
381 0 : if (idx != 0) {
382 0 : mRangeParsers[idx].SetTimecodeScale(mRangeParsers[0].GetTimecodeScale());
383 : }
384 : }
385 : }
386 :
387 0 : mRangeParsers[idx].Append(aBuffer,
388 : aLength,
389 : mTimeMapping,
390 0 : mReentrantMonitor);
391 :
392 : // Merge parsers with overlapping regions and clean up the remnants.
393 0 : uint32_t i = 0;
394 0 : while (i + 1 < mRangeParsers.Length()) {
395 0 : if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
396 0 : mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
397 0 : mRangeParsers[i + 1].mInitEndOffset = mRangeParsers[i].mInitEndOffset;
398 0 : mRangeParsers.RemoveElementAt(i);
399 : } else {
400 0 : i += 1;
401 : }
402 : }
403 :
404 0 : if (mRangeParsers.IsEmpty()) {
405 0 : return;
406 : }
407 :
408 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
409 0 : mLastBlockOffset = mRangeParsers.LastElement().mBlockEndOffset;
410 : }
411 :
412 0 : void WebMBufferedState::Reset() {
413 0 : mRangeParsers.Clear();
414 0 : mTimeMapping.Clear();
415 0 : }
416 :
417 0 : void WebMBufferedState::UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource)
418 : {
419 0 : for (uint32_t index = 0; index < aRanges.Length(); index++) {
420 0 : const MediaByteRange& range = aRanges[index];
421 0 : int64_t offset = range.mStart;
422 0 : uint32_t length = range.mEnd - range.mStart;
423 :
424 0 : uint32_t idx = mRangeParsers.IndexOfFirstElementGt(offset - 1);
425 0 : if (!idx || !(mRangeParsers[idx-1] == offset)) {
426 : // If the incoming data overlaps an already parsed range, adjust the
427 : // buffer so that we only reparse the new data. It's also possible to
428 : // have an overlap where the end of the incoming data is within an
429 : // already parsed range, but we don't bother handling that other than by
430 : // avoiding storing duplicate timecodes when the parser runs.
431 0 : if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= offset) {
432 : // Complete overlap, skip parsing.
433 0 : if (offset + length <= mRangeParsers[idx].mCurrentOffset) {
434 0 : continue;
435 : }
436 :
437 : // Partial overlap, adjust the buffer to parse only the new data.
438 0 : int64_t adjust = mRangeParsers[idx].mCurrentOffset - offset;
439 0 : NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
440 0 : offset += adjust;
441 0 : length -= uint32_t(adjust);
442 : } else {
443 0 : mRangeParsers.InsertElementAt(idx, WebMBufferedParser(offset));
444 0 : if (idx) {
445 0 : mRangeParsers[idx].SetTimecodeScale(mRangeParsers[0].GetTimecodeScale());
446 : }
447 : }
448 : }
449 0 : while (length > 0) {
450 : static const uint32_t BLOCK_SIZE = 1048576;
451 0 : uint32_t block = std::min(length, BLOCK_SIZE);
452 0 : RefPtr<MediaByteBuffer> bytes = aResource->CachedReadAt(offset, block);
453 0 : if (!bytes) {
454 0 : break;
455 : }
456 0 : NotifyDataArrived(bytes->Elements(), bytes->Length(), offset);
457 0 : length -= bytes->Length();
458 0 : offset += bytes->Length();
459 : }
460 : }
461 0 : }
462 :
463 0 : int64_t WebMBufferedState::GetInitEndOffset()
464 : {
465 0 : if (mRangeParsers.IsEmpty()) {
466 0 : return -1;
467 : }
468 0 : return mRangeParsers[0].mInitEndOffset;
469 : }
470 :
471 0 : int64_t WebMBufferedState::GetLastBlockOffset()
472 : {
473 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
474 :
475 0 : return mLastBlockOffset;
476 : }
477 :
478 0 : bool WebMBufferedState::GetStartTime(uint64_t *aTime)
479 : {
480 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
481 :
482 0 : if (mTimeMapping.IsEmpty()) {
483 0 : return false;
484 : }
485 :
486 0 : uint32_t idx = mTimeMapping.IndexOfFirstElementGt(0, SyncOffsetComparator());
487 0 : if (idx == mTimeMapping.Length()) {
488 0 : return false;
489 : }
490 :
491 0 : *aTime = mTimeMapping[idx].mTimecode;
492 0 : return true;
493 : }
494 :
495 : bool
496 0 : WebMBufferedState::GetNextKeyframeTime(uint64_t aTime, uint64_t* aKeyframeTime)
497 : {
498 0 : ReentrantMonitorAutoEnter mon(mReentrantMonitor);
499 0 : int64_t offset = 0;
500 0 : bool rv = GetOffsetForTime(aTime, &offset);
501 0 : if (!rv) {
502 0 : return false;
503 : }
504 0 : uint32_t idx = mTimeMapping.IndexOfFirstElementGt(offset, SyncOffsetComparator());
505 0 : if (idx == mTimeMapping.Length()) {
506 0 : return false;
507 : }
508 0 : *aKeyframeTime = mTimeMapping[idx].mTimecode;
509 0 : return true;
510 : }
511 : } // namespace mozilla
512 :
513 : #undef WEBM_DEBUG
514 :
|