summaryrefslogtreecommitdiffstats
path: root/dom/media/webm/WebMBufferedParser.h
blob: 4245561fc174483a09c5998219f6e7ccbdc280b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#if !defined(WebMBufferedParser_h_)
#define WebMBufferedParser_h_

#include "nsISupportsImpl.h"
#include "nsTArray.h"
#include "mozilla/ReentrantMonitor.h"
#include "MediaResource.h"

namespace mozilla {

// Stores a stream byte offset and the scaled timecode of the block at
// that offset.
struct WebMTimeDataOffset
{
  WebMTimeDataOffset(int64_t aEndOffset, uint64_t aTimecode,
                     int64_t aInitOffset, int64_t aSyncOffset,
                     int64_t aClusterEndOffset)
    : mEndOffset(aEndOffset)
    , mInitOffset(aInitOffset)
    , mSyncOffset(aSyncOffset)
    , mClusterEndOffset(aClusterEndOffset)
    , mTimecode(aTimecode)
  {}

  bool operator==(int64_t aEndOffset) const {
    return mEndOffset == aEndOffset;
  }

  bool operator!=(int64_t aEndOffset) const {
    return mEndOffset != aEndOffset;
  }

  bool operator<(int64_t aEndOffset) const {
    return mEndOffset < aEndOffset;
  }

  int64_t mEndOffset;
  int64_t mInitOffset;
  int64_t mSyncOffset;
  int64_t mClusterEndOffset;
  uint64_t mTimecode;
};

// A simple WebM parser that produces data offset to timecode pairs as it
// consumes blocks.  A new parser is created for each distinct range of data
// received and begins parsing from the first WebM cluster within that
// range.  Old parsers are destroyed when their range merges with a later
// parser or an already parsed range.  The parser may start at any position
// within the stream.
struct WebMBufferedParser
{
  explicit WebMBufferedParser(int64_t aOffset)
    : mStartOffset(aOffset)
    , mCurrentOffset(aOffset)
    , mInitEndOffset(-1)
    , mBlockEndOffset(-1)
    , mState(READ_ELEMENT_ID)
    , mNextState(READ_ELEMENT_ID)
    , mVIntRaw(false)
    , mLastInitStartOffset(-1)
    , mClusterSyncPos(0)
    , mVIntLeft(0)
    , mBlockSize(0)
    , mClusterTimecode(0)
    , mClusterOffset(-1)
    , mClusterEndOffset(-1)
    , mBlockOffset(0)
    , mBlockTimecode(0)
    , mBlockTimecodeLength(0)
    , mSkipBytes(0)
    , mTimecodeScale(1000000)
    , mGotTimecodeScale(false)
    , mGotClusterTimecode(false)
  {
    if (mStartOffset != 0) {
      mState = FIND_CLUSTER_SYNC;
    }
  }

  uint32_t GetTimecodeScale() {
    MOZ_ASSERT(mGotTimecodeScale);
    return mTimecodeScale;
  }

  // Use this function when we would only feed media segment for the parser.
  void AppendMediaSegmentOnly()
  {
    mGotTimecodeScale = true;
  }

  // If this parser is not expected to parse a segment info, it must be told
  // the appropriate timecode scale to use from elsewhere.
  void SetTimecodeScale(uint32_t aTimecodeScale) {
    mTimecodeScale = aTimecodeScale;
    mGotTimecodeScale = true;
  }

  // Steps the parser through aLength bytes of data.  Always consumes
  // aLength bytes.  Updates mCurrentOffset before returning.  Acquires
  // aReentrantMonitor before using aMapping.
  // Returns false if an error was encountered.
  bool Append(const unsigned char* aBuffer, uint32_t aLength,
              nsTArray<WebMTimeDataOffset>& aMapping,
              ReentrantMonitor& aReentrantMonitor);

  bool operator==(int64_t aOffset) const {
    return mCurrentOffset == aOffset;
  }

