|
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 "mozilla/dom/TimeRanges.h" |
|
10 #include "nsThreadUtils.h" |
|
11 #include <algorithm> |
|
12 |
|
13 namespace mozilla { |
|
14 |
|
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 } |
|
33 |
|
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; |
|
43 |
|
44 const unsigned char* p = aBuffer; |
|
45 |
|
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 } |
|
148 |
|
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 } |
|
174 |
|
175 NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data."); |
|
176 mCurrentOffset += aLength; |
|
177 } |
|
178 |
|
179 bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset, |
|
180 uint64_t* aStartTime, uint64_t* aEndTime) |
|
181 { |
|
182 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
183 |
|
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 } |
|
189 |
|
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 } |
|
195 |
|
196 // Range is empty. |
|
197 if (end <= start) { |
|
198 return false; |
|
199 } |
|
200 |
|
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 } |
|
212 |
|
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]. |
|
216 |
|
217 *aStartTime = mTimeMapping[start].mTimecode; |
|
218 *aEndTime = mTimeMapping[end].mTimecode; |
|
219 return true; |
|
220 } |
|
221 |
|
222 bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset) |
|
223 { |
|
224 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
225 WebMTimeDataOffset result(0,0); |
|
226 |
|
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 } |
|
233 |
|
234 *aOffset = result.mOffset; |
|
235 return true; |
|
236 } |
|
237 |
|
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 } |
|
253 |
|
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 } |
|
263 |
|
264 mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), |
|
265 aLength, |
|
266 mTimeMapping, |
|
267 mReentrantMonitor); |
|
268 |
|
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 } |
|
280 |
|
281 } // namespace mozilla |
|
282 |