1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/webm/WebMBufferedParser.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,282 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et cindent: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsAlgorithm.h" 1.11 +#include "WebMBufferedParser.h" 1.12 +#include "mozilla/dom/TimeRanges.h" 1.13 +#include "nsThreadUtils.h" 1.14 +#include <algorithm> 1.15 + 1.16 +namespace mozilla { 1.17 + 1.18 +static uint32_t 1.19 +VIntLength(unsigned char aFirstByte, uint32_t* aMask) 1.20 +{ 1.21 + uint32_t count = 1; 1.22 + uint32_t mask = 1 << 7; 1.23 + while (count < 8) { 1.24 + if ((aFirstByte & mask) != 0) { 1.25 + break; 1.26 + } 1.27 + mask >>= 1; 1.28 + count += 1; 1.29 + } 1.30 + if (aMask) { 1.31 + *aMask = mask; 1.32 + } 1.33 + NS_ASSERTION(count >= 1 && count <= 8, "Insane VInt length."); 1.34 + return count; 1.35 +} 1.36 + 1.37 +void WebMBufferedParser::Append(const unsigned char* aBuffer, uint32_t aLength, 1.38 + nsTArray<WebMTimeDataOffset>& aMapping, 1.39 + ReentrantMonitor& aReentrantMonitor) 1.40 +{ 1.41 + static const unsigned char CLUSTER_ID[] = { 0x1f, 0x43, 0xb6, 0x75 }; 1.42 + static const unsigned char TIMECODE_ID = 0xe7; 1.43 + static const unsigned char BLOCKGROUP_ID = 0xa0; 1.44 + static const unsigned char BLOCK_ID = 0xa1; 1.45 + static const unsigned char SIMPLEBLOCK_ID = 0xa3; 1.46 + 1.47 + const unsigned char* p = aBuffer; 1.48 + 1.49 + // Parse each byte in aBuffer one-by-one, producing timecodes and updating 1.50 + // aMapping as we go. Parser pauses at end of stream (which may be at any 1.51 + // point within the parse) and resumes parsing the next time Append is 1.52 + // called with new data. 1.53 + while (p < aBuffer + aLength) { 1.54 + switch (mState) { 1.55 + case CLUSTER_SYNC: 1.56 + if (*p++ == CLUSTER_ID[mClusterIDPos]) { 1.57 + mClusterIDPos += 1; 1.58 + } else { 1.59 + mClusterIDPos = 0; 1.60 + } 1.61 + // Cluster ID found, it's likely this is a valid sync point. If this 1.62 + // is a spurious match, the later parse steps will encounter an error 1.63 + // and return to CLUSTER_SYNC. 1.64 + if (mClusterIDPos == sizeof(CLUSTER_ID)) { 1.65 + mClusterIDPos = 0; 1.66 + mState = READ_VINT; 1.67 + mNextState = TIMECODE_SYNC; 1.68 + } 1.69 + break; 1.70 + case READ_VINT: { 1.71 + unsigned char c = *p++; 1.72 + uint32_t mask; 1.73 + mVIntLength = VIntLength(c, &mask); 1.74 + mVIntLeft = mVIntLength - 1; 1.75 + mVInt = c & ~mask; 1.76 + mState = READ_VINT_REST; 1.77 + break; 1.78 + } 1.79 + case READ_VINT_REST: 1.80 + if (mVIntLeft) { 1.81 + mVInt <<= 8; 1.82 + mVInt |= *p++; 1.83 + mVIntLeft -= 1; 1.84 + } else { 1.85 + mState = mNextState; 1.86 + } 1.87 + break; 1.88 + case TIMECODE_SYNC: 1.89 + if (*p++ != TIMECODE_ID) { 1.90 + p -= 1; 1.91 + mState = CLUSTER_SYNC; 1.92 + break; 1.93 + } 1.94 + mClusterTimecode = 0; 1.95 + mState = READ_VINT; 1.96 + mNextState = READ_CLUSTER_TIMECODE; 1.97 + break; 1.98 + case READ_CLUSTER_TIMECODE: 1.99 + if (mVInt) { 1.100 + mClusterTimecode <<= 8; 1.101 + mClusterTimecode |= *p++; 1.102 + mVInt -= 1; 1.103 + } else { 1.104 + mState = ANY_BLOCK_SYNC; 1.105 + } 1.106 + break; 1.107 + case ANY_BLOCK_SYNC: { 1.108 + unsigned char c = *p++; 1.109 + if (c == BLOCKGROUP_ID) { 1.110 + mState = READ_VINT; 1.111 + mNextState = ANY_BLOCK_SYNC; 1.112 + } else if (c == SIMPLEBLOCK_ID || c == BLOCK_ID) { 1.113 + mBlockOffset = mCurrentOffset + (p - aBuffer) - 1; 1.114 + mState = READ_VINT; 1.115 + mNextState = READ_BLOCK; 1.116 + } else { 1.117 + uint32_t length = VIntLength(c, nullptr); 1.118 + if (length == 4) { 1.119 + p -= 1; 1.120 + mState = CLUSTER_SYNC; 1.121 + } else { 1.122 + mState = READ_VINT; 1.123 + mNextState = SKIP_ELEMENT; 1.124 + } 1.125 + } 1.126 + break; 1.127 + } 1.128 + case READ_BLOCK: 1.129 + mBlockSize = mVInt; 1.130 + mBlockTimecode = 0; 1.131 + mBlockTimecodeLength = 2; 1.132 + mState = READ_VINT; 1.133 + mNextState = READ_BLOCK_TIMECODE; 1.134 + break; 1.135 + case READ_BLOCK_TIMECODE: 1.136 + if (mBlockTimecodeLength) { 1.137 + mBlockTimecode <<= 8; 1.138 + mBlockTimecode |= *p++; 1.139 + mBlockTimecodeLength -= 1; 1.140 + } else { 1.141 + // It's possible we've parsed this data before, so avoid inserting 1.142 + // duplicate WebMTimeDataOffset entries. 1.143 + { 1.144 + ReentrantMonitorAutoEnter mon(aReentrantMonitor); 1.145 + uint32_t idx = aMapping.IndexOfFirstElementGt(mBlockOffset); 1.146 + if (idx == 0 || !(aMapping[idx-1] == mBlockOffset)) { 1.147 + WebMTimeDataOffset entry(mBlockOffset, mClusterTimecode + mBlockTimecode); 1.148 + aMapping.InsertElementAt(idx, entry); 1.149 + } 1.150 + } 1.151 + 1.152 + // Skip rest of block header and the block's payload. 1.153 + mBlockSize -= mVIntLength; 1.154 + mBlockSize -= 2; 1.155 + mSkipBytes = uint32_t(mBlockSize); 1.156 + mState = SKIP_DATA; 1.157 + mNextState = ANY_BLOCK_SYNC; 1.158 + } 1.159 + break; 1.160 + case SKIP_DATA: 1.161 + if (mSkipBytes) { 1.162 + uint32_t left = aLength - (p - aBuffer); 1.163 + left = std::min(left, mSkipBytes); 1.164 + p += left; 1.165 + mSkipBytes -= left; 1.166 + } else { 1.167 + mState = mNextState; 1.168 + } 1.169 + break; 1.170 + case SKIP_ELEMENT: 1.171 + mSkipBytes = uint32_t(mVInt); 1.172 + mState = SKIP_DATA; 1.173 + mNextState = ANY_BLOCK_SYNC; 1.174 + break; 1.175 + } 1.176 + } 1.177 + 1.178 + NS_ASSERTION(p == aBuffer + aLength, "Must have parsed to end of data."); 1.179 + mCurrentOffset += aLength; 1.180 +} 1.181 + 1.182 +bool WebMBufferedState::CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset, 1.183 + uint64_t* aStartTime, uint64_t* aEndTime) 1.184 +{ 1.185 + ReentrantMonitorAutoEnter mon(mReentrantMonitor); 1.186 + 1.187 + // Find the first WebMTimeDataOffset at or after aStartOffset. 1.188 + uint32_t start = mTimeMapping.IndexOfFirstElementGt(aStartOffset-1); 1.189 + if (start == mTimeMapping.Length()) { 1.190 + return false; 1.191 + } 1.192 + 1.193 + // Find the first WebMTimeDataOffset at or before aEndOffset. 1.194 + uint32_t end = mTimeMapping.IndexOfFirstElementGt(aEndOffset-1); 1.195 + if (end > 0) { 1.196 + end -= 1; 1.197 + } 1.198 + 1.199 + // Range is empty. 1.200 + if (end <= start) { 1.201 + return false; 1.202 + } 1.203 + 1.204 + NS_ASSERTION(mTimeMapping[start].mOffset >= aStartOffset && 1.205 + mTimeMapping[end].mOffset <= aEndOffset, 1.206 + "Computed time range must lie within data range."); 1.207 + if (start > 0) { 1.208 + NS_ASSERTION(mTimeMapping[start - 1].mOffset <= aStartOffset, 1.209 + "Must have found least WebMTimeDataOffset for start"); 1.210 + } 1.211 + if (end < mTimeMapping.Length() - 1) { 1.212 + NS_ASSERTION(mTimeMapping[end + 1].mOffset >= aEndOffset, 1.213 + "Must have found greatest WebMTimeDataOffset for end"); 1.214 + } 1.215 + 1.216 + // The timestamp of the first media sample, in ns. We must subtract this 1.217 + // from the ranges' start and end timestamps, so that those timestamps are 1.218 + // normalized in the range [0,duration]. 1.219 + 1.220 + *aStartTime = mTimeMapping[start].mTimecode; 1.221 + *aEndTime = mTimeMapping[end].mTimecode; 1.222 + return true; 1.223 +} 1.224 + 1.225 +bool WebMBufferedState::GetOffsetForTime(uint64_t aTime, int64_t* aOffset) 1.226 +{ 1.227 + ReentrantMonitorAutoEnter mon(mReentrantMonitor); 1.228 + WebMTimeDataOffset result(0,0); 1.229 + 1.230 + for (uint32_t i = 0; i < mTimeMapping.Length(); ++i) { 1.231 + WebMTimeDataOffset o = mTimeMapping[i]; 1.232 + if (o.mTimecode < aTime && o.mTimecode > result.mTimecode) { 1.233 + result = o; 1.234 + } 1.235 + } 1.236 + 1.237 + *aOffset = result.mOffset; 1.238 + return true; 1.239 +} 1.240 + 1.241 +void WebMBufferedState::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) 1.242 +{ 1.243 + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); 1.244 + uint32_t idx = mRangeParsers.IndexOfFirstElementGt(aOffset - 1); 1.245 + if (idx == 0 || !(mRangeParsers[idx-1] == aOffset)) { 1.246 + // If the incoming data overlaps an already parsed range, adjust the 1.247 + // buffer so that we only reparse the new data. It's also possible to 1.248 + // have an overlap where the end of the incoming data is within an 1.249 + // already parsed range, but we don't bother handling that other than by 1.250 + // avoiding storing duplicate timecodes when the parser runs. 1.251 + if (idx != mRangeParsers.Length() && mRangeParsers[idx].mStartOffset <= aOffset) { 1.252 + // Complete overlap, skip parsing. 1.253 + if (aOffset + aLength <= mRangeParsers[idx].mCurrentOffset) { 1.254 + return; 1.255 + } 1.256 + 1.257 + // Partial overlap, adjust the buffer to parse only the new data. 1.258 + int64_t adjust = mRangeParsers[idx].mCurrentOffset - aOffset; 1.259 + NS_ASSERTION(adjust >= 0, "Overlap detection bug."); 1.260 + aBuffer += adjust; 1.261 + aLength -= uint32_t(adjust); 1.262 + } else { 1.263 + mRangeParsers.InsertElementAt(idx, WebMBufferedParser(aOffset)); 1.264 + } 1.265 + } 1.266 + 1.267 + mRangeParsers[idx].Append(reinterpret_cast<const unsigned char*>(aBuffer), 1.268 + aLength, 1.269 + mTimeMapping, 1.270 + mReentrantMonitor); 1.271 + 1.272 + // Merge parsers with overlapping regions and clean up the remnants. 1.273 + uint32_t i = 0; 1.274 + while (i + 1 < mRangeParsers.Length()) { 1.275 + if (mRangeParsers[i].mCurrentOffset >= mRangeParsers[i + 1].mStartOffset) { 1.276 + mRangeParsers[i + 1].mStartOffset = mRangeParsers[i].mStartOffset; 1.277 + mRangeParsers.RemoveElementAt(i); 1.278 + } else { 1.279 + i += 1; 1.280 + } 1.281 + } 1.282 +} 1.283 + 1.284 +} // namespace mozilla 1.285 +