  bool operator<(int64_t aOffset) const {
    return mCurrentOffset < aOffset;
  }

  // Returns the start offset of the init (EBML) or media segment (Cluster)
  // following the aOffset position. If none were found, returns mBlockEndOffset.
  // This allows to determine the end of the interval containg aOffset.
  int64_t EndSegmentOffset(int64_t aOffset);

  // Return the Cluster offset, return -1 if we can't find the Cluster.
  int64_t GetClusterOffset() const;

  // The offset at which this parser started parsing.  Used to merge
  // adjacent parsers, in which case the later parser adopts the earlier
  // parser's mStartOffset.
  int64_t mStartOffset;

  // Current offset within the stream.  Updated in chunks as Append() consumes
  // data.
  int64_t mCurrentOffset;

  // Tracks element's end offset. This indicates the end of the first init
  // segment. Will only be set if a Segment Information has been found.
  int64_t mInitEndOffset;

  // End offset of the last block parsed.
  // Will only be set if a complete block has been parsed.
  int64_t mBlockEndOffset;

private:
  enum State {
    // Parser start state.  Expects to begin at a valid EBML element.  Move
    // to READ_VINT with mVIntRaw true, then return to READ_ELEMENT_SIZE.
    READ_ELEMENT_ID,

    // Store element ID read into mVInt into mElement.mID.  Move to
    // READ_VINT with mVIntRaw false, then return to PARSE_ELEMENT.
    READ_ELEMENT_SIZE,

    // Parser start state for parsers started at an arbitrary offset.  Scans
    // forward for the first cluster, then move to READ_ELEMENT_ID.
    FIND_CLUSTER_SYNC,

    // Simplistic core of the parser.  Does not pay attention to nesting of
    // elements.  Checks mElement for an element ID of interest, then moves
    // to the next state as determined by the element ID.
    PARSE_ELEMENT,

    // Read the first byte of a variable length integer.  The first byte
    // encodes both the variable integer's length and part of the value.
    // The value read so far is stored in mVInt.mValue and the length is
    // stored in mVInt.mLength.  The number of bytes left to read is stored
    // in mVIntLeft.
    READ_VINT,

    // Reads the remaining mVIntLeft bytes into mVInt.mValue.
    READ_VINT_REST,

    // mVInt holds the parsed timecode scale, store it in mTimecodeScale,
    // then return READ_ELEMENT_ID.
    READ_TIMECODESCALE,

    // mVInt holds the parsed cluster timecode, store it in
    // mClusterTimecode, then return to READ_ELEMENT_ID.
    READ_CLUSTER_TIMECODE,

    // mBlockTimecodeLength holds the remaining length of the block timecode
    // left to read.  Read each byte of the timecode into mBlockTimecode.
    // Once complete, calculate the scaled timecode from the cluster
    // timecode, block timecode, and timecode scale, and insert a
    // WebMTimeDataOffset entry into aMapping if one is not already present
    // for this offset.
    READ_BLOCK_TIMECODE,

    // Will skip the current tracks element and set mInitEndOffset if an init
    // segment has been found.
    // Currently, only assumes it's the end of the tracks element.
    CHECK_INIT_FOUND,

    // Skip mSkipBytes of data before resuming parse at mNextState.
    SKIP_DATA,
  };

  // Current state machine action.
  State mState;

  // Next state machine action.  SKIP_DATA and READ_VINT_REST advance to
  // mNextState when the current action completes.
  State mNextState;

  struct VInt {
    VInt() : mValue(0), mLength(0) {}
    uint64_t mValue;
    uint64_t mLength;
  };

  struct EBMLElement {
    uint64_t Length() { return mID.mLength + mSize.mLength; }
    VInt mID;
    VInt mSize;
  };

  EBMLElement mElement;

  VInt mVInt;

  bool mVIntRaw;

  // EBML start offset. This indicates the start of the last init segment
  // parsed. Will only be set if an EBML element has been found.
  int64_t mLastInitStartOffset;

