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