content/media/ogg/OggCodecState.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include <string.h>
michael@0 8
michael@0 9 #include "mozilla/DebugOnly.h"
michael@0 10 #include "mozilla/Endian.h"
michael@0 11 #include <stdint.h>
michael@0 12
michael@0 13 #include "nsDebug.h"
michael@0 14 #include "MediaDecoderReader.h"
michael@0 15 #include "OggCodecState.h"
michael@0 16 #include "OggDecoder.h"
michael@0 17 #include "nsISupportsImpl.h"
michael@0 18 #include "VideoUtils.h"
michael@0 19 #include <algorithm>
michael@0 20
michael@0 21 // On Android JellyBean, the hardware.h header redefines version_major and
michael@0 22 // version_minor, which breaks our build. See:
michael@0 23 // https://bugzilla.mozilla.org/show_bug.cgi?id=912702#c6
michael@0 24 #ifdef MOZ_WIDGET_GONK
michael@0 25 #ifdef version_major
michael@0 26 #undef version_major
michael@0 27 #endif
michael@0 28 #ifdef version_minor
michael@0 29 #undef version_minor
michael@0 30 #endif
michael@0 31 #endif
michael@0 32
michael@0 33 namespace mozilla {
michael@0 34
michael@0 35 #ifdef PR_LOGGING
michael@0 36 extern PRLogModuleInfo* gMediaDecoderLog;
michael@0 37 #define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
michael@0 38 #else
michael@0 39 #define LOG(type, msg)
michael@0 40 #endif
michael@0 41
michael@0 42 /** Decoder base class for Ogg-encapsulated streams. */
michael@0 43 OggCodecState*
michael@0 44 OggCodecState::Create(ogg_page* aPage)
michael@0 45 {
michael@0 46 NS_ASSERTION(ogg_page_bos(aPage), "Only call on BOS page!");
michael@0 47 nsAutoPtr<OggCodecState> codecState;
michael@0 48 if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
michael@0 49 codecState = new TheoraState(aPage);
michael@0 50 } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
michael@0 51 codecState = new VorbisState(aPage);
michael@0 52 #ifdef MOZ_OPUS
michael@0 53 } else if (aPage->body_len > 8 && memcmp(aPage->body, "OpusHead", 8) == 0) {
michael@0 54 codecState = new OpusState(aPage);
michael@0 55 #endif
michael@0 56 } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
michael@0 57 codecState = new SkeletonState(aPage);
michael@0 58 } else {
michael@0 59 codecState = new OggCodecState(aPage, false);
michael@0 60 }
michael@0 61 return codecState->OggCodecState::Init() ? codecState.forget() : nullptr;
michael@0 62 }
michael@0 63
michael@0 64 OggCodecState::OggCodecState(ogg_page* aBosPage, bool aActive) :
michael@0 65 mPacketCount(0),
michael@0 66 mSerial(ogg_page_serialno(aBosPage)),
michael@0 67 mActive(aActive),
michael@0 68 mDoneReadingHeaders(!aActive)
michael@0 69 {
michael@0 70 MOZ_COUNT_CTOR(OggCodecState);
michael@0 71 memset(&mState, 0, sizeof(ogg_stream_state));
michael@0 72 }
michael@0 73
michael@0 74 OggCodecState::~OggCodecState() {
michael@0 75 MOZ_COUNT_DTOR(OggCodecState);
michael@0 76 Reset();
michael@0 77 #ifdef DEBUG
michael@0 78 int ret =
michael@0 79 #endif
michael@0 80 ogg_stream_clear(&mState);
michael@0 81 NS_ASSERTION(ret == 0, "ogg_stream_clear failed");
michael@0 82 }
michael@0 83
michael@0 84 nsresult OggCodecState::Reset() {
michael@0 85 if (ogg_stream_reset(&mState) != 0) {
michael@0 86 return NS_ERROR_FAILURE;
michael@0 87 }
michael@0 88 mPackets.Erase();
michael@0 89 ClearUnstamped();
michael@0 90 return NS_OK;
michael@0 91 }
michael@0 92
michael@0 93 void OggCodecState::ClearUnstamped()
michael@0 94 {
michael@0 95 for (uint32_t i = 0; i < mUnstamped.Length(); ++i) {
michael@0 96 OggCodecState::ReleasePacket(mUnstamped[i]);
michael@0 97 }
michael@0 98 mUnstamped.Clear();
michael@0 99 }
michael@0 100
michael@0 101 bool OggCodecState::Init() {
michael@0 102 int ret = ogg_stream_init(&mState, mSerial);
michael@0 103 return ret == 0;
michael@0 104 }
michael@0 105
michael@0 106 bool OggCodecState::IsValidVorbisTagName(nsCString& aName)
michael@0 107 {
michael@0 108 // Tag names must consist of ASCII 0x20 through 0x7D,
michael@0 109 // excluding 0x3D '=' which is the separator.
michael@0 110 uint32_t length = aName.Length();
michael@0 111 const char* data = aName.Data();
michael@0 112 for (uint32_t i = 0; i < length; i++) {
michael@0 113 if (data[i] < 0x20 || data[i] > 0x7D || data[i] == '=') {
michael@0 114 return false;
michael@0 115 }
michael@0 116 }
michael@0 117 return true;
michael@0 118 }
michael@0 119
michael@0 120 bool OggCodecState::AddVorbisComment(MetadataTags* aTags,
michael@0 121 const char* aComment,
michael@0 122 uint32_t aLength)
michael@0 123 {
michael@0 124 const char* div = (const char*)memchr(aComment, '=', aLength);
michael@0 125 if (!div) {
michael@0 126 LOG(PR_LOG_DEBUG, ("Skipping comment: no separator"));
michael@0 127 return false;
michael@0 128 }
michael@0 129 nsCString key = nsCString(aComment, div-aComment);
michael@0 130 if (!IsValidVorbisTagName(key)) {
michael@0 131 LOG(PR_LOG_DEBUG, ("Skipping comment: invalid tag name"));
michael@0 132 return false;
michael@0 133 }
michael@0 134 uint32_t valueLength = aLength - (div-aComment);
michael@0 135 nsCString value = nsCString(div + 1, valueLength);
michael@0 136 if (!IsUTF8(value)) {
michael@0 137 LOG(PR_LOG_DEBUG, ("Skipping comment: invalid UTF-8 in value"));
michael@0 138 return false;
michael@0 139 }
michael@0 140 aTags->Put(key, value);
michael@0 141 return true;
michael@0 142 }
michael@0 143
michael@0 144 void VorbisState::RecordVorbisPacketSamples(ogg_packet* aPacket,
michael@0 145 long aSamples)
michael@0 146 {
michael@0 147 #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
michael@0 148 mVorbisPacketSamples[aPacket] = aSamples;
michael@0 149 #endif
michael@0 150 }
michael@0 151
michael@0 152 void VorbisState::ValidateVorbisPacketSamples(ogg_packet* aPacket,
michael@0 153 long aSamples)
michael@0 154 {
michael@0 155 #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
michael@0 156 NS_ASSERTION(mVorbisPacketSamples[aPacket] == aSamples,
michael@0 157 "Decoded samples for Vorbis packet don't match expected!");
michael@0 158 mVorbisPacketSamples.erase(aPacket);
michael@0 159 #endif
michael@0 160 }
michael@0 161
michael@0 162 void VorbisState::AssertHasRecordedPacketSamples(ogg_packet* aPacket)
michael@0 163 {
michael@0 164 #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
michael@0 165 NS_ASSERTION(mVorbisPacketSamples.count(aPacket) == 1,
michael@0 166 "Must have recorded packet samples");
michael@0 167 #endif
michael@0 168 }
michael@0 169
michael@0 170 static ogg_packet* Clone(ogg_packet* aPacket) {
michael@0 171 ogg_packet* p = new ogg_packet();
michael@0 172 memcpy(p, aPacket, sizeof(ogg_packet));
michael@0 173 p->packet = new unsigned char[p->bytes];
michael@0 174 memcpy(p->packet, aPacket->packet, p->bytes);
michael@0 175 return p;
michael@0 176 }
michael@0 177
michael@0 178 void OggCodecState::ReleasePacket(ogg_packet* aPacket) {
michael@0 179 if (aPacket)
michael@0 180 delete [] aPacket->packet;
michael@0 181 delete aPacket;
michael@0 182 }
michael@0 183
michael@0 184 void OggPacketQueue::Append(ogg_packet* aPacket) {
michael@0 185 nsDeque::Push(aPacket);
michael@0 186 }
michael@0 187
michael@0 188 ogg_packet* OggCodecState::PacketOut() {
michael@0 189 if (mPackets.IsEmpty()) {
michael@0 190 return nullptr;
michael@0 191 }
michael@0 192 return mPackets.PopFront();
michael@0 193 }
michael@0 194
michael@0 195 nsresult OggCodecState::PageIn(ogg_page* aPage) {
michael@0 196 if (!mActive)
michael@0 197 return NS_OK;
michael@0 198 NS_ASSERTION(static_cast<uint32_t>(ogg_page_serialno(aPage)) == mSerial,
michael@0 199 "Page must be for this stream!");
michael@0 200 if (ogg_stream_pagein(&mState, aPage) == -1)
michael@0 201 return NS_ERROR_FAILURE;
michael@0 202 int r;
michael@0 203 do {
michael@0 204 ogg_packet packet;
michael@0 205 r = ogg_stream_packetout(&mState, &packet);
michael@0 206 if (r == 1) {
michael@0 207 mPackets.Append(Clone(&packet));
michael@0 208 }
michael@0 209 } while (r != 0);
michael@0 210 if (ogg_stream_check(&mState)) {
michael@0 211 NS_WARNING("Unrecoverable error in ogg_stream_packetout");
michael@0 212 return NS_ERROR_FAILURE;
michael@0 213 }
michael@0 214 return NS_OK;
michael@0 215 }
michael@0 216
michael@0 217 nsresult OggCodecState::PacketOutUntilGranulepos(bool& aFoundGranulepos) {
michael@0 218 int r;
michael@0 219 aFoundGranulepos = false;
michael@0 220 // Extract packets from the sync state until either no more packets
michael@0 221 // come out, or we get a data packet with non -1 granulepos.
michael@0 222 do {
michael@0 223 ogg_packet packet;
michael@0 224 r = ogg_stream_packetout(&mState, &packet);
michael@0 225 if (r == 1) {
michael@0 226 ogg_packet* clone = Clone(&packet);
michael@0 227 if (IsHeader(&packet)) {
michael@0 228 // Header packets go straight into the packet queue.
michael@0 229 mPackets.Append(clone);
michael@0 230 } else {
michael@0 231 // We buffer data packets until we encounter a granulepos. We'll
michael@0 232 // then use the granulepos to figure out the granulepos of the
michael@0 233 // preceeding packets.
michael@0 234 mUnstamped.AppendElement(clone);
michael@0 235 aFoundGranulepos = packet.granulepos > 0;
michael@0 236 }
michael@0 237 }
michael@0 238 } while (r != 0 && !aFoundGranulepos);
michael@0 239 if (ogg_stream_check(&mState)) {
michael@0 240 NS_WARNING("Unrecoverable error in ogg_stream_packetout");
michael@0 241 return NS_ERROR_FAILURE;
michael@0 242 }
michael@0 243 return NS_OK;
michael@0 244 }
michael@0 245
michael@0 246 TheoraState::TheoraState(ogg_page* aBosPage) :
michael@0 247 OggCodecState(aBosPage, true),
michael@0 248 mSetup(0),
michael@0 249 mCtx(0),
michael@0 250 mPixelAspectRatio(0)
michael@0 251 {
michael@0 252 MOZ_COUNT_CTOR(TheoraState);
michael@0 253 th_info_init(&mInfo);
michael@0 254 th_comment_init(&mComment);
michael@0 255 }
michael@0 256
michael@0 257 TheoraState::~TheoraState() {
michael@0 258 MOZ_COUNT_DTOR(TheoraState);
michael@0 259 th_setup_free(mSetup);
michael@0 260 th_decode_free(mCtx);
michael@0 261 th_comment_clear(&mComment);
michael@0 262 th_info_clear(&mInfo);
michael@0 263 }
michael@0 264
michael@0 265 bool TheoraState::Init() {
michael@0 266 if (!mActive)
michael@0 267 return false;
michael@0 268
michael@0 269 int64_t n = mInfo.aspect_numerator;
michael@0 270 int64_t d = mInfo.aspect_denominator;
michael@0 271
michael@0 272 mPixelAspectRatio = (n == 0 || d == 0) ?
michael@0 273 1.0f : static_cast<float>(n) / static_cast<float>(d);
michael@0 274
michael@0 275 // Ensure the frame and picture regions aren't larger than our prescribed
michael@0 276 // maximum, or zero sized.
michael@0 277 nsIntSize frame(mInfo.frame_width, mInfo.frame_height);
michael@0 278 nsIntRect picture(mInfo.pic_x, mInfo.pic_y, mInfo.pic_width, mInfo.pic_height);
michael@0 279 if (!IsValidVideoRegion(frame, picture, frame)) {
michael@0 280 return mActive = false;
michael@0 281 }
michael@0 282
michael@0 283 mCtx = th_decode_alloc(&mInfo, mSetup);
michael@0 284 if (mCtx == nullptr) {
michael@0 285 return mActive = false;
michael@0 286 }
michael@0 287
michael@0 288 return true;
michael@0 289 }
michael@0 290
michael@0 291 bool
michael@0 292 TheoraState::DecodeHeader(ogg_packet* aPacket)
michael@0 293 {
michael@0 294 nsAutoRef<ogg_packet> autoRelease(aPacket);
michael@0 295 mPacketCount++;
michael@0 296 int ret = th_decode_headerin(&mInfo,
michael@0 297 &mComment,
michael@0 298 &mSetup,
michael@0 299 aPacket);
michael@0 300
michael@0 301 // We must determine when we've read the last header packet.
michael@0 302 // th_decode_headerin() does not tell us when it's read the last header, so
michael@0 303 // we must keep track of the headers externally.
michael@0 304 //
michael@0 305 // There are 3 header packets, the Identification, Comment, and Setup
michael@0 306 // headers, which must be in that order. If they're out of order, the file
michael@0 307 // is invalid. If we've successfully read a header, and it's the setup
michael@0 308 // header, then we're done reading headers. The first byte of each packet
michael@0 309 // determines it's type as follows:
michael@0 310 // 0x80 -> Identification header
michael@0 311 // 0x81 -> Comment header
michael@0 312 // 0x82 -> Setup header
michael@0 313 // See http://www.theora.org/doc/Theora.pdf Chapter 6, "Bitstream Headers",
michael@0 314 // for more details of the Ogg/Theora containment scheme.
michael@0 315 bool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x82;
michael@0 316 if (ret < 0 || mPacketCount > 3) {
michael@0 317 // We've received an error, or the first three packets weren't valid
michael@0 318 // header packets. Assume bad input.
michael@0 319 // Our caller will deactivate the bitstream.
michael@0 320 return false;
michael@0 321 } else if (ret > 0 && isSetupHeader && mPacketCount == 3) {
michael@0 322 // Successfully read the three header packets.
michael@0 323 mDoneReadingHeaders = true;
michael@0 324 }
michael@0 325 return true;
michael@0 326 }
michael@0 327
michael@0 328 int64_t
michael@0 329 TheoraState::Time(int64_t granulepos) {
michael@0 330 if (!mActive) {
michael@0 331 return -1;
michael@0 332 }
michael@0 333 return TheoraState::Time(&mInfo, granulepos);
michael@0 334 }
michael@0 335
michael@0 336 bool
michael@0 337 TheoraState::IsHeader(ogg_packet* aPacket) {
michael@0 338 return th_packet_isheader(aPacket);
michael@0 339 }
michael@0 340
michael@0 341 # define TH_VERSION_CHECK(_info,_maj,_min,_sub) \
michael@0 342 (((_info)->version_major>(_maj)||(_info)->version_major==(_maj))&& \
michael@0 343 (((_info)->version_minor>(_min)||(_info)->version_minor==(_min))&& \
michael@0 344 (_info)->version_subminor>=(_sub)))
michael@0 345
michael@0 346 int64_t TheoraState::Time(th_info* aInfo, int64_t aGranulepos)
michael@0 347 {
michael@0 348 if (aGranulepos < 0 || aInfo->fps_numerator == 0) {
michael@0 349 return -1;
michael@0 350 }
michael@0 351 // Implementation of th_granule_frame inlined here to operate
michael@0 352 // on the th_info structure instead of the theora_state.
michael@0 353 int shift = aInfo->keyframe_granule_shift;
michael@0 354 ogg_int64_t iframe = aGranulepos >> shift;
michael@0 355 ogg_int64_t pframe = aGranulepos - (iframe << shift);
michael@0 356 int64_t frameno = iframe + pframe - TH_VERSION_CHECK(aInfo, 3, 2, 1);
michael@0 357 CheckedInt64 t = ((CheckedInt64(frameno) + 1) * USECS_PER_S) * aInfo->fps_denominator;
michael@0 358 if (!t.isValid())
michael@0 359 return -1;
michael@0 360 t /= aInfo->fps_numerator;
michael@0 361 return t.isValid() ? t.value() : -1;
michael@0 362 }
michael@0 363
michael@0 364 int64_t TheoraState::StartTime(int64_t granulepos) {
michael@0 365 if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) {
michael@0 366 return -1;
michael@0 367 }
michael@0 368 CheckedInt64 t = (CheckedInt64(th_granule_frame(mCtx, granulepos)) * USECS_PER_S) * mInfo.fps_denominator;
michael@0 369 if (!t.isValid())
michael@0 370 return -1;
michael@0 371 return t.value() / mInfo.fps_numerator;
michael@0 372 }
michael@0 373
michael@0 374 int64_t
michael@0 375 TheoraState::MaxKeyframeOffset()
michael@0 376 {
michael@0 377 // Determine the maximum time in microseconds by which a key frame could
michael@0 378 // offset for the theora bitstream. Theora granulepos encode time as:
michael@0 379 // ((key_frame_number << granule_shift) + frame_offset).
michael@0 380 // Therefore the maximum possible time by which any frame could be offset
michael@0 381 // from a keyframe is the duration of (1 << granule_shift) - 1) frames.
michael@0 382 int64_t frameDuration;
michael@0 383
michael@0 384 // Max number of frames keyframe could possibly be offset.
michael@0 385 int64_t keyframeDiff = (1 << mInfo.keyframe_granule_shift) - 1;
michael@0 386
michael@0 387 // Length of frame in usecs.
michael@0 388 frameDuration = (mInfo.fps_denominator * USECS_PER_S) / mInfo.fps_numerator;
michael@0 389
michael@0 390 // Total time in usecs keyframe can be offset from any given frame.
michael@0 391 return frameDuration * keyframeDiff;
michael@0 392 }
michael@0 393
michael@0 394 nsresult
michael@0 395 TheoraState::PageIn(ogg_page* aPage)
michael@0 396 {
michael@0 397 if (!mActive)
michael@0 398 return NS_OK;
michael@0 399 NS_ASSERTION(static_cast<uint32_t>(ogg_page_serialno(aPage)) == mSerial,
michael@0 400 "Page must be for this stream!");
michael@0 401 if (ogg_stream_pagein(&mState, aPage) == -1)
michael@0 402 return NS_ERROR_FAILURE;
michael@0 403 bool foundGp;
michael@0 404 nsresult res = PacketOutUntilGranulepos(foundGp);
michael@0 405 if (NS_FAILED(res))
michael@0 406 return res;
michael@0 407 if (foundGp && mDoneReadingHeaders) {
michael@0 408 // We've found a packet with a granulepos, and we've loaded our metadata
michael@0 409 // and initialized our decoder. Determine granulepos of buffered packets.
michael@0 410 ReconstructTheoraGranulepos();
michael@0 411 for (uint32_t i = 0; i < mUnstamped.Length(); ++i) {
michael@0 412 ogg_packet* packet = mUnstamped[i];
michael@0 413 #ifdef DEBUG
michael@0 414 NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
michael@0 415 NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
michael@0 416 #endif
michael@0 417 mPackets.Append(packet);
michael@0 418 }
michael@0 419 mUnstamped.Clear();
michael@0 420 }
michael@0 421 return NS_OK;
michael@0 422 }
michael@0 423
michael@0 424 // Returns 1 if the Theora info struct is decoding a media of Theora
michael@0 425 // version (maj,min,sub) or later, otherwise returns 0.
michael@0 426 int
michael@0 427 TheoraVersion(th_info* info,
michael@0 428 unsigned char maj,
michael@0 429 unsigned char min,
michael@0 430 unsigned char sub)
michael@0 431 {
michael@0 432 ogg_uint32_t ver = (maj << 16) + (min << 8) + sub;
michael@0 433 ogg_uint32_t th_ver = (info->version_major << 16) +
michael@0 434 (info->version_minor << 8) +
michael@0 435 info->version_subminor;
michael@0 436 return (th_ver >= ver) ? 1 : 0;
michael@0 437 }
michael@0 438
michael@0 439 void TheoraState::ReconstructTheoraGranulepos()
michael@0 440 {
michael@0 441 if (mUnstamped.Length() == 0) {
michael@0 442 return;
michael@0 443 }
michael@0 444 ogg_int64_t lastGranulepos = mUnstamped[mUnstamped.Length() - 1]->granulepos;
michael@0 445 NS_ASSERTION(lastGranulepos != -1, "Must know last granulepos");
michael@0 446
michael@0 447 // Reconstruct the granulepos (and thus timestamps) of the decoded
michael@0 448 // frames. Granulepos are stored as ((keyframe<<shift)+offset). We
michael@0 449 // know the granulepos of the last frame in the list, so we can infer
michael@0 450 // the granulepos of the intermediate frames using their frame numbers.
michael@0 451 ogg_int64_t shift = mInfo.keyframe_granule_shift;
michael@0 452 ogg_int64_t version_3_2_1 = TheoraVersion(&mInfo,3,2,1);
michael@0 453 ogg_int64_t lastFrame = th_granule_frame(mCtx,
michael@0 454 lastGranulepos) + version_3_2_1;
michael@0 455 ogg_int64_t firstFrame = lastFrame - mUnstamped.Length() + 1;
michael@0 456
michael@0 457 // Until we encounter a keyframe, we'll assume that the "keyframe"
michael@0 458 // segment of the granulepos is the first frame, or if that causes
michael@0 459 // the "offset" segment to overflow, we assume the required
michael@0 460 // keyframe is maximumally offset. Until we encounter a keyframe
michael@0 461 // the granulepos will probably be wrong, but we can't decode the
michael@0 462 // frame anyway (since we don't have its keyframe) so it doesn't really
michael@0 463 // matter.
michael@0 464 ogg_int64_t keyframe = lastGranulepos >> shift;
michael@0 465
michael@0 466 // The lastFrame, firstFrame, keyframe variables, as well as the frame
michael@0 467 // variable in the loop below, store the frame number for Theora
michael@0 468 // version >= 3.2.1 streams, and store the frame index for Theora
michael@0 469 // version < 3.2.1 streams.
michael@0 470 for (uint32_t i = 0; i < mUnstamped.Length() - 1; ++i) {
michael@0 471 ogg_int64_t frame = firstFrame + i;
michael@0 472 ogg_int64_t granulepos;
michael@0 473 ogg_packet* packet = mUnstamped[i];
michael@0 474 bool isKeyframe = th_packet_iskeyframe(packet) == 1;
michael@0 475
michael@0 476 if (isKeyframe) {
michael@0 477 granulepos = frame << shift;
michael@0 478 keyframe = frame;
michael@0 479 } else if (frame >= keyframe &&
michael@0 480 frame - keyframe < ((ogg_int64_t)1 << shift))
michael@0 481 {
michael@0 482 // (frame - keyframe) won't overflow the "offset" segment of the
michael@0 483 // granulepos, so it's safe to calculate the granulepos.
michael@0 484 granulepos = (keyframe << shift) + (frame - keyframe);
michael@0 485 } else {
michael@0 486 // (frame - keyframeno) will overflow the "offset" segment of the
michael@0 487 // granulepos, so we take "keyframe" to be the max possible offset
michael@0 488 // frame instead.
michael@0 489 ogg_int64_t k = std::max(frame - (((ogg_int64_t)1 << shift) - 1), version_3_2_1);
michael@0 490 granulepos = (k << shift) + (frame - k);
michael@0 491 }
michael@0 492 // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos
michael@0 493 // should be > 0.
michael@0 494 // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so
michael@0 495 // granulepos should be >= 0.
michael@0 496 NS_ASSERTION(granulepos >= version_3_2_1,
michael@0 497 "Invalid granulepos for Theora version");
michael@0 498
michael@0 499 // Check that the frame's granule number is one more than the
michael@0 500 // previous frame's.
michael@0 501 NS_ASSERTION(i == 0 ||
michael@0 502 th_granule_frame(mCtx, granulepos) ==
michael@0 503 th_granule_frame(mCtx, mUnstamped[i-1]->granulepos) + 1,
michael@0 504 "Granulepos calculation is incorrect!");
michael@0 505
michael@0 506 packet->granulepos = granulepos;
michael@0 507 }
michael@0 508
michael@0 509 // Check that the second to last frame's granule number is one less than
michael@0 510 // the last frame's (the known granule number). If not our granulepos
michael@0 511 // recovery missed a beat.
michael@0 512 NS_ASSERTION(mUnstamped.Length() < 2 ||
michael@0 513 th_granule_frame(mCtx, mUnstamped[mUnstamped.Length()-2]->granulepos) + 1 ==
michael@0 514 th_granule_frame(mCtx, lastGranulepos),
michael@0 515 "Granulepos recovery should catch up with packet->granulepos!");
michael@0 516 }
michael@0 517
michael@0 518 nsresult VorbisState::Reset()
michael@0 519 {
michael@0 520 nsresult res = NS_OK;
michael@0 521 if (mActive && vorbis_synthesis_restart(&mDsp) != 0) {
michael@0 522 res = NS_ERROR_FAILURE;
michael@0 523 }
michael@0 524 if (NS_FAILED(OggCodecState::Reset())) {
michael@0 525 return NS_ERROR_FAILURE;
michael@0 526 }
michael@0 527
michael@0 528 mGranulepos = 0;
michael@0 529 mPrevVorbisBlockSize = 0;
michael@0 530
michael@0 531 return res;
michael@0 532 }
michael@0 533
michael@0 534 VorbisState::VorbisState(ogg_page* aBosPage) :
michael@0 535 OggCodecState(aBosPage, true),
michael@0 536 mPrevVorbisBlockSize(0),
michael@0 537 mGranulepos(0)
michael@0 538 {
michael@0 539 MOZ_COUNT_CTOR(VorbisState);
michael@0 540 vorbis_info_init(&mInfo);
michael@0 541 vorbis_comment_init(&mComment);
michael@0 542 memset(&mDsp, 0, sizeof(vorbis_dsp_state));
michael@0 543 memset(&mBlock, 0, sizeof(vorbis_block));
michael@0 544 }
michael@0 545
michael@0 546 VorbisState::~VorbisState() {
michael@0 547 MOZ_COUNT_DTOR(VorbisState);
michael@0 548 Reset();
michael@0 549 vorbis_block_clear(&mBlock);
michael@0 550 vorbis_dsp_clear(&mDsp);
michael@0 551 vorbis_info_clear(&mInfo);
michael@0 552 vorbis_comment_clear(&mComment);
michael@0 553 }
michael@0 554
michael@0 555 bool VorbisState::DecodeHeader(ogg_packet* aPacket) {
michael@0 556 nsAutoRef<ogg_packet> autoRelease(aPacket);
michael@0 557 mPacketCount++;
michael@0 558 int ret = vorbis_synthesis_headerin(&mInfo,
michael@0 559 &mComment,
michael@0 560 aPacket);
michael@0 561 // We must determine when we've read the last header packet.
michael@0 562 // vorbis_synthesis_headerin() does not tell us when it's read the last
michael@0 563 // header, so we must keep track of the headers externally.
michael@0 564 //
michael@0 565 // There are 3 header packets, the Identification, Comment, and Setup
michael@0 566 // headers, which must be in that order. If they're out of order, the file
michael@0 567 // is invalid. If we've successfully read a header, and it's the setup
michael@0 568 // header, then we're done reading headers. The first byte of each packet
michael@0 569 // determines it's type as follows:
michael@0 570 // 0x1 -> Identification header
michael@0 571 // 0x3 -> Comment header
michael@0 572 // 0x5 -> Setup header
michael@0 573 // For more details of the Vorbis/Ogg containment scheme, see the Vorbis I
michael@0 574 // Specification, Chapter 4, Codec Setup and Packet Decode:
michael@0 575 // http://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-580004
michael@0 576
michael@0 577 bool isSetupHeader = aPacket->bytes > 0 && aPacket->packet[0] == 0x5;
michael@0 578
michael@0 579 if (ret < 0 || mPacketCount > 3) {
michael@0 580 // We've received an error, or the first three packets weren't valid
michael@0 581 // header packets. Assume bad input. Our caller will deactivate the
michael@0 582 // bitstream.
michael@0 583 return false;
michael@0 584 } else if (ret == 0 && isSetupHeader && mPacketCount == 3) {
michael@0 585 // Successfully read the three header packets.
michael@0 586 // The bitstream remains active.
michael@0 587 mDoneReadingHeaders = true;
michael@0 588 }
michael@0 589 return true;
michael@0 590 }
michael@0 591
michael@0 592 bool VorbisState::Init()
michael@0 593 {
michael@0 594 if (!mActive)
michael@0 595 return false;
michael@0 596
michael@0 597 int ret = vorbis_synthesis_init(&mDsp, &mInfo);
michael@0 598 if (ret != 0) {
michael@0 599 NS_WARNING("vorbis_synthesis_init() failed initializing vorbis bitstream");
michael@0 600 return mActive = false;
michael@0 601 }
michael@0 602 ret = vorbis_block_init(&mDsp, &mBlock);
michael@0 603 if (ret != 0) {
michael@0 604 NS_WARNING("vorbis_block_init() failed initializing vorbis bitstream");
michael@0 605 if (mActive) {
michael@0 606 vorbis_dsp_clear(&mDsp);
michael@0 607 }
michael@0 608 return mActive = false;
michael@0 609 }
michael@0 610 return true;
michael@0 611 }
michael@0 612
michael@0 613 int64_t VorbisState::Time(int64_t granulepos)
michael@0 614 {
michael@0 615 if (!mActive) {
michael@0 616 return -1;
michael@0 617 }
michael@0 618
michael@0 619 return VorbisState::Time(&mInfo, granulepos);
michael@0 620 }
michael@0 621
michael@0 622 int64_t VorbisState::Time(vorbis_info* aInfo, int64_t aGranulepos)
michael@0 623 {
michael@0 624 if (aGranulepos == -1 || aInfo->rate == 0) {
michael@0 625 return -1;
michael@0 626 }
michael@0 627 CheckedInt64 t = CheckedInt64(aGranulepos) * USECS_PER_S;
michael@0 628 if (!t.isValid())
michael@0 629 t = 0;
michael@0 630 return t.value() / aInfo->rate;
michael@0 631 }
michael@0 632
michael@0 633 bool
michael@0 634 VorbisState::IsHeader(ogg_packet* aPacket)
michael@0 635 {
michael@0 636 // The first byte in each Vorbis header packet is either 0x01, 0x03, or 0x05,
michael@0 637 // i.e. the first bit is odd. Audio data packets have their first bit as 0x0.
michael@0 638 // Any packet with its first bit set cannot be a data packet, it's a
michael@0 639 // (possibly invalid) header packet.
michael@0 640 // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-610004.2.1
michael@0 641 return aPacket->bytes > 0 ? (aPacket->packet[0] & 0x1) : false;
michael@0 642 }
michael@0 643
michael@0 644 MetadataTags*
michael@0 645 VorbisState::GetTags()
michael@0 646 {
michael@0 647 MetadataTags* tags;
michael@0 648 NS_ASSERTION(mComment.user_comments, "no vorbis comment strings!");
michael@0 649 NS_ASSERTION(mComment.comment_lengths, "no vorbis comment lengths!");
michael@0 650 tags = new MetadataTags;
michael@0 651 for (int i = 0; i < mComment.comments; i++) {
michael@0 652 AddVorbisComment(tags, mComment.user_comments[i],
michael@0 653 mComment.comment_lengths[i]);
michael@0 654 }
michael@0 655 return tags;
michael@0 656 }
michael@0 657
michael@0 658 nsresult
michael@0 659 VorbisState::PageIn(ogg_page* aPage)
michael@0 660 {
michael@0 661 if (!mActive)
michael@0 662 return NS_OK;
michael@0 663 NS_ASSERTION(static_cast<uint32_t>(ogg_page_serialno(aPage)) == mSerial,
michael@0 664 "Page must be for this stream!");
michael@0 665 if (ogg_stream_pagein(&mState, aPage) == -1)
michael@0 666 return NS_ERROR_FAILURE;
michael@0 667 bool foundGp;
michael@0 668 nsresult res = PacketOutUntilGranulepos(foundGp);
michael@0 669 if (NS_FAILED(res))
michael@0 670 return res;
michael@0 671 if (foundGp && mDoneReadingHeaders) {
michael@0 672 // We've found a packet with a granulepos, and we've loaded our metadata
michael@0 673 // and initialized our decoder. Determine granulepos of buffered packets.
michael@0 674 ReconstructVorbisGranulepos();
michael@0 675 for (uint32_t i = 0; i < mUnstamped.Length(); ++i) {
michael@0 676 ogg_packet* packet = mUnstamped[i];
michael@0 677 AssertHasRecordedPacketSamples(packet);
michael@0 678 NS_ASSERTION(!IsHeader(packet), "Don't try to recover header packet gp");
michael@0 679 NS_ASSERTION(packet->granulepos != -1, "Packet must have gp by now");
michael@0 680 mPackets.Append(packet);
michael@0 681 }
michael@0 682 mUnstamped.Clear();
michael@0 683 }
michael@0 684 return NS_OK;
michael@0 685 }
michael@0 686
michael@0 687 nsresult VorbisState::ReconstructVorbisGranulepos()
michael@0 688 {
michael@0 689 // The number of samples in a Vorbis packet is:
michael@0 690 // window_blocksize(previous_packet)/4+window_blocksize(current_packet)/4
michael@0 691 // See: http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-230001.3.2
michael@0 692 // So we maintain mPrevVorbisBlockSize, the block size of the last packet
michael@0 693 // encountered. We also maintain mGranulepos, which is the granulepos of
michael@0 694 // the last encountered packet. This enables us to give granulepos to
michael@0 695 // packets when the last packet in mUnstamped doesn't have a granulepos
michael@0 696 // (for example if the stream was truncated).
michael@0 697 //
michael@0 698 // We validate our prediction of the number of samples decoded when
michael@0 699 // VALIDATE_VORBIS_SAMPLE_CALCULATION is defined by recording the predicted
michael@0 700 // number of samples, and verifing we extract that many when decoding
michael@0 701 // each packet.
michael@0 702
michael@0 703 NS_ASSERTION(mUnstamped.Length() > 0, "Length must be > 0");
michael@0 704 ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
michael@0 705 NS_ASSERTION(last->e_o_s || last->granulepos >= 0,
michael@0 706 "Must know last granulepos!");
michael@0 707 if (mUnstamped.Length() == 1) {
michael@0 708 ogg_packet* packet = mUnstamped[0];
michael@0 709 long blockSize = vorbis_packet_blocksize(&mInfo, packet);
michael@0 710 if (blockSize < 0) {
michael@0 711 // On failure vorbis_packet_blocksize returns < 0. If we've got
michael@0 712 // a bad packet, we just assume that decode will have to skip this
michael@0 713 // packet, i.e. assume 0 samples are decodable from this packet.
michael@0 714 blockSize = 0;
michael@0 715 mPrevVorbisBlockSize = 0;
michael@0 716 }
michael@0 717 long samples = mPrevVorbisBlockSize / 4 + blockSize / 4;
michael@0 718 mPrevVorbisBlockSize = blockSize;
michael@0 719 if (packet->granulepos == -1) {
michael@0 720 packet->granulepos = mGranulepos + samples;
michael@0 721 }
michael@0 722
michael@0 723 // Account for a partial last frame
michael@0 724 if (packet->e_o_s && packet->granulepos >= mGranulepos) {
michael@0 725 samples = packet->granulepos - mGranulepos;
michael@0 726 }
michael@0 727
michael@0 728 mGranulepos = packet->granulepos;
michael@0 729 RecordVorbisPacketSamples(packet, samples);
michael@0 730 return NS_OK;
michael@0 731 }
michael@0 732
michael@0 733 bool unknownGranulepos = last->granulepos == -1;
michael@0 734 int totalSamples = 0;
michael@0 735 for (int32_t i = mUnstamped.Length() - 1; i > 0; i--) {
michael@0 736 ogg_packet* packet = mUnstamped[i];
michael@0 737 ogg_packet* prev = mUnstamped[i-1];
michael@0 738 ogg_int64_t granulepos = packet->granulepos;
michael@0 739 NS_ASSERTION(granulepos != -1, "Must know granulepos!");
michael@0 740 long prevBlockSize = vorbis_packet_blocksize(&mInfo, prev);
michael@0 741 long blockSize = vorbis_packet_blocksize(&mInfo, packet);
michael@0 742
michael@0 743 if (blockSize < 0 || prevBlockSize < 0) {
michael@0 744 // On failure vorbis_packet_blocksize returns < 0. If we've got
michael@0 745 // a bad packet, we just assume that decode will have to skip this
michael@0 746 // packet, i.e. assume 0 samples are decodable from this packet.
michael@0 747 blockSize = 0;
michael@0 748 prevBlockSize = 0;
michael@0 749 }
michael@0 750
michael@0 751 long samples = prevBlockSize / 4 + blockSize / 4;
michael@0 752 totalSamples += samples;
michael@0 753 prev->granulepos = granulepos - samples;
michael@0 754 RecordVorbisPacketSamples(packet, samples);
michael@0 755 }
michael@0 756
michael@0 757 if (unknownGranulepos) {
michael@0 758 for (uint32_t i = 0; i < mUnstamped.Length(); i++) {
michael@0 759 ogg_packet* packet = mUnstamped[i];
michael@0 760 packet->granulepos += mGranulepos + totalSamples + 1;
michael@0 761 }
michael@0 762 }
michael@0 763
michael@0 764 ogg_packet* first = mUnstamped[0];
michael@0 765 long blockSize = vorbis_packet_blocksize(&mInfo, first);
michael@0 766 if (blockSize < 0) {
michael@0 767 mPrevVorbisBlockSize = 0;
michael@0 768 blockSize = 0;
michael@0 769 }
michael@0 770
michael@0 771 long samples = (mPrevVorbisBlockSize == 0) ? 0 :
michael@0 772 mPrevVorbisBlockSize / 4 + blockSize / 4;
michael@0 773 int64_t start = first->granulepos - samples;
michael@0 774 RecordVorbisPacketSamples(first, samples);
michael@0 775
michael@0 776 if (last->e_o_s && start < mGranulepos) {
michael@0 777 // We've calculated that there are more samples in this page than its
michael@0 778 // granulepos claims, and it's the last page in the stream. This is legal,
michael@0 779 // and we will need to prune the trailing samples when we come to decode it.
michael@0 780 // We must correct the timestamps so that they follow the last Vorbis page's
michael@0 781 // samples.
michael@0 782 int64_t pruned = mGranulepos - start;
michael@0 783 for (uint32_t i = 0; i < mUnstamped.Length() - 1; i++) {
michael@0 784 mUnstamped[i]->granulepos += pruned;
michael@0 785 }
michael@0 786 #ifdef VALIDATE_VORBIS_SAMPLE_CALCULATION
michael@0 787 mVorbisPacketSamples[last] -= pruned;
michael@0 788 #endif
michael@0 789 }
michael@0 790
michael@0 791 mPrevVorbisBlockSize = vorbis_packet_blocksize(&mInfo, last);
michael@0 792 mPrevVorbisBlockSize = std::max(static_cast<long>(0), mPrevVorbisBlockSize);
michael@0 793 mGranulepos = last->granulepos;
michael@0 794
michael@0 795 return NS_OK;
michael@0 796 }
michael@0 797
michael@0 798 #ifdef MOZ_OPUS
michael@0 799 OpusState::OpusState(ogg_page* aBosPage) :
michael@0 800 OggCodecState(aBosPage, true),
michael@0 801 mParser(nullptr),
michael@0 802 mDecoder(nullptr),
michael@0 803 mSkip(0),
michael@0 804 mPrevPacketGranulepos(0),
michael@0 805 mPrevPageGranulepos(0)
michael@0 806 {
michael@0 807 MOZ_COUNT_CTOR(OpusState);
michael@0 808 }
michael@0 809
michael@0 810 OpusState::~OpusState() {
michael@0 811 MOZ_COUNT_DTOR(OpusState);
michael@0 812 Reset();
michael@0 813
michael@0 814 if (mDecoder) {
michael@0 815 opus_multistream_decoder_destroy(mDecoder);
michael@0 816 mDecoder = nullptr;
michael@0 817 }
michael@0 818 }
michael@0 819
michael@0 820 nsresult OpusState::Reset()
michael@0 821 {
michael@0 822 return Reset(false);
michael@0 823 }
michael@0 824
michael@0 825 nsresult OpusState::Reset(bool aStart)
michael@0 826 {
michael@0 827 nsresult res = NS_OK;
michael@0 828
michael@0 829 if (mActive && mDecoder) {
michael@0 830 // Reset the decoder.
michael@0 831 opus_multistream_decoder_ctl(mDecoder, OPUS_RESET_STATE);
michael@0 832 // Let the seek logic handle pre-roll if we're not seeking to the start.
michael@0 833 mSkip = aStart ? mParser->mPreSkip : 0;
michael@0 834 // This lets us distinguish the first page being the last page vs. just
michael@0 835 // not having processed the previous page when we encounter the last page.
michael@0 836 mPrevPageGranulepos = aStart ? 0 : -1;
michael@0 837 mPrevPacketGranulepos = aStart ? 0 : -1;
michael@0 838 }
michael@0 839
michael@0 840 // Clear queued data.
michael@0 841 if (NS_FAILED(OggCodecState::Reset())) {
michael@0 842 return NS_ERROR_FAILURE;
michael@0 843 }
michael@0 844
michael@0 845 LOG(PR_LOG_DEBUG, ("Opus decoder reset, to skip %d", mSkip));
michael@0 846
michael@0 847 return res;
michael@0 848 }
michael@0 849
michael@0 850 bool OpusState::Init(void)
michael@0 851 {
michael@0 852 if (!mActive)
michael@0 853 return false;
michael@0 854
michael@0 855 int error;
michael@0 856
michael@0 857 NS_ASSERTION(mDecoder == nullptr, "leaking OpusDecoder");
michael@0 858
michael@0 859 mDecoder = opus_multistream_decoder_create(mParser->mRate,
michael@0 860 mParser->mChannels,
michael@0 861 mParser->mStreams,
michael@0 862 mParser->mCoupledStreams,
michael@0 863 mParser->mMappingTable,
michael@0 864 &error);
michael@0 865
michael@0 866 mSkip = mParser->mPreSkip;
michael@0 867
michael@0 868 LOG(PR_LOG_DEBUG, ("Opus decoder init, to skip %d", mSkip));
michael@0 869
michael@0 870 return error == OPUS_OK;
michael@0 871 }
michael@0 872
michael@0 873 bool OpusState::DecodeHeader(ogg_packet* aPacket)
michael@0 874 {
michael@0 875 nsAutoRef<ogg_packet> autoRelease(aPacket);
michael@0 876 switch(mPacketCount++) {
michael@0 877 // Parse the id header.
michael@0 878 case 0: {
michael@0 879 mParser = new OpusParser;
michael@0 880 if(!mParser->DecodeHeader(aPacket->packet, aPacket->bytes)) {
michael@0 881 return false;
michael@0 882 }
michael@0 883 mRate = mParser->mRate;
michael@0 884 mChannels = mParser->mChannels;
michael@0 885 mPreSkip = mParser->mPreSkip;
michael@0 886 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
michael@0 887 mGain = mParser->mGain;
michael@0 888 #else
michael@0 889 mGain_Q16 = mParser->mGain_Q16;
michael@0 890 #endif
michael@0 891 }
michael@0 892 break;
michael@0 893
michael@0 894 // Parse the metadata header.
michael@0 895 case 1: {
michael@0 896 if(!mParser->DecodeTags(aPacket->packet, aPacket->bytes)) {
michael@0 897 return false;
michael@0 898 }
michael@0 899 }
michael@0 900 break;
michael@0 901
michael@0 902 // We made it to the first data packet (which includes reconstructing
michael@0 903 // timestamps for it in PageIn). Success!
michael@0 904 default: {
michael@0 905 mDoneReadingHeaders = true;
michael@0 906 // Put it back on the queue so we can decode it.
michael@0 907 mPackets.PushFront(autoRelease.disown());
michael@0 908 }
michael@0 909 break;
michael@0 910 }
michael@0 911 return true;
michael@0 912 }
michael@0 913
michael@0 914 /* Construct and return a tags hashmap from our internal array */
michael@0 915 MetadataTags* OpusState::GetTags()
michael@0 916 {
michael@0 917 MetadataTags* tags;
michael@0 918
michael@0 919 tags = new MetadataTags;
michael@0 920 for (uint32_t i = 0; i < mParser->mTags.Length(); i++) {
michael@0 921 AddVorbisComment(tags, mParser->mTags[i].Data(), mParser->mTags[i].Length());
michael@0 922 }
michael@0 923
michael@0 924 return tags;
michael@0 925 }
michael@0 926
michael@0 927 /* Return the timestamp (in microseconds) equivalent to a granulepos. */
michael@0 928 int64_t OpusState::Time(int64_t aGranulepos)
michael@0 929 {
michael@0 930 if (!mActive)
michael@0 931 return -1;
michael@0 932
michael@0 933 return Time(mParser->mPreSkip, aGranulepos);
michael@0 934 }
michael@0 935
michael@0 936 int64_t OpusState::Time(int aPreSkip, int64_t aGranulepos)
michael@0 937 {
michael@0 938 if (aGranulepos < 0)
michael@0 939 return -1;
michael@0 940
michael@0 941 // Ogg Opus always runs at a granule rate of 48 kHz.
michael@0 942 CheckedInt64 t = CheckedInt64(aGranulepos - aPreSkip) * USECS_PER_S;
michael@0 943 return t.isValid() ? t.value() / 48000 : -1;
michael@0 944 }
michael@0 945
michael@0 946 bool OpusState::IsHeader(ogg_packet* aPacket)
michael@0 947 {
michael@0 948 return aPacket->bytes >= 16 &&
michael@0 949 (!memcmp(aPacket->packet, "OpusHead", 8) ||
michael@0 950 !memcmp(aPacket->packet, "OpusTags", 8));
michael@0 951 }
michael@0 952
michael@0 953 nsresult OpusState::PageIn(ogg_page* aPage)
michael@0 954 {
michael@0 955 if (!mActive)
michael@0 956 return NS_OK;
michael@0 957 NS_ASSERTION(static_cast<uint32_t>(ogg_page_serialno(aPage)) == mSerial,
michael@0 958 "Page must be for this stream!");
michael@0 959 if (ogg_stream_pagein(&mState, aPage) == -1)
michael@0 960 return NS_ERROR_FAILURE;
michael@0 961
michael@0 962 bool haveGranulepos;
michael@0 963 nsresult rv = PacketOutUntilGranulepos(haveGranulepos);
michael@0 964 if (NS_FAILED(rv) || !haveGranulepos || mPacketCount < 2)
michael@0 965 return rv;
michael@0 966 if(!ReconstructOpusGranulepos())
michael@0 967 return NS_ERROR_FAILURE;
michael@0 968 for (uint32_t i = 0; i < mUnstamped.Length(); i++) {
michael@0 969 ogg_packet* packet = mUnstamped[i];
michael@0 970 NS_ASSERTION(!IsHeader(packet), "Don't try to play a header packet");
michael@0 971 NS_ASSERTION(packet->granulepos != -1, "Packet should have a granulepos");
michael@0 972 mPackets.Append(packet);
michael@0 973 }
michael@0 974 mUnstamped.Clear();
michael@0 975 return NS_OK;
michael@0 976 }
michael@0 977
michael@0 978 // Helper method to return the change in granule position due to an Opus packet
michael@0 979 // (as distinct from the number of samples in the packet, which depends on the
michael@0 980 // decoder rate). It should work with a multistream Opus file, and continue to
michael@0 981 // work should we ever allow the decoder to decode at a rate other than 48 kHz.
michael@0 982 // It even works before we've created the actual Opus decoder.
michael@0 983 static int GetOpusDeltaGP(ogg_packet* packet)
michael@0 984 {
michael@0 985 int nframes;
michael@0 986 nframes = opus_packet_get_nb_frames(packet->packet, packet->bytes);
michael@0 987 if (nframes > 0) {
michael@0 988 return nframes*opus_packet_get_samples_per_frame(packet->packet, 48000);
michael@0 989 }
michael@0 990 NS_WARNING("Invalid Opus packet.");
michael@0 991 return nframes;
michael@0 992 }
michael@0 993
michael@0 994 bool OpusState::ReconstructOpusGranulepos(void)
michael@0 995 {
michael@0 996 NS_ASSERTION(mUnstamped.Length() > 0, "Must have unstamped packets");
michael@0 997 ogg_packet* last = mUnstamped[mUnstamped.Length()-1];
michael@0 998 NS_ASSERTION(last->e_o_s || last->granulepos > 0,
michael@0 999 "Must know last granulepos!");
michael@0 1000 int64_t gp;
michael@0 1001 // If this is the last page, and we've seen at least one previous page (or
michael@0 1002 // this is the first page)...
michael@0 1003 if (last->e_o_s) {
michael@0 1004 if (mPrevPageGranulepos != -1) {
michael@0 1005 // If this file only has one page and the final granule position is
michael@0 1006 // smaller than the pre-skip amount, we MUST reject the stream.
michael@0 1007 if (!mDoneReadingHeaders && last->granulepos < mPreSkip)
michael@0 1008 return false;
michael@0 1009 int64_t last_gp = last->granulepos;
michael@0 1010 gp = mPrevPageGranulepos;
michael@0 1011 // Loop through the packets forwards, adding the current packet's
michael@0 1012 // duration to the previous granulepos to get the value for the
michael@0 1013 // current packet.
michael@0 1014 for (uint32_t i = 0; i < mUnstamped.Length() - 1; ++i) {
michael@0 1015 ogg_packet* packet = mUnstamped[i];
michael@0 1016 int offset = GetOpusDeltaGP(packet);
michael@0 1017 // Check for error (negative offset) and overflow.
michael@0 1018 if (offset >= 0 && gp <= INT64_MAX - offset) {
michael@0 1019 gp += offset;
michael@0 1020 if (gp >= last_gp) {
michael@0 1021 NS_WARNING("Opus end trimming removed more than a full packet.");
michael@0 1022 // We were asked to remove a full packet's worth of data or more.
michael@0 1023 // Encoders SHOULD NOT produce streams like this, but we'll handle
michael@0 1024 // it for them anyway.
michael@0 1025 gp = last_gp;
michael@0 1026 for (uint32_t j = i+1; j < mUnstamped.Length(); ++j) {
michael@0 1027 OggCodecState::ReleasePacket(mUnstamped[j]);
michael@0 1028 }
michael@0 1029 mUnstamped.RemoveElementsAt(i+1, mUnstamped.Length() - (i+1));
michael@0 1030 last = packet;
michael@0 1031 last->e_o_s = 1;
michael@0 1032 }
michael@0 1033 }
michael@0 1034 packet->granulepos = gp;
michael@0 1035 }
michael@0 1036 mPrevPageGranulepos = last_gp;
michael@0 1037 return true;
michael@0 1038 } else {
michael@0 1039 NS_WARNING("No previous granule position to use for Opus end trimming.");
michael@0 1040 // If we don't have a previous granule position, fall through.
michael@0 1041 // We simply won't trim any samples from the end.
michael@0 1042 // TODO: Are we guaranteed to have seen a previous page if there is one?
michael@0 1043 }
michael@0 1044 }
michael@0 1045
michael@0 1046 gp = last->granulepos;
michael@0 1047 // Loop through the packets backwards, subtracting the next
michael@0 1048 // packet's duration from its granulepos to get the value
michael@0 1049 // for the current packet.
michael@0 1050 for (uint32_t i = mUnstamped.Length() - 1; i > 0; i--) {
michael@0 1051 int offset = GetOpusDeltaGP(mUnstamped[i]);
michael@0 1052 // Check for error (negative offset) and overflow.
michael@0 1053 if (offset >= 0) {
michael@0 1054 if (offset <= gp) {
michael@0 1055 gp -= offset;
michael@0 1056 } else {
michael@0 1057 // If the granule position of the first data page is smaller than the
michael@0 1058 // number of decodable audio samples on that page, then we MUST reject
michael@0 1059 // the stream.
michael@0 1060 if (!mDoneReadingHeaders)
michael@0 1061 return false;
michael@0 1062 // It's too late to reject the stream.
michael@0 1063 // If we get here, this almost certainly means the file has screwed-up
michael@0 1064 // timestamps somewhere after the first page.
michael@0 1065 NS_WARNING("Clamping negative Opus granulepos to zero.");
michael@0 1066 gp = 0;
michael@0 1067 }
michael@0 1068 }
michael@0 1069 mUnstamped[i - 1]->granulepos = gp;
michael@0 1070 }
michael@0 1071
michael@0 1072 // Check to make sure the first granule position is at least as large as the
michael@0 1073 // total number of samples decodable from the first page with completed
michael@0 1074 // packets. This requires looking at the duration of the first packet, too.
michael@0 1075 // We MUST reject such streams.
michael@0 1076 if (!mDoneReadingHeaders && GetOpusDeltaGP(mUnstamped[0]) > gp)
michael@0 1077 return false;
michael@0 1078 mPrevPageGranulepos = last->granulepos;
michael@0 1079 return true;
michael@0 1080 }
michael@0 1081 #endif /* MOZ_OPUS */
michael@0 1082
michael@0 1083 SkeletonState::SkeletonState(ogg_page* aBosPage) :
michael@0 1084 OggCodecState(aBosPage, true),
michael@0 1085 mVersion(0),
michael@0 1086 mPresentationTime(0),
michael@0 1087 mLength(0)
michael@0 1088 {
michael@0 1089 MOZ_COUNT_CTOR(SkeletonState);
michael@0 1090 }
michael@0 1091
michael@0 1092 SkeletonState::~SkeletonState()
michael@0 1093 {
michael@0 1094 MOZ_COUNT_DTOR(SkeletonState);
michael@0 1095 }
michael@0 1096
michael@0 1097 // Support for Ogg Skeleton 4.0, as per specification at:
michael@0 1098 // http://wiki.xiph.org/Ogg_Skeleton_4
michael@0 1099
michael@0 1100 // Minimum length in bytes of a Skeleton header packet.
michael@0 1101 static const long SKELETON_MIN_HEADER_LEN = 28;
michael@0 1102 static const long SKELETON_4_0_MIN_HEADER_LEN = 80;
michael@0 1103
michael@0 1104 // Minimum length in bytes of a Skeleton 4.0 index packet.
michael@0 1105 static const long SKELETON_4_0_MIN_INDEX_LEN = 42;
michael@0 1106
michael@0 1107 // Minimum possible size of a compressed index keypoint.
michael@0 1108 static const size_t MIN_KEY_POINT_SIZE = 2;
michael@0 1109
michael@0 1110 // Byte offset of the major and minor version numbers in the
michael@0 1111 // Ogg Skeleton 4.0 header packet.
michael@0 1112 static const size_t SKELETON_VERSION_MAJOR_OFFSET = 8;
michael@0 1113 static const size_t SKELETON_VERSION_MINOR_OFFSET = 10;
michael@0 1114
michael@0 1115 // Byte-offsets of the presentation time numerator and denominator
michael@0 1116 static const size_t SKELETON_PRESENTATION_TIME_NUMERATOR_OFFSET = 12;
michael@0 1117 static const size_t SKELETON_PRESENTATION_TIME_DENOMINATOR_OFFSET = 20;
michael@0 1118
michael@0 1119 // Byte-offsets of the length of file field in the Skeleton 4.0 header packet.
michael@0 1120 static const size_t SKELETON_FILE_LENGTH_OFFSET = 64;
michael@0 1121
michael@0 1122 // Byte-offsets of the fields in the Skeleton index packet.
michael@0 1123 static const size_t INDEX_SERIALNO_OFFSET = 6;
michael@0 1124 static const size_t INDEX_NUM_KEYPOINTS_OFFSET = 10;
michael@0 1125 static const size_t INDEX_TIME_DENOM_OFFSET = 18;
michael@0 1126 static const size_t INDEX_FIRST_NUMER_OFFSET = 26;
michael@0 1127 static const size_t INDEX_LAST_NUMER_OFFSET = 34;
michael@0 1128 static const size_t INDEX_KEYPOINT_OFFSET = 42;
michael@0 1129
michael@0 1130 static bool IsSkeletonBOS(ogg_packet* aPacket)
michael@0 1131 {
michael@0 1132 return aPacket->bytes >= SKELETON_MIN_HEADER_LEN &&
michael@0 1133 memcmp(reinterpret_cast<char*>(aPacket->packet), "fishead", 8) == 0;
michael@0 1134 }
michael@0 1135
michael@0 1136 static bool IsSkeletonIndex(ogg_packet* aPacket)
michael@0 1137 {
michael@0 1138 return aPacket->bytes >= SKELETON_4_0_MIN_INDEX_LEN &&
michael@0 1139 memcmp(reinterpret_cast<char*>(aPacket->packet), "index", 5) == 0;
michael@0 1140 }
michael@0 1141
michael@0 1142 // Reads a variable length encoded integer at p. Will not read
michael@0 1143 // past aLimit. Returns pointer to character after end of integer.
michael@0 1144 static const unsigned char* ReadVariableLengthInt(const unsigned char* p,
michael@0 1145 const unsigned char* aLimit,
michael@0 1146 int64_t& n)
michael@0 1147 {
michael@0 1148 int shift = 0;
michael@0 1149 int64_t byte = 0;
michael@0 1150 n = 0;
michael@0 1151 while (p < aLimit &&
michael@0 1152 (byte & 0x80) != 0x80 &&
michael@0 1153 shift < 57)
michael@0 1154 {
michael@0 1155 byte = static_cast<int64_t>(*p);
michael@0 1156 n |= ((byte & 0x7f) << shift);
michael@0 1157 shift += 7;
michael@0 1158 p++;
michael@0 1159 }
michael@0 1160 return p;
michael@0 1161 }
michael@0 1162
michael@0 1163 bool SkeletonState::DecodeIndex(ogg_packet* aPacket)
michael@0 1164 {
michael@0 1165 NS_ASSERTION(aPacket->bytes >= SKELETON_4_0_MIN_INDEX_LEN,
michael@0 1166 "Index must be at least minimum size");
michael@0 1167 if (!mActive) {
michael@0 1168 return false;
michael@0 1169 }
michael@0 1170
michael@0 1171 uint32_t serialno = LittleEndian::readUint32(aPacket->packet + INDEX_SERIALNO_OFFSET);
michael@0 1172 int64_t numKeyPoints = LittleEndian::readInt64(aPacket->packet + INDEX_NUM_KEYPOINTS_OFFSET);
michael@0 1173
michael@0 1174 int64_t endTime = 0, startTime = 0;
michael@0 1175 const unsigned char* p = aPacket->packet;
michael@0 1176
michael@0 1177 int64_t timeDenom = LittleEndian::readInt64(aPacket->packet + INDEX_TIME_DENOM_OFFSET);
michael@0 1178 if (timeDenom == 0) {
michael@0 1179 LOG(PR_LOG_DEBUG, ("Ogg Skeleton Index packet for stream %u has 0 "
michael@0 1180 "timestamp denominator.", serialno));
michael@0 1181 return (mActive = false);
michael@0 1182 }
michael@0 1183
michael@0 1184 // Extract the start time.
michael@0 1185 CheckedInt64 t = CheckedInt64(LittleEndian::readInt64(p + INDEX_FIRST_NUMER_OFFSET)) * USECS_PER_S;
michael@0 1186 if (!t.isValid()) {
michael@0 1187 return (mActive = false);
michael@0 1188 } else {
michael@0 1189 startTime = t.value() / timeDenom;
michael@0 1190 }
michael@0 1191
michael@0 1192 // Extract the end time.
michael@0 1193 t = LittleEndian::readInt64(p + INDEX_LAST_NUMER_OFFSET) * USECS_PER_S;
michael@0 1194 if (!t.isValid()) {
michael@0 1195 return (mActive = false);
michael@0 1196 } else {
michael@0 1197 endTime = t.value() / timeDenom;
michael@0 1198 }
michael@0 1199
michael@0 1200 // Check the numKeyPoints value read, ensure we're not going to run out of
michael@0 1201 // memory while trying to decode the index packet.
michael@0 1202 CheckedInt64 minPacketSize = (CheckedInt64(numKeyPoints) * MIN_KEY_POINT_SIZE) + INDEX_KEYPOINT_OFFSET;
michael@0 1203 if (!minPacketSize.isValid())
michael@0 1204 {
michael@0 1205 return (mActive = false);
michael@0 1206 }
michael@0 1207
michael@0 1208 int64_t sizeofIndex = aPacket->bytes - INDEX_KEYPOINT_OFFSET;
michael@0 1209 int64_t maxNumKeyPoints = sizeofIndex / MIN_KEY_POINT_SIZE;
michael@0 1210 if (aPacket->bytes < minPacketSize.value() ||
michael@0 1211 numKeyPoints > maxNumKeyPoints ||
michael@0 1212 numKeyPoints < 0)
michael@0 1213 {
michael@0 1214 // Packet size is less than the theoretical minimum size, or the packet is
michael@0 1215 // claiming to store more keypoints than it's capable of storing. This means
michael@0 1216 // that the numKeyPoints field is too large or small for the packet to
michael@0 1217 // possibly contain as many packets as it claims to, so the numKeyPoints
michael@0 1218 // field is possibly malicious. Don't try decoding this index, we may run
michael@0 1219 // out of memory.
michael@0 1220 LOG(PR_LOG_DEBUG, ("Possibly malicious number of key points reported "
michael@0 1221 "(%lld) in index packet for stream %u.",
michael@0 1222 numKeyPoints,
michael@0 1223 serialno));
michael@0 1224 return (mActive = false);
michael@0 1225 }
michael@0 1226
michael@0 1227 nsAutoPtr<nsKeyFrameIndex> keyPoints(new nsKeyFrameIndex(startTime, endTime));
michael@0 1228
michael@0 1229 p = aPacket->packet + INDEX_KEYPOINT_OFFSET;
michael@0 1230 const unsigned char* limit = aPacket->packet + aPacket->bytes;
michael@0 1231 int64_t numKeyPointsRead = 0;
michael@0 1232 CheckedInt64 offset = 0;
michael@0 1233 CheckedInt64 time = 0;
michael@0 1234 while (p < limit &&
michael@0 1235 numKeyPointsRead < numKeyPoints)
michael@0 1236 {
michael@0 1237 int64_t delta = 0;
michael@0 1238 p = ReadVariableLengthInt(p, limit, delta);
michael@0 1239 offset += delta;
michael@0 1240 if (p == limit ||
michael@0 1241 !offset.isValid() ||
michael@0 1242 offset.value() > mLength ||
michael@0 1243 offset.value() < 0)
michael@0 1244 {
michael@0 1245 return (mActive = false);
michael@0 1246 }
michael@0 1247 p = ReadVariableLengthInt(p, limit, delta);
michael@0 1248 time += delta;
michael@0 1249 if (!time.isValid() ||
michael@0 1250 time.value() > endTime ||
michael@0 1251 time.value() < startTime)
michael@0 1252 {
michael@0 1253 return (mActive = false);
michael@0 1254 }
michael@0 1255 CheckedInt64 timeUsecs = time * USECS_PER_S;
michael@0 1256 if (!timeUsecs.isValid())
michael@0 1257 return mActive = false;
michael@0 1258 timeUsecs /= timeDenom;
michael@0 1259 keyPoints->Add(offset.value(), timeUsecs.value());
michael@0 1260 numKeyPointsRead++;
michael@0 1261 }
michael@0 1262
michael@0 1263 int32_t keyPointsRead = keyPoints->Length();
michael@0 1264 if (keyPointsRead > 0) {
michael@0 1265 mIndex.Put(serialno, keyPoints.forget());
michael@0 1266 }
michael@0 1267
michael@0 1268 LOG(PR_LOG_DEBUG, ("Loaded %d keypoints for Skeleton on stream %u",
michael@0 1269 keyPointsRead, serialno));
michael@0 1270 return true;
michael@0 1271 }
michael@0 1272
michael@0 1273 nsresult SkeletonState::IndexedSeekTargetForTrack(uint32_t aSerialno,
michael@0 1274 int64_t aTarget,
michael@0 1275 nsKeyPoint& aResult)
michael@0 1276 {
michael@0 1277 nsKeyFrameIndex* index = nullptr;
michael@0 1278 mIndex.Get(aSerialno, &index);
michael@0 1279
michael@0 1280 if (!index ||
michael@0 1281 index->Length() == 0 ||
michael@0 1282 aTarget < index->mStartTime ||
michael@0 1283 aTarget > index->mEndTime)
michael@0 1284 {
michael@0 1285 return NS_ERROR_FAILURE;
michael@0 1286 }
michael@0 1287
michael@0 1288 // Binary search to find the last key point with time less than target.
michael@0 1289 int start = 0;
michael@0 1290 int end = index->Length() - 1;
michael@0 1291 while (end > start) {
michael@0 1292 int mid = start + ((end - start + 1) >> 1);
michael@0 1293 if (index->Get(mid).mTime == aTarget) {
michael@0 1294 start = mid;
michael@0 1295 break;
michael@0 1296 } else if (index->Get(mid).mTime < aTarget) {
michael@0 1297 start = mid;
michael@0 1298 } else {
michael@0 1299 end = mid - 1;
michael@0 1300 }
michael@0 1301 }
michael@0 1302
michael@0 1303 aResult = index->Get(start);
michael@0 1304 NS_ASSERTION(aResult.mTime <= aTarget, "Result should have time <= target");
michael@0 1305 return NS_OK;
michael@0 1306 }
michael@0 1307
michael@0 1308 nsresult SkeletonState::IndexedSeekTarget(int64_t aTarget,
michael@0 1309 nsTArray<uint32_t>& aTracks,
michael@0 1310 nsSeekTarget& aResult)
michael@0 1311 {
michael@0 1312 if (!mActive || mVersion < SKELETON_VERSION(4,0)) {
michael@0 1313 return NS_ERROR_FAILURE;
michael@0 1314 }
michael@0 1315 // Loop over all requested tracks' indexes, and get the keypoint for that
michael@0 1316 // seek target. Record the keypoint with the lowest offset, this will be
michael@0 1317 // our seek result. User must seek to the one with lowest offset to ensure we
michael@0 1318 // pass "keyframes" on all tracks when we decode forwards to the seek target.
michael@0 1319 nsSeekTarget r;
michael@0 1320 for (uint32_t i=0; i<aTracks.Length(); i++) {
michael@0 1321 nsKeyPoint k;
michael@0 1322 if (NS_SUCCEEDED(IndexedSeekTargetForTrack(aTracks[i], aTarget, k)) &&
michael@0 1323 k.mOffset < r.mKeyPoint.mOffset)
michael@0 1324 {
michael@0 1325 r.mKeyPoint = k;
michael@0 1326 r.mSerial = aTracks[i];
michael@0 1327 }
michael@0 1328 }
michael@0 1329 if (r.IsNull()) {
michael@0 1330 return NS_ERROR_FAILURE;
michael@0 1331 }
michael@0 1332 LOG(PR_LOG_DEBUG, ("Indexed seek target for time %lld is offset %lld",
michael@0 1333 aTarget, r.mKeyPoint.mOffset));
michael@0 1334 aResult = r;
michael@0 1335 return NS_OK;
michael@0 1336 }
michael@0 1337
michael@0 1338 nsresult SkeletonState::GetDuration(const nsTArray<uint32_t>& aTracks,
michael@0 1339 int64_t& aDuration)
michael@0 1340 {
michael@0 1341 if (!mActive ||
michael@0 1342 mVersion < SKELETON_VERSION(4,0) ||
michael@0 1343 !HasIndex() ||
michael@0 1344 aTracks.Length() == 0)
michael@0 1345 {
michael@0 1346 return NS_ERROR_FAILURE;
michael@0 1347 }
michael@0 1348 int64_t endTime = INT64_MIN;
michael@0 1349 int64_t startTime = INT64_MAX;
michael@0 1350 for (uint32_t i=0; i<aTracks.Length(); i++) {
michael@0 1351 nsKeyFrameIndex* index = nullptr;
michael@0 1352 mIndex.Get(aTracks[i], &index);
michael@0 1353 if (!index) {
michael@0 1354 // Can't get the timestamps for one of the required tracks, fail.
michael@0 1355 return NS_ERROR_FAILURE;
michael@0 1356 }
michael@0 1357 if (index->mEndTime > endTime) {
michael@0 1358 endTime = index->mEndTime;
michael@0 1359 }
michael@0 1360 if (index->mStartTime < startTime) {
michael@0 1361 startTime = index->mStartTime;
michael@0 1362 }
michael@0 1363 }
michael@0 1364 NS_ASSERTION(endTime > startTime, "Duration must be positive");
michael@0 1365 CheckedInt64 duration = CheckedInt64(endTime) - startTime;
michael@0 1366 aDuration = duration.isValid() ? duration.value() : 0;
michael@0 1367 return duration.isValid() ? NS_OK : NS_ERROR_FAILURE;
michael@0 1368 }
michael@0 1369
michael@0 1370 bool SkeletonState::DecodeHeader(ogg_packet* aPacket)
michael@0 1371 {
michael@0 1372 nsAutoRef<ogg_packet> autoRelease(aPacket);
michael@0 1373 if (IsSkeletonBOS(aPacket)) {
michael@0 1374 uint16_t verMajor = LittleEndian::readUint16(aPacket->packet + SKELETON_VERSION_MAJOR_OFFSET);
michael@0 1375 uint16_t verMinor = LittleEndian::readUint16(aPacket->packet + SKELETON_VERSION_MINOR_OFFSET);
michael@0 1376
michael@0 1377 // Read the presentation time. We read this before the version check as the
michael@0 1378 // presentation time exists in all versions.
michael@0 1379 int64_t n = LittleEndian::readInt64(aPacket->packet + SKELETON_PRESENTATION_TIME_NUMERATOR_OFFSET);
michael@0 1380 int64_t d = LittleEndian::readInt64(aPacket->packet + SKELETON_PRESENTATION_TIME_DENOMINATOR_OFFSET);
michael@0 1381 mPresentationTime = d == 0 ? 0 : (static_cast<float>(n) / static_cast<float>(d)) * USECS_PER_S;
michael@0 1382
michael@0 1383 mVersion = SKELETON_VERSION(verMajor, verMinor);
michael@0 1384 // We can only care to parse Skeleton version 4.0+.
michael@0 1385 if (mVersion < SKELETON_VERSION(4,0) ||
michael@0 1386 mVersion >= SKELETON_VERSION(5,0) ||
michael@0 1387 aPacket->bytes < SKELETON_4_0_MIN_HEADER_LEN)
michael@0 1388 return false;
michael@0 1389
michael@0 1390 // Extract the segment length.
michael@0 1391 mLength = LittleEndian::readInt64(aPacket->packet + SKELETON_FILE_LENGTH_OFFSET);
michael@0 1392
michael@0 1393 LOG(PR_LOG_DEBUG, ("Skeleton segment length: %lld", mLength));
michael@0 1394
michael@0 1395 // Initialize the serialno-to-index map.
michael@0 1396 return true;
michael@0 1397 } else if (IsSkeletonIndex(aPacket) && mVersion >= SKELETON_VERSION(4,0)) {
michael@0 1398 return DecodeIndex(aPacket);
michael@0 1399 } else if (aPacket->e_o_s) {
michael@0 1400 mDoneReadingHeaders = true;
michael@0 1401 return true;
michael@0 1402 }
michael@0 1403 return true;
michael@0 1404 }
michael@0 1405
michael@0 1406
michael@0 1407 } // namespace mozilla
michael@0 1408

mercurial