  // Current match position within CLUSTER_SYNC_ID.  Used to find sync
  // within arbitrary data.
  uint32_t mClusterSyncPos;

  // Number of bytes of mVInt left to read.  mVInt is complete once this
  // reaches 0.
  uint32_t mVIntLeft;

  // Size of the block currently being parsed.  Any unused data within the
  // block is skipped once the block timecode has been parsed.
  uint64_t mBlockSize;

  // Cluster-level timecode.
  uint64_t mClusterTimecode;

  // Start offset of the cluster currently being parsed.  Used as the sync
  // point offset for the offset-to-time mapping as each block timecode is
  // been parsed. -1 if unknown.
  int64_t mClusterOffset;

  // End offset of the cluster currently being parsed. -1 if unknown.
  int64_t mClusterEndOffset;

  // Start offset of the block currently being parsed.  Used as the byte
  // offset for the offset-to-time mapping once the block timecode has been
  // parsed.
  int64_t mBlockOffset;

  // Block-level timecode.  This is summed with mClusterTimecode to produce
  // an absolute timecode for the offset-to-time mapping.
  int16_t mBlockTimecode;

  // Number of bytes of mBlockTimecode left to read.
  uint32_t mBlockTimecodeLength;

  // Count of bytes left to skip before resuming parse at mNextState.
  // Mostly used to skip block payload data after reading a block timecode.
  uint32_t mSkipBytes;

  // Timecode scale read from the segment info and used to scale absolute
  // timecodes.
  uint32_t mTimecodeScale;

  // True if we read the timecode scale from the segment info or have
  // confirmed that the default value is to be used.
  bool mGotTimecodeScale;

  // True if we've read the cluster time code.
  bool mGotClusterTimecode;
};

class WebMBufferedState final
{
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebMBufferedState)

public:
  WebMBufferedState()
    : mReentrantMonitor("WebMBufferedState")
    , mLastBlockOffset(-1)
  {
    MOZ_COUNT_CTOR(WebMBufferedState);
  }

  void NotifyDataArrived(const unsigned char* aBuffer, uint32_t aLength, int64_t aOffset);
  void Reset();
  void UpdateIndex(const MediaByteRangeSet& aRanges, MediaResource* aResource);
  bool CalculateBufferedForRange(int64_t aStartOffset, int64_t aEndOffset,
                                 uint64_t* aStartTime, uint64_t* aEndTime);

  // Returns true if mTimeMapping is not empty and sets aOffset to
  // the latest offset for which decoding can resume without data
  // dependencies to arrive at aTime. aTime will be clamped to the start
  // of mTimeMapping if it is earlier than the first element, and to the end
  // if later than the last
  bool GetOffsetForTime(uint64_t aTime, int64_t* aOffset);

  // Returns end offset of init segment or -1 if none found.
  int64_t GetInitEndOffset();
  // Returns the end offset of the last complete block or -1 if none found.
  int64_t GetLastBlockOffset();

  // Returns start time
  bool GetStartTime(uint64_t *aTime);

  // Returns keyframe for time
  bool GetNextKeyframeTime(uint64_t aTime, uint64_t* aKeyframeTime);

private:
  // Private destructor, to discourage deletion outside of Release():
  ~WebMBufferedState() {
    MOZ_COUNT_DTOR(WebMBufferedState);
  }

  // Synchronizes access to the mTimeMapping array and mLastBlockOffset.
  ReentrantMonitor mReentrantMonitor;

  // Sorted (by offset) map of data offsets to timecodes.  Populated
  // on the main thread as data is received and parsed by WebMBufferedParsers.
  nsTArray<WebMTimeDataOffset> mTimeMapping;
  // The last complete block parsed. -1 if not set.
  int64_t mLastBlockOffset;

  // Sorted (by offset) live parser instances.  Main thread only.
  nsTArray<WebMBufferedParser> mRangeParsers;
};

} // namespace mozilla

#endif