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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2010
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Chris Double <chris.double@double.co.nz>
24 : * Chris Pearce <chris@pearce.org.nz>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 : #include "nsDebug.h"
40 : #include "nsOggCodecState.h"
41 : #include "nsOggDecoder.h"
42 : #include <string.h>
43 : #include "nsTraceRefcnt.h"
44 : #include "VideoUtils.h"
45 : #include "nsBuiltinDecoderReader.h"
46 :
47 : #include "mozilla/StandardInteger.h"
48 :
49 : #ifdef PR_LOGGING
50 : extern PRLogModuleInfo* gBuiltinDecoderLog;
51 : #define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg)
52 : #else
53 : #define LOG(type, msg)
54 : #endif
55 :
56 : nsOggCodecState*
57 0 : nsOggCodecState::Create(ogg_page* aPage)
58 : {
59 0 : NS_ASSERTION(ogg_page_bos(aPage), "Only call on BOS page!");
60 0 : nsAutoPtr<nsOggCodecState> codecState;
61 0 : if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
62 0 : codecState = new nsTheoraState(aPage);
63 0 : } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
64 0 : codecState = new nsVorbisState(aPage);
65 0 : } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
66 0 : codecState = new nsSkeletonState(aPage);
67 : } else {
68 0 : codecState = new nsOggCodecState(aPage, false);
69 : }
70 0 : return codecState->nsOggCodecState::Init() ? codecState.forget() : nsnull;
71 : }
72 :
73 0 : nsOggCodecState::nsOggCodecState(ogg_page* aBosPage, bool aActive) :
74 : mPacketCount(0),
75 0 : mSerial(ogg_page_serialno(aBosPage)),
76 : mActive(aActive),
77 0 : mDoneReadingHeaders(!aActive)
78 : {
79 0 : MOZ_COUNT_CTOR(nsOggCodecState);
80 0 : memset(&mState, 0, sizeof(ogg_stream_state));
81 0 : }
82 :
83 0 : nsOggCodecState::~nsOggCodecState() {
84 0 : MOZ_COUNT_DTOR(nsOggCodecState);
85 0 : Reset();
86 : #ifdef DEBUG
87 : int ret =
88 : #endif
89 0 : ogg_stream_clear(&mState);
90 0 : NS_ASSERTION(ret == 0, "ogg_stream_clear failed");
91 0 : }
92 :
93 0 : nsresult nsOggCodecState::Reset() {
94 0 : if (ogg_stream_reset(&mState) != 0) {
95 0 : return NS_ERROR_FAILURE;
96 : }
97 0 : mPackets.Erase();
98 0 : ClearUnstamped();
99 0 : return NS_OK;
100 : }
101 :
102 0 : void nsOggCodecState::ClearUnstamped()
103 : {
104 0 : for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
105 0 : nsOggCodecState::ReleasePacket(mUnstamped[i]);
106 : }
107 0 : mUnstamped.Clear();
108 0 : }
109 :
110 0 : bool nsOggCodecState::Init() {
111 0 : int ret = ogg_stream_init(&mState, mSerial);
112 0 : return ret == 0;
113 : }
114 :
115 0 : void nsVorbisState::RecordVorbisPacketSamples(ogg_packet* aPacket,
116 : long aSamples)
117 : {
118 : #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
119 0 : mVorbisPacketSamples[aPacket] = aSamples;
120 : #endif
121 0 : }
122 :
123 0 : void nsVorbisState::ValidateVorbisPacketSamples(ogg_packet* aPacket,
124 : long aSamples)
125 : {
126 : #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
127 0 : NS_ASSERTION(mVorbisPacketSamples[aPacket] == aSamples,
128 : "Decoded samples for Vorbis packet don't match expected!");
129 0 : mVorbisPacketSamples.erase(aPacket);
130 : #endif
131 0 : }
132 :
133 0 : void nsVorbisState::AssertHasRecordedPacketSamples(ogg_packet* aPacket)
134 : {
135 : #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
136 0 : NS_ASSERTION(mVorbisPacketSamples.count(aPacket) == 1,
137 : "Must have recorded packet samples");
138 : #endif
139 0 : }
140 :
141 0 : static ogg_packet* Clone(ogg_packet* aPacket) {
142 0 : ogg_packet* p = new ogg_packet();
143 0 : memcpy(p, aPacket, sizeof(ogg_packet));
144 0 : p->packet = new unsigned char[p->bytes];
145 0 : memcpy(p->packet, aPacket->packet, p->bytes);
146 0 : return p;
147 : }
148 :
149 0 : void nsOggCodecState::ReleasePacket(ogg_packet* aPacket) {
150 0 : if (aPacket)
151 0 : delete [] aPacket->packet;
152 : delete aPacket;
153 0 : }
154 :
155 0 : void nsPacketQueue::Append(ogg_packet* aPacket) {
156 0 : nsDeque::Push(aPacket);
157 0 : }
158 :
159 0 : ogg_packet* nsOggCodecState::PacketOut() {
160 0 : if (mPackets.IsEmpty()) {
161 0 : return nsnull;
162 : }
163 0 : return mPackets.PopFront();
164 : }
165 :
166 0 : nsresult nsOggCodecState::PageIn(ogg_page* aPage) {
167 0 : if (!mActive)
168 0 : return NS_OK;
169 0 : NS_ASSERTION(static_cast<PRUint32>(ogg_page_serialno(aPage)) == mSerial,
170 : "Page must be for this stream!");
171 0 : if (ogg_stream_pagein(&mState, aPage) == -1)
172 0 : return NS_ERROR_FAILURE;
173 : int r;
174 0 : do {
175 : ogg_packet packet;
176 0 : r = ogg_stream_packetout(&mState, &packet);
177 0 : if (r == 1) {
178 0 : mPackets.Append(Clone(&packet));
179 : }
180 : } while (r != 0);
181 0 : if (ogg_stream_check(&mState)) {
182 0 : NS_WARNING("Unrecoverable error in ogg_stream_packetout");
183 0 : return NS_ERROR_FAILURE;
184 : }
185 0 : return NS_OK;
186 : }
187 :
188 0 : nsresult nsOggCodecState::PacketOutUntilGranulepos(bool& aFoundGranulepos) {
189 : int r;
190 0 : aFoundGranulepos = false;
191 : // Extract packets from the sync state until either no more packets
192 : // come out, or we get a data packet with non -1 granulepos.
193 0 : do {
194 : ogg_packet packet;
195 0 : r = ogg_stream_packetout(&mState, &packet);
196 0 : if (r == 1) {
197 0 : ogg_packet* clone = Clone(&packet);
198 0 : if (IsHeader(&packet)) {
199 : // Header packets go straight into the packet queue.
200 0 : mPackets.Append(clone);
201 : } else {
202 : // We buffer data packets until we encounter a granulepos. We'll
203 : // then use the granulepos to figure out the granulepos of the
204 : // preceeding packets.
205 0 : mUnstamped.AppendElement(clone);
206 0 : aFoundGranulepos = packet.granulepos > 0;
207 : }
208 : }
209 0 : } while (r != 0 && !aFoundGranulepos);
210 0 : if (ogg_stream_check(&mState)) {
211 0 : NS_WARNING("Unrecoverable error in ogg_stream_packetout");
212 0 : return NS_ERROR_FAILURE;
213 : }
214 0 : return NS_OK;
215 : }
216 :
217 0 : nsTheoraState::nsTheoraState(ogg_page* aBosPage) :
218 : nsOggCodecState(aBosPage, true),
219 : mSetup(0),
220 : mCtx(0),
221 0 : mPixelAspectRatio(0)
222 : {
223 0 : MOZ_COUNT_CTOR(nsTheoraState);
224 0 : th_info_init(&mInfo);
225 0 : th_comment_init(&mComment);
226 0 : }
227 :
228 0 : nsTheoraState::~nsTheoraState() {
229 0 : MOZ_COUNT_DTOR(nsTheoraState);
230 0 : th_setup_free(mSetup);
231 0 : th_decode_free(mCtx);
232 0 : th_comment_clear(&mComment);
233 0 : th_info_clear(&mInfo);
234 0 : }
235 :
236 0 : bool nsTheoraState::Init() {
237 0 : if (!mActive)
238 0 : return false;
239 :
240 0 : PRInt64 n = mInfo.aspect_numerator;
241 0 : PRInt64 d = mInfo.aspect_denominator;
242 :
243 : mPixelAspectRatio = (n == 0 || d == 0) ?
244 0 : 1.0f : static_cast<float>(n) / static_cast<float>(d);
245 :
246 : // Ensure the frame and picture regions aren't larger than our prescribed
247 : // maximum, or zero sized.
248 0 : nsIntSize frame(mInfo.frame_width, mInfo.frame_height);
249 0 : nsIntRect picture(mInfo.pic_x, mInfo.pic_y, mInfo.pic_width, mInfo.pic_height);
250 0 : if (!nsVideoInfo::ValidateVideoRegion(frame, picture, frame)) {
251 0 : return mActive = false;
252 : }
253 :
254 0 : mCtx = th_decode_alloc(&mInfo, mSetup);
255 0 : if (mCtx == NULL) {
256 0 : return mActive = false;
257 : }
258 :
259 0 : return true;
260 : }
261 :
262 : bool
263 0 : nsTheoraState::DecodeHeader(ogg_packet* aPacket)
264 : {
265 0 : mPacketCount++;
266 : int ret = th_decode_headerin(&mInfo,
267 : &mComment,
268 : &mSetup,
269 0 : aPacket);
270 :
271 : // We must determine when we've read the last header packet.
272 : // th_decode_headerin() does not tell us when it's read the last header, so
273 : // we must keep track of the headers externally.
274 : //
275 : // There are 3 header packets, the Identification, Comment, and Setup
276 : // headers, which must be in that order. If they're out of order, the file
277 : // is invalid. If we've successfully read a header, and it's the setup
278 : // header, then we're done reading headers. The first byte of each packet
279 : // determines it's type as follows:
280 : // 0x80 -> Identification header
281 : // 0x81 -> Comment header
282 : // 0x82 -> Setup header
283 : // See http://www.theora.org/doc/Theora.pdf Chapter 6, "Bitstream Headers",
284 : // for more details of the Ogg/Theora containment scheme.
285 0 : bool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x82;
286 0 : if (ret < 0 || mPacketCount > 3) {
287 : // We've received an error, or the first three packets weren't valid
288 : // header packets, assume bad input, and don't activate the bitstream.
289 0 : mDoneReadingHeaders = true;
290 0 : } else if (ret > 0 && isSetupHeader && mPacketCount == 3) {
291 : // Successfully read the three header packets.
292 0 : mDoneReadingHeaders = true;
293 0 : mActive = true;
294 : }
295 0 : return mDoneReadingHeaders;
296 : }
297 :
298 : PRInt64
299 0 : nsTheoraState::Time(PRInt64 granulepos) {
300 0 : if (!mActive) {
301 0 : return -1;
302 : }
303 0 : return nsTheoraState::Time(&mInfo, granulepos);
304 : }
305 :
306 : bool
307 0 : nsTheoraState::IsHeader(ogg_packet* aPacket) {
308 0 : return th_packet_isheader(aPacket);
309 : }
310 :
311 : # define TH_VERSION_CHECK(_info,_maj,_min,_sub) \
312 : (((_info)->version_major>(_maj)||(_info)->version_major==(_maj))&& \
313 : (((_info)->version_minor>(_min)||(_info)->version_minor==(_min))&& \
314 : (_info)->version_subminor>=(_sub)))
315 :
316 0 : PRInt64 nsTheoraState::Time(th_info* aInfo, PRInt64 aGranulepos)
317 : {
318 0 : if (aGranulepos < 0 || aInfo->fps_numerator == 0) {
319 0 : return -1;
320 : }
321 : // Implementation of th_granule_frame inlined here to operate
322 : // on the th_info structure instead of the theora_state.
323 0 : int shift = aInfo->keyframe_granule_shift;
324 0 : ogg_int64_t iframe = aGranulepos >> shift;
325 0 : ogg_int64_t pframe = aGranulepos - (iframe << shift);
326 0 : PRInt64 frameno = iframe + pframe - TH_VERSION_CHECK(aInfo, 3, 2, 1);
327 0 : CheckedInt64 t = ((CheckedInt64(frameno) + 1) * USECS_PER_S) * aInfo->fps_denominator;
328 0 : if (!t.valid())
329 0 : return -1;
330 0 : t /= aInfo->fps_numerator;
331 0 : return t.valid() ? t.value() : -1;
332 : }
333 :
334 0 : PRInt64 nsTheoraState::StartTime(PRInt64 granulepos) {
335 0 : if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) {
336 0 : return -1;
337 : }
338 0 : CheckedInt64 t = (CheckedInt64(th_granule_frame(mCtx, granulepos)) * USECS_PER_S) * mInfo.fps_denominator;
339 0 : if (!t.valid())
340 0 : return -1;
341 0 : return t.value() / mInfo.fps_numerator;
342 : }
343 :
344 : PRInt64
345 0 : nsTheoraState::MaxKeyframeOffset()
346 : {
347 : // Determine the maximum time in microseconds by which a key frame could
348 : // offset for the theora bitstream. Theora granulepos encode time as:
349 : // ((key_frame_number << granule_shift) + frame_offset).
350 : // Therefore the maximum possible time by which any frame could be offset
351 : // from a keyframe is the duration of (1 << granule_shift) - 1) frames.
352 : PRInt64 frameDuration;
353 :
354 : // Max number of frames keyframe could possibly be offset.
355 0 : PRInt64 keyframeDiff = (1 << mInfo.keyframe_granule_shift) - 1;
356 :
357 : // Length of frame in usecs.
358 0 : CheckedInt64 d = CheckedInt64(mInfo.fps_denominator) * USECS_PER_S;
359 0 : if (!d.valid())
360 0 : d = 0;
361 0 : frameDuration = d.value() / mInfo.fps_numerator;
362 :
363 : // Total time in usecs keyframe can be offset from any given frame.
364 0 : return frameDuration * keyframeDiff;
365 : }
366 :
367 : nsresult
368 0 : nsTheoraState::PageIn(ogg_page* aPage)
369 : {
370 0 : if (!mActive)
371 0 : return NS_OK;
372 0 : NS_ASSERTION(static_cast<PRUint32>(ogg_page_serialno(aPage)) == mSerial,
373 : "Page must be for this stream!");
374 0 : if (ogg_stream_pagein(&mState, aPage) == -1)
375 0 : return NS_ERROR_FAILURE;
376 : bool foundGp;
377 0 : nsresult res = PacketOutUntilGranulepos(foundGp);
378 0 : if (NS_FAILED(res))
379 0 : return res;
380 0 : if (foundGp && mDoneReadingHeaders) {
381 : // We've found a packet with a granulepos, and we've loaded our metadata
382 : // and initialized our decoder. Determine granulepos of buffered packets.
383 0 : ReconstructTheoraGranulepos();
384 0 : for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
385 0 : ogg_packet* packet = mUnstamped[i];
386 : #ifdef DEBUG
387 0 : NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
388 0 : NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
389 : #endif
390 0 : mPackets.Append(packet);
391 : }
392 0 : mUnstamped.Clear();
393 : }
394 0 : return NS_OK;
395 : }
396 :
397 : // Returns 1 if the Theora info struct is decoding a media of Theora
398 : // version (maj,min,sub) or later, otherwise returns 0.
399 : int
400 0 : TheoraVersion(th_info* info,
401 : unsigned char maj,
402 : unsigned char min,
403 : unsigned char sub)
404 : {
405 0 : ogg_uint32_t ver = (maj << 16) + (min << 8) + sub;
406 : ogg_uint32_t th_ver = (info->version_major << 16) +
407 : (info->version_minor << 8) +
408 0 : info->version_subminor;
409 0 : return (th_ver >= ver) ? 1 : 0;
410 : }
411 :
412 0 : void nsTheoraState::ReconstructTheoraGranulepos()
413 : {
414 0 : if (mUnstamped.Length() == 0) {
415 0 : return;
416 : }
417 0 : ogg_int64_t lastGranulepos = mUnstamped[mUnstamped.Length() - 1]->granulepos;
418 0 : NS_ASSERTION(lastGranulepos != -1, "Must know last granulepos");
419 :
420 : // Reconstruct the granulepos (and thus timestamps) of the decoded
421 : // frames. Granulepos are stored as ((keyframe<<shift)+offset). We
422 : // know the granulepos of the last frame in the list, so we can infer
423 : // the granulepos of the intermediate frames using their frame numbers.
424 0 : ogg_int64_t shift = mInfo.keyframe_granule_shift;
425 0 : ogg_int64_t version_3_2_1 = TheoraVersion(&mInfo,3,2,1);
426 : ogg_int64_t lastFrame = th_granule_frame(mCtx,
427 0 : lastGranulepos) + version_3_2_1;
428 0 : ogg_int64_t firstFrame = lastFrame - mUnstamped.Length() + 1;
429 :
430 : // Until we encounter a keyframe, we'll assume that the "keyframe"
431 : // segment of the granulepos is the first frame, or if that causes
432 : // the "offset" segment to overflow, we assume the required
433 : // keyframe is maximumally offset. Until we encounter a keyframe
434 : // the granulepos will probably be wrong, but we can't decode the
435 : // frame anyway (since we don't have its keyframe) so it doesn't really
436 : // matter.
437 0 : ogg_int64_t keyframe = lastGranulepos >> shift;
438 :
439 : // The lastFrame, firstFrame, keyframe variables, as well as the frame
440 : // variable in the loop below, store the frame number for Theora
441 : // version >= 3.2.1 streams, and store the frame index for Theora
442 : // version < 3.2.1 streams.
443 0 : for (PRUint32 i = 0; i < mUnstamped.Length() - 1; ++i) {
444 0 : ogg_int64_t frame = firstFrame + i;
445 : ogg_int64_t granulepos;
446 0 : ogg_packet* packet = mUnstamped[i];
447 0 : bool isKeyframe = th_packet_iskeyframe(packet) == 1;
448 :
449 0 : if (isKeyframe) {
450 0 : granulepos = frame << shift;
451 0 : keyframe = frame;
452 0 : } else if (frame >= keyframe &&
453 : frame - keyframe < ((ogg_int64_t)1 << shift))
454 : {
455 : // (frame - keyframe) won't overflow the "offset" segment of the
456 : // granulepos, so it's safe to calculate the granulepos.
457 0 : granulepos = (keyframe << shift) + (frame - keyframe);
458 : } else {
459 : // (frame - keyframeno) will overflow the "offset" segment of the
460 : // granulepos, so we take "keyframe" to be the max possible offset
461 : // frame instead.
462 0 : ogg_int64_t k = NS_MAX(frame - (((ogg_int64_t)1 << shift) - 1), version_3_2_1);
463 0 : granulepos = (k << shift) + (frame - k);
464 : }
465 : // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos
466 : // should be > 0.
467 : // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so
468 : // granulepos should be >= 0.
469 0 : NS_ASSERTION(granulepos >= version_3_2_1,
470 : "Invalid granulepos for Theora version");
471 :
472 : // Check that the frame's granule number is one more than the
473 : // previous frame's.
474 0 : NS_ASSERTION(i == 0 ||
475 : th_granule_frame(mCtx, granulepos) ==
476 : th_granule_frame(mCtx, mUnstamped[i-1]->granulepos) + 1,
477 : "Granulepos calculation is incorrect!");
478 :
479 0 : packet->granulepos = granulepos;
480 : }
481 :
482 : // Check that the second to last frame's granule number is one less than
483 : // the last frame's (the known granule number). If not our granulepos
484 : // recovery missed a beat.
485 0 : NS_ASSERTION(mUnstamped.Length() < 2 ||
486 : th_granule_frame(mCtx, mUnstamped[mUnstamped.Length()-2]->granulepos) + 1 ==
487 : th_granule_frame(mCtx, lastGranulepos),
488 : "Granulepos recovery should catch up with packet->granulepos!");
489 : }
490 :
491 0 : nsresult nsVorbisState::Reset()
492 : {
493 0 : nsresult res = NS_OK;
494 0 : if (mActive && vorbis_synthesis_restart(&mDsp) != 0) {
495 0 : res = NS_ERROR_FAILURE;
496 : }
497 0 : if (NS_FAILED(nsOggCodecState::Reset())) {
498 0 : return NS_ERROR_FAILURE;
499 : }
500 :
501 0 : mGranulepos = 0;
502 0 : mPrevVorbisBlockSize = 0;
503 :
504 0 : return res;
505 : }
506 :
507 0 : nsVorbisState::nsVorbisState(ogg_page* aBosPage) :
508 : nsOggCodecState(aBosPage, true),
509 : mPrevVorbisBlockSize(0),
510 0 : mGranulepos(0)
511 : {
512 0 : MOZ_COUNT_CTOR(nsVorbisState);
513 0 : vorbis_info_init(&mInfo);
514 0 : vorbis_comment_init(&mComment);
515 0 : memset(&mDsp, 0, sizeof(vorbis_dsp_state));
516 0 : memset(&mBlock, 0, sizeof(vorbis_block));
517 0 : }
518 :
519 0 : nsVorbisState::~nsVorbisState() {
520 0 : MOZ_COUNT_DTOR(nsVorbisState);
521 0 : Reset();
522 0 : vorbis_block_clear(&mBlock);
523 0 : vorbis_dsp_clear(&mDsp);
524 0 : vorbis_info_clear(&mInfo);
525 0 : vorbis_comment_clear(&mComment);
526 0 : }
527 :
528 0 : bool nsVorbisState::DecodeHeader(ogg_packet* aPacket) {
529 0 : mPacketCount++;
530 : int ret = vorbis_synthesis_headerin(&mInfo,
531 : &mComment,
532 0 : aPacket);
533 : // We must determine when we've read the last header packet.
534 : // vorbis_synthesis_headerin() does not tell us when it's read the last
535 : // header, so we must keep track of the headers externally.
536 : //
537 : // There are 3 header packets, the Identification, Comment, and Setup
538 : // headers, which must be in that order. If they're out of order, the file
539 : // is invalid. If we've successfully read a header, and it's the setup
540 : // header, then we're done reading headers. The first byte of each packet
541 : // determines it's type as follows:
542 : // 0x1 -> Identification header
543 : // 0x3 -> Comment header
544 : // 0x5 -> Setup header
545 : // For more details of the Vorbis/Ogg containment scheme, see the Vorbis I
546 : // Specification, Chapter 4, Codec Setup and Packet Decode:
547 : // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-580004
548 :
549 0 : bool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x5;
550 :
551 0 : if (ret < 0 || mPacketCount > 3) {
552 : // We've received an error, or the first three packets weren't valid
553 : // header packets, assume bad input, and deactivate the bitstream.
554 0 : mDoneReadingHeaders = true;
555 0 : mActive = false;
556 0 : } else if (ret == 0 && isSetupHeader && mPacketCount == 3) {
557 : // Successfully read the three header packets.
558 : // The bitstream remains active.
559 0 : mDoneReadingHeaders = true;
560 : }
561 0 : return mDoneReadingHeaders;
562 : }
563 :
564 0 : bool nsVorbisState::Init()
565 : {
566 0 : if (!mActive)
567 0 : return false;
568 :
569 0 : int ret = vorbis_synthesis_init(&mDsp, &mInfo);
570 0 : if (ret != 0) {
571 0 : NS_WARNING("vorbis_synthesis_init() failed initializing vorbis bitstream");
572 0 : return mActive = false;
573 : }
574 0 : ret = vorbis_block_init(&mDsp, &mBlock);
575 0 : if (ret != 0) {
576 0 : NS_WARNING("vorbis_block_init() failed initializing vorbis bitstream");
577 0 : if (mActive) {
578 0 : vorbis_dsp_clear(&mDsp);
579 : }
580 0 : return mActive = false;
581 : }
582 0 : return true;
583 : }
584 :
585 0 : PRInt64 nsVorbisState::Time(PRInt64 granulepos)
586 : {
587 0 : if (!mActive) {
588 0 : return -1;
589 : }
590 :
591 0 : return nsVorbisState::Time(&mInfo, granulepos);
592 : }
593 :
594 0 : PRInt64 nsVorbisState::Time(vorbis_info* aInfo, PRInt64 aGranulepos)
595 : {
596 0 : if (aGranulepos == -1 || aInfo->rate == 0) {
597 0 : return -1;
598 : }
599 0 : CheckedInt64 t = CheckedInt64(aGranulepos) * USECS_PER_S;
600 0 : if (!t.valid())
601 0 : t = 0;
602 0 : return t.value() / aInfo->rate;
603 : }
604 :
605 : bool
606 0 : nsVorbisState::IsHeader(ogg_packet* aPacket)
607 : {
608 : // The first byte in each Vorbis header packet is either 0x01, 0x03, or 0x05,
609 : // i.e. the first bit is odd. Audio data packets have their first bit as 0x0.
610 : // Any packet with its first bit set cannot be a data packet, it's a
611 : // (possibly invalid) header packet.
612 : // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2.1
613 0 : return aPacket->bytes > 0 ? (aPacket->packet[0] & 0x1) : false;
614 : }
615 :
616 : nsresult
617 0 : nsVorbisState::PageIn(ogg_page* aPage)
618 : {
619 0 : if (!mActive)
620 0 : return NS_OK;
621 0 : NS_ASSERTION(static_cast<PRUint32>(ogg_page_serialno(aPage)) == mSerial,
622 : "Page must be for this stream!");
623 0 : if (ogg_stream_pagein(&mState, aPage) == -1)
624 0 : return NS_ERROR_FAILURE;
625 : bool foundGp;
626 0 : nsresult res = PacketOutUntilGranulepos(foundGp);
627 0 : if (NS_FAILED(res))
628 0 : return res;
629 0 : if (foundGp && mDoneReadingHeaders) {
630 : // We've found a packet with a granulepos, and we've loaded our metadata
631 : // and initialized our decoder. Determine granulepos of buffered packets.
632 0 : ReconstructVorbisGranulepos();
633 0 : for (PRUint32 i = 0; i < mUnstamped.Length(); ++i) {
634 0 : ogg_packet* packet = mUnstamped[i];
635 0 : AssertHasRecordedPacketSamples(packet);
636 0 : NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
637 0 : NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
638 0 : mPackets.Append(packet);
639 : }
640 0 : mUnstamped.Clear();
641 : }
642 0 : return NS_OK;
643 : }
644 :
645 0 : nsresult nsVorbisState::ReconstructVorbisGranulepos()
646 : {
647 : // The number of samples in a Vorbis packet is:
648 : // window_blocksize(previous_packet)/4+window_blocksize(current_packet)/4
649 : // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-230001.3.2
650 : // So we maintain mPrevVorbisBlockSize, the block size of the last packet
651 : // encountered. We also maintain mGranulepos, which is the granulepos of
652 : // the last encountered packet. This enables us to give granulepos to
653 : // packets when the last packet in mUnstamped doesn't have a granulepos
654 : // (for example if the stream was truncated).
655 : //
656 : // We validate our prediction of the number of samples decoded when
657 : // VALIDATE_VORBIS_SAMPLE_CALCULATION is defined by recording the predicted
658 : // number of samples, and verifing we extract that many when decoding
659 : // each packet.
660 :
661 0 : NS_ASSERTION(mUnstamped.Length() > 0, "Length must be > 0");
662 0 : ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
663 0 : NS_ASSERTION(last->e_o_s || last->granulepos >= 0,
664 : "Must know last granulepos!");
665 0 : if (mUnstamped.Length() == 1) {
666 0 : ogg_packet* packet = mUnstamped[0];
667 0 : long blockSize = vorbis_packet_blocksize(&mInfo, packet);
668 0 : if (blockSize < 0) {
669 : // On failure vorbis_packet_blocksize returns < 0. If we've got
670 : // a bad packet, we just assume that decode will have to skip this
671 : // packet, i.e. assume 0 samples are decodable from this packet.
672 0 : blockSize = 0;
673 0 : mPrevVorbisBlockSize = 0;
674 : }
675 0 : long samples = mPrevVorbisBlockSize / 4 + blockSize / 4;
676 0 : mPrevVorbisBlockSize = blockSize;
677 0 : if (packet->granulepos == -1) {
678 0 : packet->granulepos = mGranulepos + samples;
679 : }
680 :
681 : // Account for a partial last frame
682 0 : if (packet->e_o_s && packet->granulepos >= mGranulepos) {
683 0 : samples = packet->granulepos - mGranulepos;
684 : }
685 :
686 0 : mGranulepos = packet->granulepos;
687 0 : RecordVorbisPacketSamples(packet, samples);
688 0 : return NS_OK;
689 : }
690 :
691 0 : bool unknownGranulepos = last->granulepos == -1;
692 0 : int totalSamples = 0;
693 0 : for (PRInt32 i = mUnstamped.Length() - 1; i > 0; i--) {
694 0 : ogg_packet* packet = mUnstamped[i];
695 0 : ogg_packet* prev = mUnstamped[i-1];
696 0 : ogg_int64_t granulepos = packet->granulepos;
697 0 : NS_ASSERTION(granulepos != -1, "Must know granulepos!");
698 0 : long prevBlockSize = vorbis_packet_blocksize(&mInfo, prev);
699 0 : long blockSize = vorbis_packet_blocksize(&mInfo, packet);
700 :
701 0 : if (blockSize < 0 || prevBlockSize < 0) {
702 : // On failure vorbis_packet_blocksize returns < 0. If we've got
703 : // a bad packet, we just assume that decode will have to skip this
704 : // packet, i.e. assume 0 samples are decodable from this packet.
705 0 : blockSize = 0;
706 0 : prevBlockSize = 0;
707 : }
708 :
709 0 : long samples = prevBlockSize / 4 + blockSize / 4;
710 0 : totalSamples += samples;
711 0 : prev->granulepos = granulepos - samples;
712 0 : RecordVorbisPacketSamples(packet, samples);
713 : }
714 :
715 0 : if (unknownGranulepos) {
716 0 : for (PRUint32 i = 0; i < mUnstamped.Length(); i++) {
717 0 : ogg_packet* packet = mUnstamped[i];
718 0 : packet->granulepos += mGranulepos + totalSamples + 1;
719 : }
720 : }
721 :
722 0 : ogg_packet* first = mUnstamped[0];
723 0 : long blockSize = vorbis_packet_blocksize(&mInfo, first);
724 0 : if (blockSize < 0) {
725 0 : mPrevVorbisBlockSize = 0;
726 0 : blockSize = 0;
727 : }
728 :
729 : long samples = (mPrevVorbisBlockSize == 0) ? 0 :
730 0 : mPrevVorbisBlockSize / 4 + blockSize / 4;
731 0 : PRInt64 start = first->granulepos - samples;
732 0 : RecordVorbisPacketSamples(first, samples);
733 :
734 0 : if (last->e_o_s && start < mGranulepos) {
735 : // We've calculated that there are more samples in this page than its
736 : // granulepos claims, and it's the last page in the stream. This is legal,
737 : // and we will need to prune the trailing samples when we come to decode it.
738 : // We must correct the timestamps so that they follow the last Vorbis page's
739 : // samples.
740 0 : PRInt64 pruned = mGranulepos - start;
741 0 : for (PRUint32 i = 0; i < mUnstamped.Length() - 1; i++) {
742 0 : mUnstamped[i]->granulepos += pruned;
743 : }
744 : #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
745 0 : mVorbisPacketSamples[last] -= pruned;
746 : #endif
747 : }
748 :
749 0 : mPrevVorbisBlockSize = vorbis_packet_blocksize(&mInfo, last);
750 0 : mPrevVorbisBlockSize = NS_MAX(static_cast<long>(0), mPrevVorbisBlockSize);
751 0 : mGranulepos = last->granulepos;
752 :
753 0 : return NS_OK;
754 : }
755 :
756 :
757 0 : nsSkeletonState::nsSkeletonState(ogg_page* aBosPage)
758 : : nsOggCodecState(aBosPage, true),
759 : mVersion(0),
760 : mPresentationTime(0),
761 0 : mLength(0)
762 : {
763 0 : MOZ_COUNT_CTOR(nsSkeletonState);
764 0 : }
765 :
766 0 : nsSkeletonState::~nsSkeletonState()
767 : {
768 0 : MOZ_COUNT_DTOR(nsSkeletonState);
769 0 : }
770 :
771 : // Support for Ogg Skeleton 4.0, as per specification at:
772 : // http://wiki.xiph.org/Ogg_Skeleton_4
773 :
774 : // Minimum length in bytes of a Skeleton header packet.
775 : static const long SKELETON_MIN_HEADER_LEN = 28;
776 : static const long SKELETON_4_0_MIN_HEADER_LEN = 80;
777 :
778 : // Minimum length in bytes of a Skeleton 4.0 index packet.
779 : static const long SKELETON_4_0_MIN_INDEX_LEN = 42;
780 :
781 : // Minimum possible size of a compressed index keypoint.
782 : static const size_t MIN_KEY_POINT_SIZE = 2;
783 :
784 : // Byte offset of the major and minor version numbers in the
785 : // Ogg Skeleton 4.0 header packet.
786 : static const size_t SKELETON_VERSION_MAJOR_OFFSET = 8;
787 : static const size_t SKELETON_VERSION_MINOR_OFFSET = 10;
788 :
789 : // Byte-offsets of the presentation time numerator and denominator
790 : static const size_t SKELETON_PRESENTATION_TIME_NUMERATOR_OFFSET = 12;
791 : static const size_t SKELETON_PRESENTATION_TIME_DENOMINATOR_OFFSET = 20;
792 :
793 : // Byte-offsets of the length of file field in the Skeleton 4.0 header packet.
794 : static const size_t SKELETON_FILE_LENGTH_OFFSET = 64;
795 :
796 : // Byte-offsets of the fields in the Skeleton index packet.
797 : static const size_t INDEX_SERIALNO_OFFSET = 6;
798 : static const size_t INDEX_NUM_KEYPOINTS_OFFSET = 10;
799 : static const size_t INDEX_TIME_DENOM_OFFSET = 18;
800 : static const size_t INDEX_FIRST_NUMER_OFFSET = 26;
801 : static const size_t INDEX_LAST_NUMER_OFFSET = 34;
802 : static const size_t INDEX_KEYPOINT_OFFSET = 42;
803 :
804 0 : static bool IsSkeletonBOS(ogg_packet* aPacket)
805 : {
806 : return aPacket->bytes >= SKELETON_MIN_HEADER_LEN &&
807 0 : memcmp(reinterpret_cast<char*>(aPacket->packet), "fishead", 8) == 0;
808 : }
809 :
810 0 : static bool IsSkeletonIndex(ogg_packet* aPacket)
811 : {
812 : return aPacket->bytes >= SKELETON_4_0_MIN_INDEX_LEN &&
813 0 : memcmp(reinterpret_cast<char*>(aPacket->packet), "index", 5) == 0;
814 : }
815 :
816 : // Reads a little-endian encoded unsigned 32bit integer at p.
817 0 : static PRUint32 LEUint32(const unsigned char* p)
818 : {
819 0 : return p[0] +
820 0 : (p[1] << 8) +
821 0 : (p[2] << 16) +
822 0 : (p[3] << 24);
823 : }
824 :
825 : // Reads a little-endian encoded 64bit integer at p.
826 0 : static PRInt64 LEInt64(const unsigned char* p)
827 : {
828 0 : PRUint32 lo = LEUint32(p);
829 0 : PRUint32 hi = LEUint32(p + 4);
830 0 : return static_cast<PRInt64>(lo) | (static_cast<PRInt64>(hi) << 32);
831 : }
832 :
833 : // Reads a little-endian encoded unsigned 16bit integer at p.
834 0 : static PRUint16 LEUint16(const unsigned char* p)
835 : {
836 0 : return p[0] + (p[1] << 8);
837 : }
838 :
839 : // Reads a variable length encoded integer at p. Will not read
840 : // past aLimit. Returns pointer to character after end of integer.
841 0 : static const unsigned char* ReadVariableLengthInt(const unsigned char* p,
842 : const unsigned char* aLimit,
843 : PRInt64& n)
844 : {
845 0 : int shift = 0;
846 0 : PRInt64 byte = 0;
847 0 : n = 0;
848 0 : while (p < aLimit &&
849 : (byte & 0x80) != 0x80 &&
850 : shift < 57)
851 : {
852 0 : byte = static_cast<PRInt64>(*p);
853 0 : n |= ((byte & 0x7f) << shift);
854 0 : shift += 7;
855 0 : p++;
856 : }
857 0 : return p;
858 : }
859 :
860 0 : bool nsSkeletonState::DecodeIndex(ogg_packet* aPacket)
861 : {
862 0 : NS_ASSERTION(aPacket->bytes >= SKELETON_4_0_MIN_INDEX_LEN,
863 : "Index must be at least minimum size");
864 0 : if (!mActive) {
865 0 : return false;
866 : }
867 :
868 0 : PRUint32 serialno = LEUint32(aPacket->packet + INDEX_SERIALNO_OFFSET);
869 0 : PRInt64 numKeyPoints = LEInt64(aPacket->packet + INDEX_NUM_KEYPOINTS_OFFSET);
870 :
871 0 : PRInt64 endTime = 0, startTime = 0;
872 0 : const unsigned char* p = aPacket->packet;
873 :
874 0 : PRInt64 timeDenom = LEInt64(aPacket->packet + INDEX_TIME_DENOM_OFFSET);
875 0 : if (timeDenom == 0) {
876 0 : LOG(PR_LOG_DEBUG, ("Ogg Skeleton Index packet for stream %u has 0 "
877 : "timestamp denominator.", serialno));
878 0 : return (mActive = false);
879 : }
880 :
881 : // Extract the start time.
882 0 : CheckedInt64 t = CheckedInt64(LEInt64(p + INDEX_FIRST_NUMER_OFFSET)) * USECS_PER_S;
883 0 : if (!t.valid()) {
884 0 : return (mActive = false);
885 : } else {
886 0 : startTime = t.value() / timeDenom;
887 : }
888 :
889 : // Extract the end time.
890 0 : t = LEInt64(p + INDEX_LAST_NUMER_OFFSET) * USECS_PER_S;
891 0 : if (!t.valid()) {
892 0 : return (mActive = false);
893 : } else {
894 0 : endTime = t.value() / timeDenom;
895 : }
896 :
897 : // Check the numKeyPoints value read, ensure we're not going to run out of
898 : // memory while trying to decode the index packet.
899 0 : CheckedInt64 minPacketSize = (CheckedInt64(numKeyPoints) * MIN_KEY_POINT_SIZE) + INDEX_KEYPOINT_OFFSET;
900 0 : if (!minPacketSize.valid())
901 : {
902 0 : return (mActive = false);
903 : }
904 :
905 0 : PRInt64 sizeofIndex = aPacket->bytes - INDEX_KEYPOINT_OFFSET;
906 0 : PRInt64 maxNumKeyPoints = sizeofIndex / MIN_KEY_POINT_SIZE;
907 0 : if (aPacket->bytes < minPacketSize.value() ||
908 : numKeyPoints > maxNumKeyPoints ||
909 : numKeyPoints < 0)
910 : {
911 : // Packet size is less than the theoretical minimum size, or the packet is
912 : // claiming to store more keypoints than it's capable of storing. This means
913 : // that the numKeyPoints field is too large or small for the packet to
914 : // possibly contain as many packets as it claims to, so the numKeyPoints
915 : // field is possibly malicious. Don't try decoding this index, we may run
916 : // out of memory.
917 0 : LOG(PR_LOG_DEBUG, ("Possibly malicious number of key points reported "
918 : "(%lld) in index packet for stream %u.",
919 : numKeyPoints,
920 : serialno));
921 0 : return (mActive = false);
922 : }
923 :
924 0 : nsAutoPtr<nsKeyFrameIndex> keyPoints(new nsKeyFrameIndex(startTime, endTime));
925 :
926 0 : p = aPacket->packet + INDEX_KEYPOINT_OFFSET;
927 0 : const unsigned char* limit = aPacket->packet + aPacket->bytes;
928 0 : PRInt64 numKeyPointsRead = 0;
929 0 : CheckedInt64 offset = 0;
930 0 : CheckedInt64 time = 0;
931 0 : while (p < limit &&
932 : numKeyPointsRead < numKeyPoints)
933 : {
934 0 : PRInt64 delta = 0;
935 0 : p = ReadVariableLengthInt(p, limit, delta);
936 0 : offset += delta;
937 0 : if (p == limit ||
938 0 : !offset.valid() ||
939 0 : offset.value() > mLength ||
940 0 : offset.value() < 0)
941 : {
942 0 : return (mActive = false);
943 : }
944 0 : p = ReadVariableLengthInt(p, limit, delta);
945 0 : time += delta;
946 0 : if (!time.valid() ||
947 0 : time.value() > endTime ||
948 0 : time.value() < startTime)
949 : {
950 0 : return (mActive = false);
951 : }
952 0 : CheckedInt64 timeUsecs = time * USECS_PER_S;
953 0 : if (!timeUsecs.valid())
954 0 : return mActive = false;
955 0 : timeUsecs /= timeDenom;
956 0 : keyPoints->Add(offset.value(), timeUsecs.value());
957 0 : numKeyPointsRead++;
958 : }
959 :
960 0 : PRInt32 keyPointsRead = keyPoints->Length();
961 0 : if (keyPointsRead > 0) {
962 0 : mIndex.Put(serialno, keyPoints.forget());
963 : }
964 :
965 0 : LOG(PR_LOG_DEBUG, ("Loaded %d keypoints for Skeleton on stream %u",
966 : keyPointsRead, serialno));
967 0 : return true;
968 : }
969 :
970 0 : nsresult nsSkeletonState::IndexedSeekTargetForTrack(PRUint32 aSerialno,
971 : PRInt64 aTarget,
972 : nsKeyPoint& aResult)
973 : {
974 0 : nsKeyFrameIndex* index = nsnull;
975 0 : mIndex.Get(aSerialno, &index);
976 :
977 0 : if (!index ||
978 0 : index->Length() == 0 ||
979 : aTarget < index->mStartTime ||
980 : aTarget > index->mEndTime)
981 : {
982 0 : return NS_ERROR_FAILURE;
983 : }
984 :
985 : // Binary search to find the last key point with time less than target.
986 0 : int start = 0;
987 0 : int end = index->Length() - 1;
988 0 : while (end > start) {
989 0 : int mid = start + ((end - start + 1) >> 1);
990 0 : if (index->Get(mid).mTime == aTarget) {
991 0 : start = mid;
992 0 : break;
993 0 : } else if (index->Get(mid).mTime < aTarget) {
994 0 : start = mid;
995 : } else {
996 0 : end = mid - 1;
997 : }
998 : }
999 :
1000 0 : aResult = index->Get(start);
1001 0 : NS_ASSERTION(aResult.mTime <= aTarget, "Result should have time <= target");
1002 0 : return NS_OK;
1003 : }
1004 :
1005 0 : nsresult nsSkeletonState::IndexedSeekTarget(PRInt64 aTarget,
1006 : nsTArray<PRUint32>& aTracks,
1007 : nsSeekTarget& aResult)
1008 : {
1009 0 : if (!mActive || mVersion < SKELETON_VERSION(4,0)) {
1010 0 : return NS_ERROR_FAILURE;
1011 : }
1012 : // Loop over all requested tracks' indexes, and get the keypoint for that
1013 : // seek target. Record the keypoint with the lowest offset, this will be
1014 : // our seek result. User must seek to the one with lowest offset to ensure we
1015 : // pass "keyframes" on all tracks when we decode forwards to the seek target.
1016 0 : nsSeekTarget r;
1017 0 : for (PRUint32 i=0; i<aTracks.Length(); i++) {
1018 0 : nsKeyPoint k;
1019 0 : if (NS_SUCCEEDED(IndexedSeekTargetForTrack(aTracks[i], aTarget, k)) &&
1020 : k.mOffset < r.mKeyPoint.mOffset)
1021 : {
1022 0 : r.mKeyPoint = k;
1023 0 : r.mSerial = aTracks[i];
1024 : }
1025 : }
1026 0 : if (r.IsNull()) {
1027 0 : return NS_ERROR_FAILURE;
1028 : }
1029 0 : LOG(PR_LOG_DEBUG, ("Indexed seek target for time %lld is offset %lld",
1030 : aTarget, r.mKeyPoint.mOffset));
1031 0 : aResult = r;
1032 0 : return NS_OK;
1033 : }
1034 :
1035 0 : nsresult nsSkeletonState::GetDuration(const nsTArray<PRUint32>& aTracks,
1036 : PRInt64& aDuration)
1037 : {
1038 0 : if (!mActive ||
1039 : mVersion < SKELETON_VERSION(4,0) ||
1040 0 : !HasIndex() ||
1041 0 : aTracks.Length() == 0)
1042 : {
1043 0 : return NS_ERROR_FAILURE;
1044 : }
1045 0 : PRInt64 endTime = INT64_MIN;
1046 0 : PRInt64 startTime = INT64_MAX;
1047 0 : for (PRUint32 i=0; i<aTracks.Length(); i++) {
1048 0 : nsKeyFrameIndex* index = nsnull;
1049 0 : mIndex.Get(aTracks[i], &index);
1050 0 : if (!index) {
1051 : // Can't get the timestamps for one of the required tracks, fail.
1052 0 : return NS_ERROR_FAILURE;
1053 : }
1054 0 : if (index->mEndTime > endTime) {
1055 0 : endTime = index->mEndTime;
1056 : }
1057 0 : if (index->mStartTime < startTime) {
1058 0 : startTime = index->mStartTime;
1059 : }
1060 : }
1061 0 : NS_ASSERTION(endTime > startTime, "Duration must be positive");
1062 0 : CheckedInt64 duration = CheckedInt64(endTime) - startTime;
1063 0 : aDuration = duration.valid() ? duration.value() : 0;
1064 0 : return duration.valid() ? NS_OK : NS_ERROR_FAILURE;
1065 : }
1066 :
1067 0 : bool nsSkeletonState::DecodeHeader(ogg_packet* aPacket)
1068 : {
1069 0 : if (IsSkeletonBOS(aPacket)) {
1070 0 : PRUint16 verMajor = LEUint16(aPacket->packet + SKELETON_VERSION_MAJOR_OFFSET);
1071 0 : PRUint16 verMinor = LEUint16(aPacket->packet + SKELETON_VERSION_MINOR_OFFSET);
1072 :
1073 : // Read the presentation time. We read this before the version check as the
1074 : // presentation time exists in all versions.
1075 0 : PRInt64 n = LEInt64(aPacket->packet + SKELETON_PRESENTATION_TIME_NUMERATOR_OFFSET);
1076 0 : PRInt64 d = LEInt64(aPacket->packet + SKELETON_PRESENTATION_TIME_DENOMINATOR_OFFSET);
1077 0 : mPresentationTime = d == 0 ? 0 : (static_cast<float>(n) / static_cast<float>(d)) * USECS_PER_S;
1078 :
1079 0 : mVersion = SKELETON_VERSION(verMajor, verMinor);
1080 0 : if (mVersion < SKELETON_VERSION(4,0) ||
1081 : mVersion >= SKELETON_VERSION(5,0) ||
1082 : aPacket->bytes < SKELETON_4_0_MIN_HEADER_LEN)
1083 : {
1084 : // We can only care to parse Skeleton version 4.0+.
1085 0 : mActive = false;
1086 0 : return mDoneReadingHeaders = true;
1087 : }
1088 :
1089 : // Extract the segment length.
1090 0 : mLength = LEInt64(aPacket->packet + SKELETON_FILE_LENGTH_OFFSET);
1091 :
1092 0 : LOG(PR_LOG_DEBUG, ("Skeleton segment length: %lld", mLength));
1093 :
1094 : // Initialize the serianlno-to-index map.
1095 0 : bool init = mIndex.Init();
1096 0 : if (!init) {
1097 0 : NS_WARNING("Failed to initialize Ogg skeleton serialno-to-index map");
1098 0 : mActive = false;
1099 0 : return mDoneReadingHeaders = true;
1100 : }
1101 0 : mActive = true;
1102 0 : } else if (IsSkeletonIndex(aPacket) && mVersion >= SKELETON_VERSION(4,0)) {
1103 0 : if (!DecodeIndex(aPacket)) {
1104 : // Failed to parse index, or invalid/hostile index. DecodeIndex() will
1105 : // have deactivated the track.
1106 0 : return mDoneReadingHeaders = true;
1107 : }
1108 :
1109 0 : } else if (aPacket->e_o_s) {
1110 0 : mDoneReadingHeaders = true;
1111 : }
1112 0 : return mDoneReadingHeaders;
1113 : }
|