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

     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

mercurial