content/media/webm/WebMBufferedParser.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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
michael@0 7 #include "nsAlgorithm.h"
michael@0 8 #include "WebMBufferedParser.h"
michael@0 9 #include "mozilla/dom/TimeRanges.h"
michael@0 10 #include "nsThreadUtils.h"
michael@0 11 #include <algorithm>
michael@0 12
michael@0 13 namespace mozilla {
michael@0 14
michael@0 15 static uint32_t
michael@0 16 VIntLength(unsigned char aFirstByte, uint32_t* aMask)
michael@0 17 {
michael@0 18 uint32_t count = 1;
michael@0 19 uint32_t mask = 1 << 7;
michael@0 20 while (count < 8) {
michael@0 21 if ((aFirstByte & mask) != 0) {
michael@0 22 break;
michael@0 23 }
michael@0 24 mask >>= 1;
michael@0 25 count += 1;
michael@0 26 }
michael@0 27 if (aMask) {
michael@0 28 *aMask = mask;
michael@0 29 }
michael@0 30 NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length.");
michael@0 31 return count;
michael@0 32 }
michael@0 33
michael@0 34 void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength,
michael@0 35 nsTArray<WebMTimeDataOffset>& aMapping,
michael@0 36 ReentrantMonitor& aReentrantMonitor)
michael@0 37 {
michael@0 38 static const unsigned char CLUSTER_ID[] = { 0x1f, 0x43, 0xb6, 0x75 };
michael@0 39 static const unsigned char TIMECODE_ID = 0xe7;
michael@0 40 static const unsigned char BLOCKGROUP_ID = 0xa0;
michael@0 41 static const unsigned char BLOCK_ID = 0xa1;
michael@0 42 static const unsigned char SIMPLEBLOCK_ID = 0xa3;
michael@0 43
michael@0 44 const unsigned char* p = aBuffer;
michael@0 45
michael@0 46 // Parse each byte in aBuffer one-by-one, producing timecodes and updating
michael@0 47 // aMapping as we go. Parser pauses at end of stream (which may be at any
michael@0 48 // point within the parse) and resumes parsing the next time Append is
michael@0 49 // called with new data.
michael@0 50 while (p < aBuffer + aLength) {
michael@0 51 switch (mState) {
michael@0 52 case CLUSTER_SYNC:
michael@0 53 if (*p++ == CLUSTER_ID[mClusterIDPos]) {
michael@0 54 mClusterIDPos += 1;
michael@0 55 } else {
michael@0 56 mClusterIDPos = 0;
michael@0 57 }
michael@0 58 // Cluster ID found, it's likely this is a valid sync point. If this
michael@0 59 // is a spurious match, the later parse steps will encounter an error
michael@0 60 // and return to CLUSTER_SYNC.
michael@0 61 if (mClusterIDPos == sizeof(CLUSTER_ID)) {
michael@0 62 mClusterIDPos = 0;
michael@0 63 mState = READ_VINT;
michael@0 64 mNextState = TIMECODE_SYNC;
michael@0 65 }
michael@0 66 break;
michael@0 67 case READ_VINT: {
michael@0 68 unsigned char c = *p++;
michael@0 69 uint32_t mask;
michael@0 70 mVIntLength = VIntLength(c, &mask);
michael@0 71 mVIntLeft = mVIntLength - 1;
michael@0 72 mVInt = c & ~mask;
michael@0 73 mState = READ_VINT_REST;
michael@0 74 break;
michael@0 75 }
michael@0 76 case READ_VINT_REST:
michael@0 77 if (mVIntLeft) {
michael@0 78 mVInt <<= 8;
michael@0 79 mVInt |= *p++;
michael@0 80 mVIntLeft -= 1;
michael@0 81 } else {
michael@0 82 mState = mNextState;
michael@0 83 }
michael@0 84 break;
michael@0 85 case TIMECODE_SYNC:
michael@0 86 if (*p++ != TIMECODE_ID) {
michael@0 87 p -= 1;
michael@0 88 mState = CLUSTER_SYNC;
michael@0 89 break;
michael@0 90 }
michael@0 91 mClusterTimecode = 0;
michael@0 92 mState = READ_VINT;
michael@0 93 mNextState = READ_CLUSTER_TIMECODE;
michael@0 94 break;
michael@0 95 case READ_CLUSTER_TIMECODE:
michael@0 96 if (mVInt) {
michael@0 97 mClusterTimecode <<= 8;
michael@0 98 mClusterTimecode |= *p++;
michael@0 99 mVInt -= 1;
michael@0 100 } else {
michael@0 101 mState = ANY_BLOCK_SYNC;
michael@0 102 }
michael@0 103 break;
michael@0 104 case ANY_BLOCK_SYNC: {
michael@0 105 unsigned char c = *p++;
michael@0 106 if (c == BLOCKGROUP_ID) {
michael@0 107 mState = READ_VINT;
michael@0 108 mNextState = ANY_BLOCK_SYNC;
michael@0 109 } else if (c == SIMPLEBLOCK_ID || c == BLOCK_ID) {
michael@0 110 mBlockOffset = mCurrentOffset + (p - aBuffer) - 1;
michael@0 111 mState = READ_VINT;
michael@0 112 mNextState = READ_BLOCK;
michael@0 113 } else {
michael@0 114 uint32_t length = VIntLength(c, nullptr);
michael@0 115 if (length == 4) {
michael@0 116 p -= 1;
michael@0 117 mState = CLUSTER_SYNC;
michael@0 118 } else {
michael@0 119 mState = READ_VINT;
michael@0 120 mNextState = SKIP_ELEMENT;
michael@0 121 }
michael@0 122 }
michael@0 123 break;
michael@0 124 }
michael@0 125 case READ_BLOCK:
michael@0 126 mBlockSize = mVInt;
michael@0 127 mBlockTimecode = 0;
michael@0 128 mBlockTimecodeLength = 2;
michael@0 129 mState = READ_VINT;
michael@0 130 mNextState = READ_BLOCK_TIMECODE;
michael@0 131 break;
michael@0 132 case READ_BLOCK_TIMECODE:
michael@0 133 if (mBlockTimecodeLength) {
michael@0 134 mBlockTimecode <<= 8;
michael@0 135 mBlockTimecode |= *p++;
michael@0 136 mBlockTimecodeLength -= 1;
michael@0 137 } else {
michael@0 138 // It's possible we've parsed this data before, so avoid inserting
michael@0 139 // duplicate WebMTimeDataOffset entries.
michael@0 140 {
michael@0 141 ReentrantMonitorAutoEnter mon(aReentrantMonitor);
michael@0 142 uint32_t idx = aMapping.IndexOfFirstElementGt(mBlockOffset);
michael@0 143 if (idx == 0 || !(aMapping[idx-1] == mBlockOffset)) {
michael@0 144 WebMTimeDataOffset entry(mBlockOffset, mClusterTimecode + mBlockTimecode);
michael@0 145 aMapping.InsertElementAt(idx, entry);
michael@0 146 }
michael@0 147 }
michael@0 148
michael@0 149 // Skip rest of block header and the block's payload.
michael@0 150 mBlockSize -= mVIntLength;
michael@0 151 mBlockSize -= 2;
michael@0 152 mSkipBytes = uint32_t(mBlockSize);
michael@0 153 mState = SKIP_DATA;
michael@0 154 mNextState = ANY_BLOCK_SYNC;
michael@0 155 }
michael@0 156 break;
michael@0 157 case SKIP_DATA:
michael@0 158 if (mSkipBytes) {
michael@0 159 uint32_t left = aLength - (p - aBuffer);
michael@0 160 left = std::min(left, mSkipBytes);
michael@0 161 p += left;
michael@0 162 mSkipBytes -= left;
michael@0 163 } else {
michael@0 164 mState = mNextState;
michael@0 165 }
michael@0 166 break;
michael@0 167 case SKIP_ELEMENT:
michael@0 168 mSkipBytes = uint32_t(mVInt);
michael@0 169 mState = SKIP_DATA;
michael@0 170 mNextState = ANY_BLOCK_SYNC;
michael@0 171 break;
michael@0 172 }
michael@0 173 }
michael@0 174
michael@0 175 NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data.");
michael@0 176 mCurrentOffset += aLength;
michael@0 177 }
michael@0 178
michael@0 179 bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
michael@0 180 uint64_t* aStartTime, uint64_t* aEndTime)
michael@0 181 {
michael@0 182 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
michael@0 183
michael@0 184 // Find the first WebMTimeDataOffset at or after aStartOffset.
michael@0 185 uint32_t start = mTimeMapping.IndexOfFirstElementGt(aStartOffset-1);
michael@0 186 if (start == mTimeMapping.Length()) {
michael@0 187 return false;
michael@0 188 }
michael@0 189
michael@0 190 // Find the first WebMTimeDataOffset at or before aEndOffset.
michael@0 191 uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset-1);
michael@0 192 if (end > 0) {
michael@0 193 end -= 1;
michael@0 194 }
michael@0 195
michael@0 196 // Range is empty.
michael@0 197 if (end <= start) {
michael@0 198 return false;
michael@0 199 }
michael@0 200
michael@0 201 NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset &&
michael@0 202 mTimeMapping[end].mOffset <= aEndOffset,
michael@0 203 "Computed time range must lie within data range.");
michael@0 204 if (start > 0) {
michael@0 205 NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset,
michael@0 206 "Must have found least WebMTimeDataOffset for start");
michael@0 207 }
michael@0 208 if (end < mTimeMapping.Length() - 1) {
michael@0 209 NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset,
michael@0 210 "Must have found greatest WebMTimeDataOffset for end");
michael@0 211 }
michael@0 212
michael@0 213 // The timestamp of the first media sample, in ns. We must subtract this
michael@0 214 // from the ranges' start and end timestamps, so that those timestamps are
michael@0 215 // normalized in the range [0,duration].
michael@0 216
michael@0 217 *aStartTime = mTimeMapping[start].mTimecode;
michael@0 218 *aEndTime = mTimeMapping[end].mTimecode;
michael@0 219 return true;
michael@0 220 }
michael@0 221
michael@0 222 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset)
michael@0 223 {
michael@0 224 ReentrantMonitorAutoEnter mon(mReentrantMonitor);
michael@0 225 WebMTimeDataOffset result(0,0);
michael@0 226
michael@0 227 for (uint32_t i = 0; i < mTimeMapping.Length(); ++i) {
michael@0 228 WebMTimeDataOffset o = mTimeMapping[i];
michael@0 229 if (o.mTimecode < aTime && o.mTimecode > result.mTimecode) {
michael@0 230 result = o;
michael@0 231 }
michael@0 232 }
michael@0 233
michael@0 234 *aOffset = result.mOffset;
michael@0 235 return true;
michael@0 236 }
michael@0 237
michael@0 238 void WebMBufferedState::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset)
michael@0 239 {
michael@0 240 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
michael@0 241 uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1);
michael@0 242 if (idx == 0 || !(mRangeParsers[idx-1] == aOffset)) {
michael@0 243 // If the incoming data overlaps an already parsed range, adjust the
michael@0 244 // buffer so that we only reparse the new data. It's also possible to
michael@0 245 // have an overlap where the end of the incoming data is within an
michael@0 246 // already parsed range, but we don't bother handling that other than by
michael@0 247 // avoiding storing duplicate timecodes when the parser runs.
michael@0 248 if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) {
michael@0 249 // Complete overlap, skip parsing.
michael@0 250 if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) {
michael@0 251 return;
michael@0 252 }
michael@0 253
michael@0 254 // Partial overlap, adjust the buffer to parse only the new data.
michael@0 255 int64_t adjust = mRangeParsers[idx].mCurrentOffset - aOffset;
michael@0 256 NS_ASSERTION(adjust >= 0, "Overlap detection bug.");
michael@0 257 aBuffer += adjust;
michael@0 258 aLength -= uint32_t(adjust);
michael@0 259 } else {
michael@0 260 mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset));
michael@0 261 }
michael@0 262 }
michael@0 263
michael@0 264 mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer),
michael@0 265 aLength,
michael@0 266 mTimeMapping,
michael@0 267 mReentrantMonitor);
michael@0 268
michael@0 269 // Merge parsers with overlapping regions and clean up the remnants.
michael@0 270 uint32_t i = 0;
michael@0 271 while (i + 1 < mRangeParsers.Length()) {
michael@0 272 if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) {
michael@0 273 mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset;
michael@0 274 mRangeParsers.RemoveElementAt(i);
michael@0 275 } else {
michael@0 276 i += 1;
michael@0 277 }
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 } // namespace mozilla
michael@0 282

mercurial