content/media/ogg/OggReader.cpp

Fri, 16 Jan 2015 04:50:19 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 04:50:19 +0100
branch
TOR_BUG_9701
changeset 13
44a2da4a2ab2
permissions
-rw-r--r--

Replace accessor implementation with direct member state manipulation, by
request https://trac.torproject.org/projects/tor/ticket/9701#comment:32

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 "mozilla/DebugOnly.h"
michael@0 8
michael@0 9 #include "nsError.h"
michael@0 10 #include "MediaDecoderStateMachine.h"
michael@0 11 #include "MediaDecoder.h"
michael@0 12 #include "OggReader.h"
michael@0 13 #include "VideoUtils.h"
michael@0 14 #include "theora/theoradec.h"
michael@0 15 #include <algorithm>
michael@0 16 #ifdef MOZ_OPUS
michael@0 17 #include "opus/opus.h"
michael@0 18 extern "C" {
michael@0 19 #include "opus/opus_multistream.h"
michael@0 20 }
michael@0 21 #endif
michael@0 22 #include "mozilla/dom/TimeRanges.h"
michael@0 23 #include "mozilla/TimeStamp.h"
michael@0 24 #include "VorbisUtils.h"
michael@0 25 #include "MediaMetadataManager.h"
michael@0 26 #include "nsISeekableStream.h"
michael@0 27 #include "gfx2DGlue.h"
michael@0 28
michael@0 29 using namespace mozilla::gfx;
michael@0 30
michael@0 31 namespace mozilla {
michael@0 32
michael@0 33 // On B2G estimate the buffered ranges rather than calculating them explicitly.
michael@0 34 // This prevents us doing I/O on the main thread, which is prohibited in B2G.
michael@0 35 #ifdef MOZ_WIDGET_GONK
michael@0 36 #define OGG_ESTIMATE_BUFFERED 1
michael@0 37 #endif
michael@0 38
michael@0 39 // Un-comment to enable logging of seek bisections.
michael@0 40 //#define SEEK_LOGGING
michael@0 41
michael@0 42 #ifdef PR_LOGGING
michael@0 43 extern PRLogModuleInfo* gMediaDecoderLog;
michael@0 44 #define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
michael@0 45 #ifdef SEEK_LOGGING
michael@0 46 #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
michael@0 47 #else
michael@0 48 #define SEEK_LOG(type, msg)
michael@0 49 #endif
michael@0 50 #else
michael@0 51 #define LOG(type, msg)
michael@0 52 #define SEEK_LOG(type, msg)
michael@0 53 #endif
michael@0 54
michael@0 55 // The number of microseconds of "fuzz" we use in a bisection search over
michael@0 56 // HTTP. When we're seeking with fuzz, we'll stop the search if a bisection
michael@0 57 // lands between the seek target and SEEK_FUZZ_USECS microseconds before the
michael@0 58 // seek target. This is becaue it's usually quicker to just keep downloading
michael@0 59 // from an exisiting connection than to do another bisection inside that
michael@0 60 // small range, which would open a new HTTP connetion.
michael@0 61 static const uint32_t SEEK_FUZZ_USECS = 500000;
michael@0 62
michael@0 63 // The number of microseconds of "pre-roll" we use for Opus streams.
michael@0 64 // The specification recommends 80 ms.
michael@0 65 #ifdef MOZ_OPUS
michael@0 66 static const int64_t SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
michael@0 67 #endif /* MOZ_OPUS */
michael@0 68
michael@0 69 enum PageSyncResult {
michael@0 70 PAGE_SYNC_ERROR = 1,
michael@0 71 PAGE_SYNC_END_OF_RANGE= 2,
michael@0 72 PAGE_SYNC_OK = 3
michael@0 73 };
michael@0 74
michael@0 75 // Reads a page from the media resource.
michael@0 76 static PageSyncResult
michael@0 77 PageSync(MediaResource* aResource,
michael@0 78 ogg_sync_state* aState,
michael@0 79 bool aCachedDataOnly,
michael@0 80 int64_t aOffset,
michael@0 81 int64_t aEndOffset,
michael@0 82 ogg_page* aPage,
michael@0 83 int& aSkippedBytes);
michael@0 84
michael@0 85 // Chunk size to read when reading Ogg files. Average Ogg page length
michael@0 86 // is about 4300 bytes, so we read the file in chunks larger than that.
michael@0 87 static const int PAGE_STEP = 8192;
michael@0 88
michael@0 89 OggReader::OggReader(AbstractMediaDecoder* aDecoder)
michael@0 90 : MediaDecoderReader(aDecoder),
michael@0 91 mMonitor("OggReader"),
michael@0 92 mTheoraState(nullptr),
michael@0 93 mVorbisState(nullptr),
michael@0 94 #ifdef MOZ_OPUS
michael@0 95 mOpusState(nullptr),
michael@0 96 mOpusEnabled(MediaDecoder::IsOpusEnabled()),
michael@0 97 #endif /* MOZ_OPUS */
michael@0 98 mSkeletonState(nullptr),
michael@0 99 mVorbisSerial(0),
michael@0 100 mOpusSerial(0),
michael@0 101 mTheoraSerial(0),
michael@0 102 mOpusPreSkip(0),
michael@0 103 mIsChained(false),
michael@0 104 mDecodedAudioFrames(0)
michael@0 105 {
michael@0 106 MOZ_COUNT_CTOR(OggReader);
michael@0 107 memset(&mTheoraInfo, 0, sizeof(mTheoraInfo));
michael@0 108 }
michael@0 109
michael@0 110 OggReader::~OggReader()
michael@0 111 {
michael@0 112 ogg_sync_clear(&mOggState);
michael@0 113 MOZ_COUNT_DTOR(OggReader);
michael@0 114 }
michael@0 115
michael@0 116 nsresult OggReader::Init(MediaDecoderReader* aCloneDonor) {
michael@0 117 int ret = ogg_sync_init(&mOggState);
michael@0 118 NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
michael@0 119 return NS_OK;
michael@0 120 }
michael@0 121
michael@0 122 nsresult OggReader::ResetDecode()
michael@0 123 {
michael@0 124 return ResetDecode(false);
michael@0 125 }
michael@0 126
michael@0 127 nsresult OggReader::ResetDecode(bool start)
michael@0 128 {
michael@0 129 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 130 nsresult res = NS_OK;
michael@0 131
michael@0 132 if (NS_FAILED(MediaDecoderReader::ResetDecode())) {
michael@0 133 res = NS_ERROR_FAILURE;
michael@0 134 }
michael@0 135
michael@0 136 // Discard any previously buffered packets/pages.
michael@0 137 ogg_sync_reset(&mOggState);
michael@0 138 if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
michael@0 139 res = NS_ERROR_FAILURE;
michael@0 140 }
michael@0 141 #ifdef MOZ_OPUS
michael@0 142 if (mOpusState && NS_FAILED(mOpusState->Reset(start))) {
michael@0 143 res = NS_ERROR_FAILURE;
michael@0 144 }
michael@0 145 #endif /* MOZ_OPUS */
michael@0 146 if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
michael@0 147 res = NS_ERROR_FAILURE;
michael@0 148 }
michael@0 149
michael@0 150 return res;
michael@0 151 }
michael@0 152
michael@0 153 bool OggReader::ReadHeaders(OggCodecState* aState)
michael@0 154 {
michael@0 155 while (!aState->DoneReadingHeaders()) {
michael@0 156 ogg_packet* packet = NextOggPacket(aState);
michael@0 157 // DecodeHeader is responsible for releasing packet.
michael@0 158 if (!packet || !aState->DecodeHeader(packet)) {
michael@0 159 aState->Deactivate();
michael@0 160 return false;
michael@0 161 }
michael@0 162 }
michael@0 163 return aState->Init();
michael@0 164 }
michael@0 165
michael@0 166 void OggReader::BuildSerialList(nsTArray<uint32_t>& aTracks)
michael@0 167 {
michael@0 168 if (HasVideo()) {
michael@0 169 aTracks.AppendElement(mTheoraState->mSerial);
michael@0 170 }
michael@0 171 if (HasAudio()) {
michael@0 172 if (mVorbisState) {
michael@0 173 aTracks.AppendElement(mVorbisState->mSerial);
michael@0 174 #ifdef MOZ_OPUS
michael@0 175 } else if (mOpusState) {
michael@0 176 aTracks.AppendElement(mOpusState->mSerial);
michael@0 177 #endif /* MOZ_OPUS */
michael@0 178 }
michael@0 179 }
michael@0 180 }
michael@0 181
michael@0 182 nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
michael@0 183 MetadataTags** aTags)
michael@0 184 {
michael@0 185 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 186
michael@0 187 // We read packets until all bitstreams have read all their header packets.
michael@0 188 // We record the offset of the first non-header page so that we know
michael@0 189 // what page to seek to when seeking to the media start.
michael@0 190
michael@0 191 NS_ASSERTION(aTags, "Called with null MetadataTags**.");
michael@0 192 *aTags = nullptr;
michael@0 193
michael@0 194 ogg_page page;
michael@0 195 nsAutoTArray<OggCodecState*,4> bitstreams;
michael@0 196 bool readAllBOS = false;
michael@0 197 while (!readAllBOS) {
michael@0 198 if (!ReadOggPage(&page)) {
michael@0 199 // Some kind of error...
michael@0 200 break;
michael@0 201 }
michael@0 202
michael@0 203 int serial = ogg_page_serialno(&page);
michael@0 204 OggCodecState* codecState = 0;
michael@0 205
michael@0 206 if (!ogg_page_bos(&page)) {
michael@0 207 // We've encountered a non Beginning Of Stream page. No more BOS pages
michael@0 208 // can follow in this Ogg segment, so there will be no other bitstreams
michael@0 209 // in the Ogg (unless it's invalid).
michael@0 210 readAllBOS = true;
michael@0 211 } else if (!mCodecStore.Contains(serial)) {
michael@0 212 // We've not encountered a stream with this serial number before. Create
michael@0 213 // an OggCodecState to demux it, and map that to the OggCodecState
michael@0 214 // in mCodecStates.
michael@0 215 codecState = OggCodecState::Create(&page);
michael@0 216 mCodecStore.Add(serial, codecState);
michael@0 217 bitstreams.AppendElement(codecState);
michael@0 218 if (codecState &&
michael@0 219 codecState->GetType() == OggCodecState::TYPE_VORBIS &&
michael@0 220 !mVorbisState)
michael@0 221 {
michael@0 222 // First Vorbis bitstream, we'll play this one. Subsequent Vorbis
michael@0 223 // bitstreams will be ignored.
michael@0 224 mVorbisState = static_cast<VorbisState*>(codecState);
michael@0 225 }
michael@0 226 if (codecState &&
michael@0 227 codecState->GetType() == OggCodecState::TYPE_THEORA &&
michael@0 228 !mTheoraState)
michael@0 229 {
michael@0 230 // First Theora bitstream, we'll play this one. Subsequent Theora
michael@0 231 // bitstreams will be ignored.
michael@0 232 mTheoraState = static_cast<TheoraState*>(codecState);
michael@0 233 }
michael@0 234 #ifdef MOZ_OPUS
michael@0 235 if (codecState &&
michael@0 236 codecState->GetType() == OggCodecState::TYPE_OPUS &&
michael@0 237 !mOpusState)
michael@0 238 {
michael@0 239 if (mOpusEnabled) {
michael@0 240 mOpusState = static_cast<OpusState*>(codecState);
michael@0 241 } else {
michael@0 242 NS_WARNING("Opus decoding disabled."
michael@0 243 " See media.opus.enabled in about:config");
michael@0 244 }
michael@0 245 }
michael@0 246 #endif /* MOZ_OPUS */
michael@0 247 if (codecState &&
michael@0 248 codecState->GetType() == OggCodecState::TYPE_SKELETON &&
michael@0 249 !mSkeletonState)
michael@0 250 {
michael@0 251 mSkeletonState = static_cast<SkeletonState*>(codecState);
michael@0 252 }
michael@0 253 }
michael@0 254
michael@0 255 codecState = mCodecStore.Get(serial);
michael@0 256 NS_ENSURE_TRUE(codecState != nullptr, NS_ERROR_FAILURE);
michael@0 257
michael@0 258 if (NS_FAILED(codecState->PageIn(&page))) {
michael@0 259 return NS_ERROR_FAILURE;
michael@0 260 }
michael@0 261 }
michael@0 262
michael@0 263 // We've read all BOS pages, so we know the streams contained in the media.
michael@0 264 // Now process all available header packets in the active Theora, Vorbis and
michael@0 265 // Skeleton streams.
michael@0 266
michael@0 267 // Deactivate any non-primary bitstreams.
michael@0 268 for (uint32_t i = 0; i < bitstreams.Length(); i++) {
michael@0 269 OggCodecState* s = bitstreams[i];
michael@0 270 if (s != mVorbisState &&
michael@0 271 #ifdef MOZ_OPUS
michael@0 272 s != mOpusState &&
michael@0 273 #endif /* MOZ_OPUS */
michael@0 274 s != mTheoraState && s != mSkeletonState) {
michael@0 275 s->Deactivate();
michael@0 276 }
michael@0 277 }
michael@0 278
michael@0 279 if (mTheoraState && ReadHeaders(mTheoraState)) {
michael@0 280 nsIntRect picture = nsIntRect(mTheoraState->mInfo.pic_x,
michael@0 281 mTheoraState->mInfo.pic_y,
michael@0 282 mTheoraState->mInfo.pic_width,
michael@0 283 mTheoraState->mInfo.pic_height);
michael@0 284
michael@0 285 nsIntSize displaySize = nsIntSize(mTheoraState->mInfo.pic_width,
michael@0 286 mTheoraState->mInfo.pic_height);
michael@0 287
michael@0 288 // Apply the aspect ratio to produce the intrinsic display size we report
michael@0 289 // to the element.
michael@0 290 ScaleDisplayByAspectRatio(displaySize, mTheoraState->mPixelAspectRatio);
michael@0 291
michael@0 292 nsIntSize frameSize(mTheoraState->mInfo.frame_width,
michael@0 293 mTheoraState->mInfo.frame_height);
michael@0 294 if (IsValidVideoRegion(frameSize, picture, displaySize)) {
michael@0 295 // Video track's frame sizes will not overflow. Activate the video track.
michael@0 296 mInfo.mVideo.mHasVideo = true;
michael@0 297 mInfo.mVideo.mDisplay = displaySize;
michael@0 298 mPicture = picture;
michael@0 299
michael@0 300 VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
michael@0 301 if (container) {
michael@0 302 container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height),
michael@0 303 nullptr,
michael@0 304 TimeStamp::Now());
michael@0 305 }
michael@0 306
michael@0 307 // Copy Theora info data for time computations on other threads.
michael@0 308 memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));
michael@0 309 mTheoraSerial = mTheoraState->mSerial;
michael@0 310 }
michael@0 311 }
michael@0 312
michael@0 313 if (mVorbisState && ReadHeaders(mVorbisState)) {
michael@0 314 mInfo.mAudio.mHasAudio = true;
michael@0 315 mInfo.mAudio.mRate = mVorbisState->mInfo.rate;
michael@0 316 mInfo.mAudio.mChannels = mVorbisState->mInfo.channels;
michael@0 317 // Copy Vorbis info data for time computations on other threads.
michael@0 318 memcpy(&mVorbisInfo, &mVorbisState->mInfo, sizeof(mVorbisInfo));
michael@0 319 mVorbisInfo.codec_setup = nullptr;
michael@0 320 mVorbisSerial = mVorbisState->mSerial;
michael@0 321 *aTags = mVorbisState->GetTags();
michael@0 322 } else {
michael@0 323 memset(&mVorbisInfo, 0, sizeof(mVorbisInfo));
michael@0 324 }
michael@0 325 #ifdef MOZ_OPUS
michael@0 326 if (mOpusState && ReadHeaders(mOpusState)) {
michael@0 327 mInfo.mAudio.mHasAudio = true;
michael@0 328 mInfo.mAudio.mRate = mOpusState->mRate;
michael@0 329 mInfo.mAudio.mChannels = mOpusState->mChannels;
michael@0 330 mOpusSerial = mOpusState->mSerial;
michael@0 331 mOpusPreSkip = mOpusState->mPreSkip;
michael@0 332
michael@0 333 *aTags = mOpusState->GetTags();
michael@0 334 }
michael@0 335 #endif
michael@0 336 if (mSkeletonState) {
michael@0 337 if (!HasAudio() && !HasVideo()) {
michael@0 338 // We have a skeleton track, but no audio or video, may as well disable
michael@0 339 // the skeleton, we can't do anything useful with this media.
michael@0 340 mSkeletonState->Deactivate();
michael@0 341 } else if (ReadHeaders(mSkeletonState) && mSkeletonState->HasIndex()) {
michael@0 342 // Extract the duration info out of the index, so we don't need to seek to
michael@0 343 // the end of resource to get it.
michael@0 344 nsAutoTArray<uint32_t, 2> tracks;
michael@0 345 BuildSerialList(tracks);
michael@0 346 int64_t duration = 0;
michael@0 347 if (NS_SUCCEEDED(mSkeletonState->GetDuration(tracks, duration))) {
michael@0 348 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 349 mDecoder->SetMediaDuration(duration);
michael@0 350 LOG(PR_LOG_DEBUG, ("Got duration from Skeleton index %lld", duration));
michael@0 351 }
michael@0 352 }
michael@0 353 }
michael@0 354
michael@0 355 if (HasAudio() || HasVideo()) {
michael@0 356 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 357
michael@0 358 MediaResource* resource = mDecoder->GetResource();
michael@0 359 if (mDecoder->GetMediaDuration() == -1 &&
michael@0 360 !mDecoder->IsShutdown() &&
michael@0 361 resource->GetLength() >= 0 &&
michael@0 362 mDecoder->IsMediaSeekable())
michael@0 363 {
michael@0 364 // We didn't get a duration from the index or a Content-Duration header.
michael@0 365 // Seek to the end of file to find the end time.
michael@0 366 mDecoder->GetResource()->StartSeekingForMetadata();
michael@0 367 int64_t length = resource->GetLength();
michael@0 368
michael@0 369 NS_ASSERTION(length > 0, "Must have a content length to get end time");
michael@0 370
michael@0 371 int64_t endTime = 0;
michael@0 372 {
michael@0 373 ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
michael@0 374 endTime = RangeEndTime(length);
michael@0 375 }
michael@0 376 if (endTime != -1) {
michael@0 377 mDecoder->SetMediaEndTime(endTime);
michael@0 378 LOG(PR_LOG_DEBUG, ("Got Ogg duration from seeking to end %lld", endTime));
michael@0 379 }
michael@0 380 mDecoder->GetResource()->EndSeekingForMetadata();
michael@0 381 } else if (mDecoder->GetMediaDuration() == -1) {
michael@0 382 // We don't have a duration, and we don't know enough about the resource
michael@0 383 // to try a seek. Abort trying to get a duration. This happens for example
michael@0 384 // when the server says it accepts range requests, but does not give us a
michael@0 385 // Content-Length.
michael@0 386 mDecoder->SetTransportSeekable(false);
michael@0 387 }
michael@0 388 } else {
michael@0 389 return NS_ERROR_FAILURE;
michael@0 390 }
michael@0 391 *aInfo = mInfo;
michael@0 392
michael@0 393 return NS_OK;
michael@0 394 }
michael@0 395
michael@0 396 nsresult OggReader::DecodeVorbis(ogg_packet* aPacket) {
michael@0 397 NS_ASSERTION(aPacket->granulepos != -1, "Must know vorbis granulepos!");
michael@0 398
michael@0 399 if (vorbis_synthesis(&mVorbisState->mBlock, aPacket) != 0) {
michael@0 400 return NS_ERROR_FAILURE;
michael@0 401 }
michael@0 402 if (vorbis_synthesis_blockin(&mVorbisState->mDsp,
michael@0 403 &mVorbisState->mBlock) != 0)
michael@0 404 {
michael@0 405 return NS_ERROR_FAILURE;
michael@0 406 }
michael@0 407
michael@0 408 VorbisPCMValue** pcm = 0;
michael@0 409 int32_t frames = 0;
michael@0 410 uint32_t channels = mVorbisState->mInfo.channels;
michael@0 411 ogg_int64_t endFrame = aPacket->granulepos;
michael@0 412 while ((frames = vorbis_synthesis_pcmout(&mVorbisState->mDsp, &pcm)) > 0) {
michael@0 413 mVorbisState->ValidateVorbisPacketSamples(aPacket, frames);
michael@0 414 nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]);
michael@0 415 for (uint32_t j = 0; j < channels; ++j) {
michael@0 416 VorbisPCMValue* channel = pcm[j];
michael@0 417 for (uint32_t i = 0; i < uint32_t(frames); ++i) {
michael@0 418 buffer[i*channels + j] = MOZ_CONVERT_VORBIS_SAMPLE(channel[i]);
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 // No channel mapping for more than 8 channels.
michael@0 423 if (channels > 8) {
michael@0 424 return NS_ERROR_FAILURE;
michael@0 425 }
michael@0 426
michael@0 427 int64_t duration = mVorbisState->Time((int64_t)frames);
michael@0 428 int64_t startTime = mVorbisState->Time(endFrame - frames);
michael@0 429 mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
michael@0 430 startTime,
michael@0 431 duration,
michael@0 432 frames,
michael@0 433 buffer.forget(),
michael@0 434 channels));
michael@0 435
michael@0 436 mDecodedAudioFrames += frames;
michael@0 437
michael@0 438 endFrame -= frames;
michael@0 439 if (vorbis_synthesis_read(&mVorbisState->mDsp, frames) != 0) {
michael@0 440 return NS_ERROR_FAILURE;
michael@0 441 }
michael@0 442 }
michael@0 443 return NS_OK;
michael@0 444 }
michael@0 445 #ifdef MOZ_OPUS
michael@0 446 nsresult OggReader::DecodeOpus(ogg_packet* aPacket) {
michael@0 447 NS_ASSERTION(aPacket->granulepos != -1, "Must know opus granulepos!");
michael@0 448
michael@0 449 // Maximum value is 63*2880, so there's no chance of overflow.
michael@0 450 int32_t frames_number = opus_packet_get_nb_frames(aPacket->packet,
michael@0 451 aPacket->bytes);
michael@0 452 if (frames_number <= 0)
michael@0 453 return NS_ERROR_FAILURE; // Invalid packet header.
michael@0 454 int32_t samples = opus_packet_get_samples_per_frame(aPacket->packet,
michael@0 455 (opus_int32) mOpusState->mRate);
michael@0 456 int32_t frames = frames_number*samples;
michael@0 457
michael@0 458 // A valid Opus packet must be between 2.5 and 120 ms long.
michael@0 459 if (frames < 120 || frames > 5760)
michael@0 460 return NS_ERROR_FAILURE;
michael@0 461 uint32_t channels = mOpusState->mChannels;
michael@0 462 nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[frames * channels]);
michael@0 463
michael@0 464 // Decode to the appropriate sample type.
michael@0 465 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
michael@0 466 int ret = opus_multistream_decode_float(mOpusState->mDecoder,
michael@0 467 aPacket->packet, aPacket->bytes,
michael@0 468 buffer, frames, false);
michael@0 469 #else
michael@0 470 int ret = opus_multistream_decode(mOpusState->mDecoder,
michael@0 471 aPacket->packet, aPacket->bytes,
michael@0 472 buffer, frames, false);
michael@0 473 #endif
michael@0 474 if (ret < 0)
michael@0 475 return NS_ERROR_FAILURE;
michael@0 476 NS_ASSERTION(ret == frames, "Opus decoded too few audio samples");
michael@0 477
michael@0 478 int64_t endFrame = aPacket->granulepos;
michael@0 479 int64_t startFrame;
michael@0 480 // If this is the last packet, perform end trimming.
michael@0 481 if (aPacket->e_o_s && mOpusState->mPrevPacketGranulepos != -1) {
michael@0 482 startFrame = mOpusState->mPrevPacketGranulepos;
michael@0 483 frames = static_cast<int32_t>(std::max(static_cast<int64_t>(0),
michael@0 484 std::min(endFrame - startFrame,
michael@0 485 static_cast<int64_t>(frames))));
michael@0 486 } else {
michael@0 487 startFrame = endFrame - frames;
michael@0 488 }
michael@0 489
michael@0 490 // Trim the initial frames while the decoder is settling.
michael@0 491 if (mOpusState->mSkip > 0) {
michael@0 492 int32_t skipFrames = std::min(mOpusState->mSkip, frames);
michael@0 493 if (skipFrames == frames) {
michael@0 494 // discard the whole packet
michael@0 495 mOpusState->mSkip -= frames;
michael@0 496 LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames"
michael@0 497 " (whole packet)", frames));
michael@0 498 return NS_OK;
michael@0 499 }
michael@0 500 int32_t keepFrames = frames - skipFrames;
michael@0 501 int samples = keepFrames * channels;
michael@0 502 nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
michael@0 503 for (int i = 0; i < samples; i++)
michael@0 504 trimBuffer[i] = buffer[skipFrames*channels + i];
michael@0 505
michael@0 506 startFrame = endFrame - keepFrames;
michael@0 507 frames = keepFrames;
michael@0 508 buffer = trimBuffer;
michael@0 509
michael@0 510 mOpusState->mSkip -= skipFrames;
michael@0 511 LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames));
michael@0 512 }
michael@0 513 // Save this packet's granule position in case we need to perform end
michael@0 514 // trimming on the next packet.
michael@0 515 mOpusState->mPrevPacketGranulepos = endFrame;
michael@0 516
michael@0 517 // Apply the header gain if one was specified.
michael@0 518 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
michael@0 519 if (mOpusState->mGain != 1.0f) {
michael@0 520 float gain = mOpusState->mGain;
michael@0 521 int samples = frames * channels;
michael@0 522 for (int i = 0; i < samples; i++) {
michael@0 523 buffer[i] *= gain;
michael@0 524 }
michael@0 525 }
michael@0 526 #else
michael@0 527 if (mOpusState->mGain_Q16 != 65536) {
michael@0 528 int64_t gain_Q16 = mOpusState->mGain_Q16;
michael@0 529 int samples = frames * channels;
michael@0 530 for (int i = 0; i < samples; i++) {
michael@0 531 int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16);
michael@0 532 buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val));
michael@0 533 }
michael@0 534 }
michael@0 535 #endif
michael@0 536
michael@0 537 // No channel mapping for more than 8 channels.
michael@0 538 if (channels > 8) {
michael@0 539 return NS_ERROR_FAILURE;
michael@0 540 }
michael@0 541
michael@0 542 LOG(PR_LOG_DEBUG, ("Opus decoder pushing %d frames", frames));
michael@0 543 int64_t startTime = mOpusState->Time(startFrame);
michael@0 544 int64_t endTime = mOpusState->Time(endFrame);
michael@0 545 mAudioQueue.Push(new AudioData(mDecoder->GetResource()->Tell(),
michael@0 546 startTime,
michael@0 547 endTime - startTime,
michael@0 548 frames,
michael@0 549 buffer.forget(),
michael@0 550 channels));
michael@0 551
michael@0 552 mDecodedAudioFrames += frames;
michael@0 553
michael@0 554 return NS_OK;
michael@0 555 }
michael@0 556 #endif /* MOZ_OPUS */
michael@0 557
michael@0 558 bool OggReader::DecodeAudioData()
michael@0 559 {
michael@0 560 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 561 DebugOnly<bool> haveCodecState = mVorbisState != nullptr
michael@0 562 #ifdef MOZ_OPUS
michael@0 563 || mOpusState != nullptr
michael@0 564 #endif /* MOZ_OPUS */
michael@0 565 ;
michael@0 566 NS_ASSERTION(haveCodecState, "Need audio codec state to decode audio");
michael@0 567
michael@0 568 // Read the next data packet. Skip any non-data packets we encounter.
michael@0 569 ogg_packet* packet = 0;
michael@0 570 OggCodecState* codecState;
michael@0 571 if (mVorbisState)
michael@0 572 codecState = static_cast<OggCodecState*>(mVorbisState);
michael@0 573 #ifdef MOZ_OPUS
michael@0 574 else
michael@0 575 codecState = static_cast<OggCodecState*>(mOpusState);
michael@0 576 #endif /* MOZ_OPUS */
michael@0 577 do {
michael@0 578 if (packet) {
michael@0 579 OggCodecState::ReleasePacket(packet);
michael@0 580 }
michael@0 581 packet = NextOggPacket(codecState);
michael@0 582 } while (packet && codecState->IsHeader(packet));
michael@0 583
michael@0 584 if (!packet) {
michael@0 585 return false;
michael@0 586 }
michael@0 587
michael@0 588 NS_ASSERTION(packet && packet->granulepos != -1,
michael@0 589 "Must have packet with known granulepos");
michael@0 590 nsAutoRef<ogg_packet> autoRelease(packet);
michael@0 591 if (mVorbisState) {
michael@0 592 DecodeVorbis(packet);
michael@0 593 #ifdef MOZ_OPUS
michael@0 594 } else if (mOpusState) {
michael@0 595 DecodeOpus(packet);
michael@0 596 #endif
michael@0 597 }
michael@0 598
michael@0 599 if ((packet->e_o_s) && (!ReadOggChain())) {
michael@0 600 // We've encountered an end of bitstream packet, or we've hit the end of
michael@0 601 // file while trying to decode, so inform the audio queue that there'll
michael@0 602 // be no more samples.
michael@0 603 return false;
michael@0 604 }
michael@0 605
michael@0 606 return true;
michael@0 607 }
michael@0 608
michael@0 609 void OggReader::SetChained(bool aIsChained) {
michael@0 610 {
michael@0 611 ReentrantMonitorAutoEnter mon(mMonitor);
michael@0 612 mIsChained = aIsChained;
michael@0 613 }
michael@0 614 {
michael@0 615 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 616 mDecoder->SetMediaSeekable(false);
michael@0 617 }
michael@0 618 }
michael@0 619
michael@0 620 bool OggReader::ReadOggChain()
michael@0 621 {
michael@0 622 bool chained = false;
michael@0 623 #ifdef MOZ_OPUS
michael@0 624 OpusState* newOpusState = nullptr;
michael@0 625 #endif /* MOZ_OPUS */
michael@0 626 VorbisState* newVorbisState = nullptr;
michael@0 627 int channels = 0;
michael@0 628 long rate = 0;
michael@0 629 MetadataTags* tags = nullptr;
michael@0 630
michael@0 631 if (HasVideo() || HasSkeleton() || !HasAudio()) {
michael@0 632 return false;
michael@0 633 }
michael@0 634
michael@0 635 ogg_page page;
michael@0 636 if (!ReadOggPage(&page) || !ogg_page_bos(&page)) {
michael@0 637 return false;
michael@0 638 }
michael@0 639
michael@0 640 int serial = ogg_page_serialno(&page);
michael@0 641 if (mCodecStore.Contains(serial)) {
michael@0 642 return false;
michael@0 643 }
michael@0 644
michael@0 645 nsAutoPtr<OggCodecState> codecState;
michael@0 646 codecState = OggCodecState::Create(&page);
michael@0 647 if (!codecState) {
michael@0 648 return false;
michael@0 649 }
michael@0 650
michael@0 651 if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
michael@0 652 newVorbisState = static_cast<VorbisState*>(codecState.get());
michael@0 653 }
michael@0 654 #ifdef MOZ_OPUS
michael@0 655 else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
michael@0 656 newOpusState = static_cast<OpusState*>(codecState.get());
michael@0 657 }
michael@0 658 #endif
michael@0 659 else {
michael@0 660 return false;
michael@0 661 }
michael@0 662 OggCodecState* state;
michael@0 663
michael@0 664 mCodecStore.Add(serial, codecState.forget());
michael@0 665 state = mCodecStore.Get(serial);
michael@0 666
michael@0 667 NS_ENSURE_TRUE(state != nullptr, false);
michael@0 668
michael@0 669 if (NS_FAILED(state->PageIn(&page))) {
michael@0 670 return false;
michael@0 671 }
michael@0 672
michael@0 673 if ((newVorbisState && ReadHeaders(newVorbisState)) &&
michael@0 674 (mVorbisState->mInfo.rate == newVorbisState->mInfo.rate) &&
michael@0 675 (mVorbisState->mInfo.channels == newVorbisState->mInfo.channels)) {
michael@0 676 mVorbisState->Reset();
michael@0 677 mVorbisState = newVorbisState;
michael@0 678 mVorbisSerial = mVorbisState->mSerial;
michael@0 679 LOG(PR_LOG_DEBUG, ("New vorbis ogg link, serial=%d\n", mVorbisSerial));
michael@0 680 chained = true;
michael@0 681 rate = mVorbisState->mInfo.rate;
michael@0 682 channels = mVorbisState->mInfo.channels;
michael@0 683 tags = mVorbisState->GetTags();
michael@0 684 }
michael@0 685
michael@0 686 #ifdef MOZ_OPUS
michael@0 687 if ((newOpusState && ReadHeaders(newOpusState)) &&
michael@0 688 (mOpusState->mRate == newOpusState->mRate) &&
michael@0 689 (mOpusState->mChannels == newOpusState->mChannels)) {
michael@0 690 mOpusState->Reset();
michael@0 691 mOpusState = newOpusState;
michael@0 692 mOpusSerial = mOpusState->mSerial;
michael@0 693 chained = true;
michael@0 694 rate = mOpusState->mRate;
michael@0 695 channels = mOpusState->mChannels;
michael@0 696 tags = mOpusState->GetTags();
michael@0 697 }
michael@0 698 #endif
michael@0 699
michael@0 700 if (chained) {
michael@0 701 SetChained(true);
michael@0 702 {
michael@0 703 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 704 mDecoder->QueueMetadata((mDecodedAudioFrames * USECS_PER_S) / rate,
michael@0 705 channels,
michael@0 706 rate,
michael@0 707 HasAudio(),
michael@0 708 HasVideo(),
michael@0 709 tags);
michael@0 710 }
michael@0 711 return true;
michael@0 712 }
michael@0 713
michael@0 714 return false;
michael@0 715 }
michael@0 716
michael@0 717 nsresult OggReader::DecodeTheora(ogg_packet* aPacket, int64_t aTimeThreshold)
michael@0 718 {
michael@0 719 NS_ASSERTION(aPacket->granulepos >= TheoraVersion(&mTheoraState->mInfo,3,2,1),
michael@0 720 "Packets must have valid granulepos and packetno");
michael@0 721
michael@0 722 int ret = th_decode_packetin(mTheoraState->mCtx, aPacket, 0);
michael@0 723 if (ret != 0 && ret != TH_DUPFRAME) {
michael@0 724 return NS_ERROR_FAILURE;
michael@0 725 }
michael@0 726 int64_t time = mTheoraState->StartTime(aPacket->granulepos);
michael@0 727
michael@0 728 // Don't use the frame if it's outside the bounds of the presentation
michael@0 729 // start time in the skeleton track. Note we still must submit the frame
michael@0 730 // to the decoder (via th_decode_packetin), as the frames which are
michael@0 731 // presentable may depend on this frame's data.
michael@0 732 if (mSkeletonState && !mSkeletonState->IsPresentable(time)) {
michael@0 733 return NS_OK;
michael@0 734 }
michael@0 735
michael@0 736 int64_t endTime = mTheoraState->Time(aPacket->granulepos);
michael@0 737 if (endTime < aTimeThreshold) {
michael@0 738 // The end time of this frame is already before the current playback
michael@0 739 // position. It will never be displayed, don't bother enqueing it.
michael@0 740 return NS_OK;
michael@0 741 }
michael@0 742
michael@0 743 if (ret == TH_DUPFRAME) {
michael@0 744 VideoData* v = VideoData::CreateDuplicate(mDecoder->GetResource()->Tell(),
michael@0 745 time,
michael@0 746 endTime - time,
michael@0 747 aPacket->granulepos);
michael@0 748 mVideoQueue.Push(v);
michael@0 749 } else if (ret == 0) {
michael@0 750 th_ycbcr_buffer buffer;
michael@0 751 ret = th_decode_ycbcr_out(mTheoraState->mCtx, buffer);
michael@0 752 NS_ASSERTION(ret == 0, "th_decode_ycbcr_out failed");
michael@0 753 bool isKeyframe = th_packet_iskeyframe(aPacket) == 1;
michael@0 754 VideoData::YCbCrBuffer b;
michael@0 755 for (uint32_t i=0; i < 3; ++i) {
michael@0 756 b.mPlanes[i].mData = buffer[i].data;
michael@0 757 b.mPlanes[i].mHeight = buffer[i].height;
michael@0 758 b.mPlanes[i].mWidth = buffer[i].width;
michael@0 759 b.mPlanes[i].mStride = buffer[i].stride;
michael@0 760 b.mPlanes[i].mOffset = b.mPlanes[i].mSkip = 0;
michael@0 761 }
michael@0 762
michael@0 763 VideoData *v = VideoData::Create(mInfo.mVideo,
michael@0 764 mDecoder->GetImageContainer(),
michael@0 765 mDecoder->GetResource()->Tell(),
michael@0 766 time,
michael@0 767 endTime - time,
michael@0 768 b,
michael@0 769 isKeyframe,
michael@0 770 aPacket->granulepos,
michael@0 771 ToIntRect(mPicture));
michael@0 772 if (!v) {
michael@0 773 // There may be other reasons for this error, but for
michael@0 774 // simplicity just assume the worst case: out of memory.
michael@0 775 NS_WARNING("Failed to allocate memory for video frame");
michael@0 776 return NS_ERROR_OUT_OF_MEMORY;
michael@0 777 }
michael@0 778 mVideoQueue.Push(v);
michael@0 779 }
michael@0 780 return NS_OK;
michael@0 781 }
michael@0 782
michael@0 783 bool OggReader::DecodeVideoFrame(bool &aKeyframeSkip,
michael@0 784 int64_t aTimeThreshold)
michael@0 785 {
michael@0 786 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 787
michael@0 788 // Record number of frames decoded and parsed. Automatically update the
michael@0 789 // stats counters using the AutoNotifyDecoded stack-based class.
michael@0 790 uint32_t parsed = 0, decoded = 0;
michael@0 791 AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
michael@0 792
michael@0 793 // Read the next data packet. Skip any non-data packets we encounter.
michael@0 794 ogg_packet* packet = 0;
michael@0 795 do {
michael@0 796 if (packet) {
michael@0 797 OggCodecState::ReleasePacket(packet);
michael@0 798 }
michael@0 799 packet = NextOggPacket(mTheoraState);
michael@0 800 } while (packet && mTheoraState->IsHeader(packet));
michael@0 801 if (!packet) {
michael@0 802 return false;
michael@0 803 }
michael@0 804 nsAutoRef<ogg_packet> autoRelease(packet);
michael@0 805
michael@0 806 parsed++;
michael@0 807 NS_ASSERTION(packet && packet->granulepos != -1,
michael@0 808 "Must know first packet's granulepos");
michael@0 809 bool eos = packet->e_o_s;
michael@0 810 int64_t frameEndTime = mTheoraState->Time(packet->granulepos);
michael@0 811 if (!aKeyframeSkip ||
michael@0 812 (th_packet_iskeyframe(packet) && frameEndTime >= aTimeThreshold))
michael@0 813 {
michael@0 814 aKeyframeSkip = false;
michael@0 815 nsresult res = DecodeTheora(packet, aTimeThreshold);
michael@0 816 decoded++;
michael@0 817 if (NS_FAILED(res)) {
michael@0 818 return false;
michael@0 819 }
michael@0 820 }
michael@0 821
michael@0 822 if (eos) {
michael@0 823 // We've encountered an end of bitstream packet. Inform the queue that
michael@0 824 // there will be no more frames.
michael@0 825 return false;
michael@0 826 }
michael@0 827
michael@0 828 return true;
michael@0 829 }
michael@0 830
michael@0 831 bool OggReader::ReadOggPage(ogg_page* aPage)
michael@0 832 {
michael@0 833 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 834
michael@0 835 int ret = 0;
michael@0 836 while((ret = ogg_sync_pageseek(&mOggState, aPage)) <= 0) {
michael@0 837 if (ret < 0) {
michael@0 838 // Lost page sync, have to skip up to next page.
michael@0 839 continue;
michael@0 840 }
michael@0 841 // Returns a buffer that can be written too
michael@0 842 // with the given size. This buffer is stored
michael@0 843 // in the ogg synchronisation structure.
michael@0 844 char* buffer = ogg_sync_buffer(&mOggState, 4096);
michael@0 845 NS_ASSERTION(buffer, "ogg_sync_buffer failed");
michael@0 846
michael@0 847 // Read from the resource into the buffer
michael@0 848 uint32_t bytesRead = 0;
michael@0 849
michael@0 850 nsresult rv = mDecoder->GetResource()->Read(buffer, 4096, &bytesRead);
michael@0 851 if (NS_FAILED(rv) || (bytesRead == 0 && ret == 0)) {
michael@0 852 // End of file.
michael@0 853 return false;
michael@0 854 }
michael@0 855
michael@0 856 // Update the synchronisation layer with the number
michael@0 857 // of bytes written to the buffer
michael@0 858 ret = ogg_sync_wrote(&mOggState, bytesRead);
michael@0 859 NS_ENSURE_TRUE(ret == 0, false);
michael@0 860 }
michael@0 861
michael@0 862 return true;
michael@0 863 }
michael@0 864
michael@0 865 ogg_packet* OggReader::NextOggPacket(OggCodecState* aCodecState)
michael@0 866 {
michael@0 867 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 868
michael@0 869 if (!aCodecState || !aCodecState->mActive) {
michael@0 870 return nullptr;
michael@0 871 }
michael@0 872
michael@0 873 ogg_packet* packet;
michael@0 874 while ((packet = aCodecState->PacketOut()) == nullptr) {
michael@0 875 // The codec state does not have any buffered pages, so try to read another
michael@0 876 // page from the channel.
michael@0 877 ogg_page page;
michael@0 878 if (!ReadOggPage(&page)) {
michael@0 879 return nullptr;
michael@0 880 }
michael@0 881
michael@0 882 uint32_t serial = ogg_page_serialno(&page);
michael@0 883 OggCodecState* codecState = nullptr;
michael@0 884 codecState = mCodecStore.Get(serial);
michael@0 885 if (codecState && NS_FAILED(codecState->PageIn(&page))) {
michael@0 886 return nullptr;
michael@0 887 }
michael@0 888 }
michael@0 889
michael@0 890 return packet;
michael@0 891 }
michael@0 892
michael@0 893 // Returns an ogg page's checksum.
michael@0 894 static ogg_uint32_t
michael@0 895 GetChecksum(ogg_page* page)
michael@0 896 {
michael@0 897 if (page == 0 || page->header == 0 || page->header_len < 25) {
michael@0 898 return 0;
michael@0 899 }
michael@0 900 const unsigned char* p = page->header + 22;
michael@0 901 uint32_t c = p[0] +
michael@0 902 (p[1] << 8) +
michael@0 903 (p[2] << 16) +
michael@0 904 (p[3] << 24);
michael@0 905 return c;
michael@0 906 }
michael@0 907
michael@0 908 int64_t OggReader::RangeStartTime(int64_t aOffset)
michael@0 909 {
michael@0 910 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 911 MediaResource* resource = mDecoder->GetResource();
michael@0 912 NS_ENSURE_TRUE(resource != nullptr, 0);
michael@0 913 nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
michael@0 914 NS_ENSURE_SUCCESS(res, 0);
michael@0 915 int64_t startTime = 0;
michael@0 916 MediaDecoderReader::FindStartTime(startTime);
michael@0 917 return startTime;
michael@0 918 }
michael@0 919
michael@0 920 struct nsAutoOggSyncState {
michael@0 921 nsAutoOggSyncState() {
michael@0 922 ogg_sync_init(&mState);
michael@0 923 }
michael@0 924 ~nsAutoOggSyncState() {
michael@0 925 ogg_sync_clear(&mState);
michael@0 926 }
michael@0 927 ogg_sync_state mState;
michael@0 928 };
michael@0 929
michael@0 930 int64_t OggReader::RangeEndTime(int64_t aEndOffset)
michael@0 931 {
michael@0 932 NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(),
michael@0 933 "Should be on state machine or decode thread.");
michael@0 934
michael@0 935 MediaResource* resource = mDecoder->GetResource();
michael@0 936 NS_ENSURE_TRUE(resource != nullptr, -1);
michael@0 937 int64_t position = resource->Tell();
michael@0 938 int64_t endTime = RangeEndTime(0, aEndOffset, false);
michael@0 939 nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, position);
michael@0 940 NS_ENSURE_SUCCESS(res, -1);
michael@0 941 return endTime;
michael@0 942 }
michael@0 943
michael@0 944 int64_t OggReader::RangeEndTime(int64_t aStartOffset,
michael@0 945 int64_t aEndOffset,
michael@0 946 bool aCachedDataOnly)
michael@0 947 {
michael@0 948 MediaResource* resource = mDecoder->GetResource();
michael@0 949 nsAutoOggSyncState sync;
michael@0 950
michael@0 951 // We need to find the last page which ends before aEndOffset that
michael@0 952 // has a granulepos that we can convert to a timestamp. We do this by
michael@0 953 // backing off from aEndOffset until we encounter a page on which we can
michael@0 954 // interpret the granulepos. If while backing off we encounter a page which
michael@0 955 // we've previously encountered before, we'll either backoff again if we
michael@0 956 // haven't found an end time yet, or return the last end time found.
michael@0 957 const int step = 5000;
michael@0 958 const int maxOggPageSize = 65306;
michael@0 959 int64_t readStartOffset = aEndOffset;
michael@0 960 int64_t readLimitOffset = aEndOffset;
michael@0 961 int64_t readHead = aEndOffset;
michael@0 962 int64_t endTime = -1;
michael@0 963 uint32_t checksumAfterSeek = 0;
michael@0 964 uint32_t prevChecksumAfterSeek = 0;
michael@0 965 bool mustBackOff = false;
michael@0 966 while (true) {
michael@0 967 ogg_page page;
michael@0 968 int ret = ogg_sync_pageseek(&sync.mState, &page);
michael@0 969 if (ret == 0) {
michael@0 970 // We need more data if we've not encountered a page we've seen before,
michael@0 971 // or we've read to the end of file.
michael@0 972 if (mustBackOff || readHead == aEndOffset || readHead == aStartOffset) {
michael@0 973 if (endTime != -1 || readStartOffset == 0) {
michael@0 974 // We have encountered a page before, or we're at the end of file.
michael@0 975 break;
michael@0 976 }
michael@0 977 mustBackOff = false;
michael@0 978 prevChecksumAfterSeek = checksumAfterSeek;
michael@0 979 checksumAfterSeek = 0;
michael@0 980 ogg_sync_reset(&sync.mState);
michael@0 981 readStartOffset = std::max(static_cast<int64_t>(0), readStartOffset - step);
michael@0 982 // There's no point reading more than the maximum size of
michael@0 983 // an Ogg page into data we've previously scanned. Any data
michael@0 984 // between readLimitOffset and aEndOffset must be garbage
michael@0 985 // and we can ignore it thereafter.
michael@0 986 readLimitOffset = std::min(readLimitOffset,
michael@0 987 readStartOffset + maxOggPageSize);
michael@0 988 readHead = std::max(aStartOffset, readStartOffset);
michael@0 989 }
michael@0 990
michael@0 991 int64_t limit = std::min(static_cast<int64_t>(UINT32_MAX),
michael@0 992 aEndOffset - readHead);
michael@0 993 limit = std::max(static_cast<int64_t>(0), limit);
michael@0 994 limit = std::min(limit, static_cast<int64_t>(step));
michael@0 995 uint32_t bytesToRead = static_cast<uint32_t>(limit);
michael@0 996 uint32_t bytesRead = 0;
michael@0 997 char* buffer = ogg_sync_buffer(&sync.mState, bytesToRead);
michael@0 998 NS_ASSERTION(buffer, "Must have buffer");
michael@0 999 nsresult res;
michael@0 1000 if (aCachedDataOnly) {
michael@0 1001 res = resource->ReadFromCache(buffer, readHead, bytesToRead);
michael@0 1002 NS_ENSURE_SUCCESS(res, -1);
michael@0 1003 bytesRead = bytesToRead;
michael@0 1004 } else {
michael@0 1005 NS_ASSERTION(readHead < aEndOffset,
michael@0 1006 "resource pos must be before range end");
michael@0 1007 res = resource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
michael@0 1008 NS_ENSURE_SUCCESS(res, -1);
michael@0 1009 res = resource->Read(buffer, bytesToRead, &bytesRead);
michael@0 1010 NS_ENSURE_SUCCESS(res, -1);
michael@0 1011 }
michael@0 1012 readHead += bytesRead;
michael@0 1013 if (readHead > readLimitOffset) {
michael@0 1014 mustBackOff = true;
michael@0 1015 }
michael@0 1016
michael@0 1017 // Update the synchronisation layer with the number
michael@0 1018 // of bytes written to the buffer
michael@0 1019 ret = ogg_sync_wrote(&sync.mState, bytesRead);
michael@0 1020 if (ret != 0) {
michael@0 1021 endTime = -1;
michael@0 1022 break;
michael@0 1023 }
michael@0 1024
michael@0 1025 continue;
michael@0 1026 }
michael@0 1027
michael@0 1028 if (ret < 0 || ogg_page_granulepos(&page) < 0) {
michael@0 1029 continue;
michael@0 1030 }
michael@0 1031
michael@0 1032 uint32_t checksum = GetChecksum(&page);
michael@0 1033 if (checksumAfterSeek == 0) {
michael@0 1034 // This is the first page we've decoded after a backoff/seek. Remember
michael@0 1035 // the page checksum. If we backoff further and encounter this page
michael@0 1036 // again, we'll know that we won't find a page with an end time after
michael@0 1037 // this one, so we'll know to back off again.
michael@0 1038 checksumAfterSeek = checksum;
michael@0 1039 }
michael@0 1040 if (checksum == prevChecksumAfterSeek) {
michael@0 1041 // This page has the same checksum as the first page we encountered
michael@0 1042 // after the last backoff/seek. Since we've already scanned after this
michael@0 1043 // page and failed to find an end time, we may as well backoff again and
michael@0 1044 // try to find an end time from an earlier page.
michael@0 1045 mustBackOff = true;
michael@0 1046 continue;
michael@0 1047 }
michael@0 1048
michael@0 1049 int64_t granulepos = ogg_page_granulepos(&page);
michael@0 1050 int serial = ogg_page_serialno(&page);
michael@0 1051
michael@0 1052 OggCodecState* codecState = nullptr;
michael@0 1053 codecState = mCodecStore.Get(serial);
michael@0 1054
michael@0 1055 if (!codecState) {
michael@0 1056 // This page is from a bitstream which we haven't encountered yet.
michael@0 1057 // It's probably from a new "link" in a "chained" ogg. Don't
michael@0 1058 // bother even trying to find a duration...
michael@0 1059 SetChained(true);
michael@0 1060 endTime = -1;
michael@0 1061 break;
michael@0 1062 }
michael@0 1063
michael@0 1064 int64_t t = codecState->Time(granulepos);
michael@0 1065 if (t != -1) {
michael@0 1066 endTime = t;
michael@0 1067 }
michael@0 1068 }
michael@0 1069
michael@0 1070 return endTime;
michael@0 1071 }
michael@0 1072
michael@0 1073 nsresult OggReader::GetSeekRanges(nsTArray<SeekRange>& aRanges)
michael@0 1074 {
michael@0 1075 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 1076 nsTArray<MediaByteRange> cached;
michael@0 1077 nsresult res = mDecoder->GetResource()->GetCachedRanges(cached);
michael@0 1078 NS_ENSURE_SUCCESS(res, res);
michael@0 1079
michael@0 1080 for (uint32_t index = 0; index < cached.Length(); index++) {
michael@0 1081 MediaByteRange& range = cached[index];
michael@0 1082 int64_t startTime = -1;
michael@0 1083 int64_t endTime = -1;
michael@0 1084 if (NS_FAILED(ResetDecode())) {
michael@0 1085 return NS_ERROR_FAILURE;
michael@0 1086 }
michael@0 1087 int64_t startOffset = range.mStart;
michael@0 1088 int64_t endOffset = range.mEnd;
michael@0 1089 startTime = RangeStartTime(startOffset);
michael@0 1090 if (startTime != -1 &&
michael@0 1091 ((endTime = RangeEndTime(endOffset)) != -1))
michael@0 1092 {
michael@0 1093 NS_WARN_IF_FALSE(startTime < endTime,
michael@0 1094 "Start time must be before end time");
michael@0 1095 aRanges.AppendElement(SeekRange(startOffset,
michael@0 1096 endOffset,
michael@0 1097 startTime,
michael@0 1098 endTime));
michael@0 1099 }
michael@0 1100 }
michael@0 1101 if (NS_FAILED(ResetDecode())) {
michael@0 1102 return NS_ERROR_FAILURE;
michael@0 1103 }
michael@0 1104 return NS_OK;
michael@0 1105 }
michael@0 1106
michael@0 1107 OggReader::SeekRange
michael@0 1108 OggReader::SelectSeekRange(const nsTArray<SeekRange>& ranges,
michael@0 1109 int64_t aTarget,
michael@0 1110 int64_t aStartTime,
michael@0 1111 int64_t aEndTime,
michael@0 1112 bool aExact)
michael@0 1113 {
michael@0 1114 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 1115 int64_t so = 0;
michael@0 1116 int64_t eo = mDecoder->GetResource()->GetLength();
michael@0 1117 int64_t st = aStartTime;
michael@0 1118 int64_t et = aEndTime;
michael@0 1119 for (uint32_t i = 0; i < ranges.Length(); i++) {
michael@0 1120 const SeekRange &r = ranges[i];
michael@0 1121 if (r.mTimeStart < aTarget) {
michael@0 1122 so = r.mOffsetStart;
michael@0 1123 st = r.mTimeStart;
michael@0 1124 }
michael@0 1125 if (r.mTimeEnd >= aTarget && r.mTimeEnd < et) {
michael@0 1126 eo = r.mOffsetEnd;
michael@0 1127 et = r.mTimeEnd;
michael@0 1128 }
michael@0 1129
michael@0 1130 if (r.mTimeStart < aTarget && aTarget <= r.mTimeEnd) {
michael@0 1131 // Target lies exactly in this range.
michael@0 1132 return ranges[i];
michael@0 1133 }
michael@0 1134 }
michael@0 1135 if (aExact || eo == -1) {
michael@0 1136 return SeekRange();
michael@0 1137 }
michael@0 1138 return SeekRange(so, eo, st, et);
michael@0 1139 }
michael@0 1140
michael@0 1141 OggReader::IndexedSeekResult OggReader::RollbackIndexedSeek(int64_t aOffset)
michael@0 1142 {
michael@0 1143 mSkeletonState->Deactivate();
michael@0 1144 MediaResource* resource = mDecoder->GetResource();
michael@0 1145 NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
michael@0 1146 nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET, aOffset);
michael@0 1147 NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
michael@0 1148 return SEEK_INDEX_FAIL;
michael@0 1149 }
michael@0 1150
michael@0 1151 OggReader::IndexedSeekResult OggReader::SeekToKeyframeUsingIndex(int64_t aTarget)
michael@0 1152 {
michael@0 1153 MediaResource* resource = mDecoder->GetResource();
michael@0 1154 NS_ENSURE_TRUE(resource != nullptr, SEEK_FATAL_ERROR);
michael@0 1155 if (!HasSkeleton() || !mSkeletonState->HasIndex()) {
michael@0 1156 return SEEK_INDEX_FAIL;
michael@0 1157 }
michael@0 1158 // We have an index from the Skeleton track, try to use it to seek.
michael@0 1159 nsAutoTArray<uint32_t, 2> tracks;
michael@0 1160 BuildSerialList(tracks);
michael@0 1161 SkeletonState::nsSeekTarget keyframe;
michael@0 1162 if (NS_FAILED(mSkeletonState->IndexedSeekTarget(aTarget,
michael@0 1163 tracks,
michael@0 1164 keyframe)))
michael@0 1165 {
michael@0 1166 // Could not locate a keypoint for the target in the index.
michael@0 1167 return SEEK_INDEX_FAIL;
michael@0 1168 }
michael@0 1169
michael@0 1170 // Remember original resource read cursor position so we can rollback on failure.
michael@0 1171 int64_t tell = resource->Tell();
michael@0 1172
michael@0 1173 // Seek to the keypoint returned by the index.
michael@0 1174 if (keyframe.mKeyPoint.mOffset > resource->GetLength() ||
michael@0 1175 keyframe.mKeyPoint.mOffset < 0)
michael@0 1176 {
michael@0 1177 // Index must be invalid.
michael@0 1178 return RollbackIndexedSeek(tell);
michael@0 1179 }
michael@0 1180 LOG(PR_LOG_DEBUG, ("Seeking using index to keyframe at offset %lld\n",
michael@0 1181 keyframe.mKeyPoint.mOffset));
michael@0 1182 nsresult res = resource->Seek(nsISeekableStream::NS_SEEK_SET,
michael@0 1183 keyframe.mKeyPoint.mOffset);
michael@0 1184 NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
michael@0 1185
michael@0 1186 // We've moved the read set, so reset decode.
michael@0 1187 res = ResetDecode();
michael@0 1188 NS_ENSURE_SUCCESS(res, SEEK_FATAL_ERROR);
michael@0 1189
michael@0 1190 // Check that the page the index thinks is exactly here is actually exactly
michael@0 1191 // here. If not, the index is invalid.
michael@0 1192 ogg_page page;
michael@0 1193 int skippedBytes = 0;
michael@0 1194 PageSyncResult syncres = PageSync(resource,
michael@0 1195 &mOggState,
michael@0 1196 false,
michael@0 1197 keyframe.mKeyPoint.mOffset,
michael@0 1198 resource->GetLength(),
michael@0 1199 &page,
michael@0 1200 skippedBytes);
michael@0 1201 NS_ENSURE_TRUE(syncres != PAGE_SYNC_ERROR, SEEK_FATAL_ERROR);
michael@0 1202 if (syncres != PAGE_SYNC_OK || skippedBytes != 0) {
michael@0 1203 LOG(PR_LOG_DEBUG, ("Indexed-seek failure: Ogg Skeleton Index is invalid "
michael@0 1204 "or sync error after seek"));
michael@0 1205 return RollbackIndexedSeek(tell);
michael@0 1206 }
michael@0 1207 uint32_t serial = ogg_page_serialno(&page);
michael@0 1208 if (serial != keyframe.mSerial) {
michael@0 1209 // Serialno of page at offset isn't what the index told us to expect.
michael@0 1210 // Assume the index is invalid.
michael@0 1211 return RollbackIndexedSeek(tell);
michael@0 1212 }
michael@0 1213 OggCodecState* codecState = mCodecStore.Get(serial);
michael@0 1214 if (codecState &&
michael@0 1215 codecState->mActive &&
michael@0 1216 ogg_stream_pagein(&codecState->mState, &page) != 0)
michael@0 1217 {
michael@0 1218 // Couldn't insert page into the ogg resource, or somehow the resource
michael@0 1219 // is no longer active.
michael@0 1220 return RollbackIndexedSeek(tell);
michael@0 1221 }
michael@0 1222 return SEEK_OK;
michael@0 1223 }
michael@0 1224
michael@0 1225 nsresult OggReader::SeekInBufferedRange(int64_t aTarget,
michael@0 1226 int64_t aAdjustedTarget,
michael@0 1227 int64_t aStartTime,
michael@0 1228 int64_t aEndTime,
michael@0 1229 const nsTArray<SeekRange>& aRanges,
michael@0 1230 const SeekRange& aRange)
michael@0 1231 {
michael@0 1232 LOG(PR_LOG_DEBUG, ("%p Seeking in buffered data to %lld using bisection search", mDecoder, aTarget));
michael@0 1233 nsresult res = NS_OK;
michael@0 1234 if (HasVideo() || aAdjustedTarget >= aTarget) {
michael@0 1235 // We know the exact byte range in which the target must lie. It must
michael@0 1236 // be buffered in the media cache. Seek there.
michael@0 1237 nsresult res = SeekBisection(aTarget, aRange, 0);
michael@0 1238 if (NS_FAILED(res) || !HasVideo()) {
michael@0 1239 return res;
michael@0 1240 }
michael@0 1241
michael@0 1242 // We have an active Theora bitstream. Decode the next Theora frame, and
michael@0 1243 // extract its keyframe's time.
michael@0 1244 bool eof;
michael@0 1245 do {
michael@0 1246 bool skip = false;
michael@0 1247 eof = !DecodeVideoFrame(skip, 0);
michael@0 1248 {
michael@0 1249 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 1250 if (mDecoder->IsShutdown()) {
michael@0 1251 return NS_ERROR_FAILURE;
michael@0 1252 }
michael@0 1253 }
michael@0 1254 } while (!eof &&
michael@0 1255 mVideoQueue.GetSize() == 0);
michael@0 1256
michael@0 1257 VideoData* video = mVideoQueue.PeekFront();
michael@0 1258 if (video && !video->mKeyframe) {
michael@0 1259 // First decoded frame isn't a keyframe, seek back to previous keyframe,
michael@0 1260 // otherwise we'll get visual artifacts.
michael@0 1261 NS_ASSERTION(video->mTimecode != -1, "Must have a granulepos");
michael@0 1262 int shift = mTheoraState->mInfo.keyframe_granule_shift;
michael@0 1263 int64_t keyframeGranulepos = (video->mTimecode >> shift) << shift;
michael@0 1264 int64_t keyframeTime = mTheoraState->StartTime(keyframeGranulepos);
michael@0 1265 SEEK_LOG(PR_LOG_DEBUG, ("Keyframe for %lld is at %lld, seeking back to it",
michael@0 1266 video->mTime, keyframeTime));
michael@0 1267 aAdjustedTarget = std::min(aAdjustedTarget, keyframeTime);
michael@0 1268 }
michael@0 1269 }
michael@0 1270 if (aAdjustedTarget < aTarget) {
michael@0 1271 SeekRange k = SelectSeekRange(aRanges,
michael@0 1272 aAdjustedTarget,
michael@0 1273 aStartTime,
michael@0 1274 aEndTime,
michael@0 1275 false);
michael@0 1276 res = SeekBisection(aAdjustedTarget, k, SEEK_FUZZ_USECS);
michael@0 1277 }
michael@0 1278 return res;
michael@0 1279 }
michael@0 1280
michael@0 1281 nsresult OggReader::SeekInUnbuffered(int64_t aTarget,
michael@0 1282 int64_t aStartTime,
michael@0 1283 int64_t aEndTime,
michael@0 1284 const nsTArray<SeekRange>& aRanges)
michael@0 1285 {
michael@0 1286 LOG(PR_LOG_DEBUG, ("%p Seeking in unbuffered data to %lld using bisection search", mDecoder, aTarget));
michael@0 1287
michael@0 1288 // If we've got an active Theora bitstream, determine the maximum possible
michael@0 1289 // time in usecs which a keyframe could be before a given interframe. We
michael@0 1290 // subtract this from our seek target, seek to the new target, and then
michael@0 1291 // will decode forward to the original seek target. We should encounter a
michael@0 1292 // keyframe in that interval. This prevents us from needing to run two
michael@0 1293 // bisections; one for the seek target frame, and another to find its
michael@0 1294 // keyframe. It's usually faster to just download this extra data, rather
michael@0 1295 // tham perform two bisections to find the seek target's keyframe. We
michael@0 1296 // don't do this offsetting when seeking in a buffered range,
michael@0 1297 // as the extra decoding causes a noticeable speed hit when all the data
michael@0 1298 // is buffered (compared to just doing a bisection to exactly find the
michael@0 1299 // keyframe).
michael@0 1300 int64_t keyframeOffsetMs = 0;
michael@0 1301 if (HasVideo() && mTheoraState) {
michael@0 1302 keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
michael@0 1303 }
michael@0 1304 #ifdef MOZ_OPUS
michael@0 1305 // Add in the Opus pre-roll if necessary, as well.
michael@0 1306 if (HasAudio() && mOpusState) {
michael@0 1307 keyframeOffsetMs = std::max(keyframeOffsetMs, SEEK_OPUS_PREROLL);
michael@0 1308 }
michael@0 1309 #endif /* MOZ_OPUS */
michael@0 1310 int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
michael@0 1311 // Minimize the bisection search space using the known timestamps from the
michael@0 1312 // buffered ranges.
michael@0 1313 SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false);
michael@0 1314 return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
michael@0 1315 }
michael@0 1316
michael@0 1317 nsresult OggReader::Seek(int64_t aTarget,
michael@0 1318 int64_t aStartTime,
michael@0 1319 int64_t aEndTime,
michael@0 1320 int64_t aCurrentTime)
michael@0 1321 {
michael@0 1322 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 1323 if (mIsChained)
michael@0 1324 return NS_ERROR_FAILURE;
michael@0 1325 LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
michael@0 1326 nsresult res;
michael@0 1327 MediaResource* resource = mDecoder->GetResource();
michael@0 1328 NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE);
michael@0 1329 int64_t adjustedTarget = aTarget;
michael@0 1330 #ifdef MOZ_OPUS
michael@0 1331 if (HasAudio() && mOpusState){
michael@0 1332 adjustedTarget = std::max(aStartTime, aTarget - SEEK_OPUS_PREROLL);
michael@0 1333 }
michael@0 1334 #endif /* MOZ_OPUS */
michael@0 1335
michael@0 1336 if (adjustedTarget == aStartTime) {
michael@0 1337 // We've seeked to the media start. Just seek to the offset of the first
michael@0 1338 // content page.
michael@0 1339 res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
michael@0 1340 NS_ENSURE_SUCCESS(res,res);
michael@0 1341
michael@0 1342 res = ResetDecode(true);
michael@0 1343 NS_ENSURE_SUCCESS(res,res);
michael@0 1344
michael@0 1345 NS_ASSERTION(aStartTime != -1, "mStartTime should be known");
michael@0 1346 {
michael@0 1347 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 1348 mDecoder->UpdatePlaybackPosition(aStartTime);
michael@0 1349 }
michael@0 1350 } else {
michael@0 1351 // TODO: This may seek back unnecessarily far in the video, but we don't
michael@0 1352 // have a way of asking Skeleton to seek to a different target for each
michael@0 1353 // stream yet. Using adjustedTarget here is at least correct, if slow.
michael@0 1354 IndexedSeekResult sres = SeekToKeyframeUsingIndex(adjustedTarget);
michael@0 1355 NS_ENSURE_TRUE(sres != SEEK_FATAL_ERROR, NS_ERROR_FAILURE);
michael@0 1356 if (sres == SEEK_INDEX_FAIL) {
michael@0 1357 // No index or other non-fatal index-related failure. Try to seek
michael@0 1358 // using a bisection search. Determine the already downloaded data
michael@0 1359 // in the media cache, so we can try to seek in the cached data first.
michael@0 1360 nsAutoTArray<SeekRange, 16> ranges;
michael@0 1361 res = GetSeekRanges(ranges);
michael@0 1362 NS_ENSURE_SUCCESS(res,res);
michael@0 1363
michael@0 1364 // Figure out if the seek target lies in a buffered range.
michael@0 1365 SeekRange r = SelectSeekRange(ranges, aTarget, aStartTime, aEndTime, true);
michael@0 1366
michael@0 1367 if (!r.IsNull()) {
michael@0 1368 // We know the buffered range in which the seek target lies, do a
michael@0 1369 // bisection search in that buffered range.
michael@0 1370 res = SeekInBufferedRange(aTarget, adjustedTarget, aStartTime, aEndTime, ranges, r);
michael@0 1371 NS_ENSURE_SUCCESS(res,res);
michael@0 1372 } else {
michael@0 1373 // The target doesn't lie in a buffered range. Perform a bisection
michael@0 1374 // search over the whole media, using the known buffered ranges to
michael@0 1375 // reduce the search space.
michael@0 1376 res = SeekInUnbuffered(aTarget, aStartTime, aEndTime, ranges);
michael@0 1377 NS_ENSURE_SUCCESS(res,res);
michael@0 1378 }
michael@0 1379 }
michael@0 1380 }
michael@0 1381
michael@0 1382 if (HasVideo()) {
michael@0 1383 // Decode forwards until we find the next keyframe. This is required,
michael@0 1384 // as although the seek should finish on a page containing a keyframe,
michael@0 1385 // there may be non-keyframes in the page before the keyframe.
michael@0 1386 // When doing fastSeek we display the first frame after the seek, so
michael@0 1387 // we need to advance the decode to the keyframe otherwise we'll get
michael@0 1388 // visual artifacts in the first frame output after the seek.
michael@0 1389 bool skip = true;
michael@0 1390 while (DecodeVideoFrame(skip, 0) && skip) {
michael@0 1391 ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
michael@0 1392 if (mDecoder->IsShutdown()) {
michael@0 1393 return NS_ERROR_FAILURE;
michael@0 1394 }
michael@0 1395 }
michael@0 1396
michael@0 1397 #ifdef DEBUG
michael@0 1398 const VideoData* v = mVideoQueue.PeekFront();
michael@0 1399 if (!v || !v->mKeyframe) {
michael@0 1400 NS_WARNING("Ogg seek didn't end up before a key frame!");
michael@0 1401 }
michael@0 1402 #endif
michael@0 1403 }
michael@0 1404 return NS_OK;
michael@0 1405 }
michael@0 1406
michael@0 1407 // Reads a page from the media resource.
michael@0 1408 static PageSyncResult
michael@0 1409 PageSync(MediaResource* aResource,
michael@0 1410 ogg_sync_state* aState,
michael@0 1411 bool aCachedDataOnly,
michael@0 1412 int64_t aOffset,
michael@0 1413 int64_t aEndOffset,
michael@0 1414 ogg_page* aPage,
michael@0 1415 int& aSkippedBytes)
michael@0 1416 {
michael@0 1417 aSkippedBytes = 0;
michael@0 1418 // Sync to the next page.
michael@0 1419 int ret = 0;
michael@0 1420 uint32_t bytesRead = 0;
michael@0 1421 int64_t readHead = aOffset;
michael@0 1422 while (ret <= 0) {
michael@0 1423 ret = ogg_sync_pageseek(aState, aPage);
michael@0 1424 if (ret == 0) {
michael@0 1425 char* buffer = ogg_sync_buffer(aState, PAGE_STEP);
michael@0 1426 NS_ASSERTION(buffer, "Must have a buffer");
michael@0 1427
michael@0 1428 // Read from the file into the buffer
michael@0 1429 int64_t bytesToRead = std::min(static_cast<int64_t>(PAGE_STEP),
michael@0 1430 aEndOffset - readHead);
michael@0 1431 NS_ASSERTION(bytesToRead <= UINT32_MAX, "bytesToRead range check");
michael@0 1432 if (bytesToRead <= 0) {
michael@0 1433 return PAGE_SYNC_END_OF_RANGE;
michael@0 1434 }
michael@0 1435 nsresult rv = NS_OK;
michael@0 1436 if (aCachedDataOnly) {
michael@0 1437 rv = aResource->ReadFromCache(buffer, readHead,
michael@0 1438 static_cast<uint32_t>(bytesToRead));
michael@0 1439 NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
michael@0 1440 bytesRead = static_cast<uint32_t>(bytesToRead);
michael@0 1441 } else {
michael@0 1442 rv = aResource->Seek(nsISeekableStream::NS_SEEK_SET, readHead);
michael@0 1443 NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
michael@0 1444 rv = aResource->Read(buffer,
michael@0 1445 static_cast<uint32_t>(bytesToRead),
michael@0 1446 &bytesRead);
michael@0 1447 NS_ENSURE_SUCCESS(rv,PAGE_SYNC_ERROR);
michael@0 1448 }
michael@0 1449 if (bytesRead == 0 && NS_SUCCEEDED(rv)) {
michael@0 1450 // End of file.
michael@0 1451 return PAGE_SYNC_END_OF_RANGE;
michael@0 1452 }
michael@0 1453 readHead += bytesRead;
michael@0 1454
michael@0 1455 // Update the synchronisation layer with the number
michael@0 1456 // of bytes written to the buffer
michael@0 1457 ret = ogg_sync_wrote(aState, bytesRead);
michael@0 1458 NS_ENSURE_TRUE(ret == 0, PAGE_SYNC_ERROR);
michael@0 1459 continue;
michael@0 1460 }
michael@0 1461
michael@0 1462 if (ret < 0) {
michael@0 1463 NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
michael@0 1464 aSkippedBytes += -ret;
michael@0 1465 NS_ASSERTION(aSkippedBytes >= 0, "Offset >= 0");
michael@0 1466 continue;
michael@0 1467 }
michael@0 1468 }
michael@0 1469
michael@0 1470 return PAGE_SYNC_OK;
michael@0 1471 }
michael@0 1472
michael@0 1473 nsresult OggReader::SeekBisection(int64_t aTarget,
michael@0 1474 const SeekRange& aRange,
michael@0 1475 uint32_t aFuzz)
michael@0 1476 {
michael@0 1477 NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
michael@0 1478 nsresult res;
michael@0 1479 MediaResource* resource = mDecoder->GetResource();
michael@0 1480
michael@0 1481 if (aTarget == aRange.mTimeStart) {
michael@0 1482 if (NS_FAILED(ResetDecode())) {
michael@0 1483 return NS_ERROR_FAILURE;
michael@0 1484 }
michael@0 1485 res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
michael@0 1486 NS_ENSURE_SUCCESS(res,res);
michael@0 1487 return NS_OK;
michael@0 1488 }
michael@0 1489
michael@0 1490 // Bisection search, find start offset of last page with end time less than
michael@0 1491 // the seek target.
michael@0 1492 ogg_int64_t startOffset = aRange.mOffsetStart;
michael@0 1493 ogg_int64_t startTime = aRange.mTimeStart;
michael@0 1494 ogg_int64_t startLength = 0; // Length of the page at startOffset.
michael@0 1495 ogg_int64_t endOffset = aRange.mOffsetEnd;
michael@0 1496 ogg_int64_t endTime = aRange.mTimeEnd;
michael@0 1497
michael@0 1498 ogg_int64_t seekTarget = aTarget;
michael@0 1499 int64_t seekLowerBound = std::max(static_cast<int64_t>(0), aTarget - aFuzz);
michael@0 1500 int hops = 0;
michael@0 1501 DebugOnly<ogg_int64_t> previousGuess = -1;
michael@0 1502 int backsteps = 0;
michael@0 1503 const int maxBackStep = 10;
michael@0 1504 NS_ASSERTION(static_cast<uint64_t>(PAGE_STEP) * pow(2.0, maxBackStep) < INT32_MAX,
michael@0 1505 "Backstep calculation must not overflow");
michael@0 1506
michael@0 1507 // Seek via bisection search. Loop until we find the offset where the page
michael@0 1508 // before the offset is before the seek target, and the page after the offset
michael@0 1509 // is after the seek target.
michael@0 1510 while (true) {
michael@0 1511 ogg_int64_t duration = 0;
michael@0 1512 double target = 0;
michael@0 1513 ogg_int64_t interval = 0;
michael@0 1514 ogg_int64_t guess = 0;
michael@0 1515 ogg_page page;
michael@0 1516 int skippedBytes = 0;
michael@0 1517 ogg_int64_t pageOffset = 0;
michael@0 1518 ogg_int64_t pageLength = 0;
michael@0 1519 ogg_int64_t granuleTime = -1;
michael@0 1520 bool mustBackoff = false;
michael@0 1521
michael@0 1522 // Guess where we should bisect to, based on the bit rate and the time
michael@0 1523 // remaining in the interval. Loop until we can determine the time at
michael@0 1524 // the guess offset.
michael@0 1525 while (true) {
michael@0 1526
michael@0 1527 // Discard any previously buffered packets/pages.
michael@0 1528 if (NS_FAILED(ResetDecode())) {
michael@0 1529 return NS_ERROR_FAILURE;
michael@0 1530 }
michael@0 1531
michael@0 1532 interval = endOffset - startOffset - startLength;
michael@0 1533 if (interval == 0) {
michael@0 1534 // Our interval is empty, we've found the optimal seek point, as the
michael@0 1535 // page at the start offset is before the seek target, and the page
michael@0 1536 // at the end offset is after the seek target.
michael@0 1537 SEEK_LOG(PR_LOG_DEBUG, ("Interval narrowed, terminating bisection."));
michael@0 1538 break;
michael@0 1539 }
michael@0 1540
michael@0 1541 // Guess bisection point.
michael@0 1542 duration = endTime - startTime;
michael@0 1543 target = (double)(seekTarget - startTime) / (double)duration;
michael@0 1544 guess = startOffset + startLength +
michael@0 1545 static_cast<ogg_int64_t>((double)interval * target);
michael@0 1546 guess = std::min(guess, endOffset - PAGE_STEP);
michael@0 1547 if (mustBackoff) {
michael@0 1548 // We previously failed to determine the time at the guess offset,
michael@0 1549 // probably because we ran out of data to decode. This usually happens
michael@0 1550 // when we guess very close to the end offset. So reduce the guess
michael@0 1551 // offset using an exponential backoff until we determine the time.
michael@0 1552 SEEK_LOG(PR_LOG_DEBUG, ("Backing off %d bytes, backsteps=%d",
michael@0 1553 static_cast<int32_t>(PAGE_STEP * pow(2.0, backsteps)), backsteps));
michael@0 1554 guess -= PAGE_STEP * static_cast<ogg_int64_t>(pow(2.0, backsteps));
michael@0 1555
michael@0 1556 if (guess <= startOffset) {
michael@0 1557 // We've tried to backoff to before the start offset of our seek
michael@0 1558 // range. This means we couldn't find a seek termination position
michael@0 1559 // near the end of the seek range, so just set the seek termination
michael@0 1560 // condition, and break out of the bisection loop. We'll begin
michael@0 1561 // decoding from the start of the seek range.
michael@0 1562 interval = 0;
michael@0 1563 break;
michael@0 1564 }
michael@0 1565
michael@0 1566 backsteps = std::min(backsteps + 1, maxBackStep);
michael@0 1567 // We reset mustBackoff. If we still need to backoff further, it will
michael@0 1568 // be set to true again.
michael@0 1569 mustBackoff = false;
michael@0 1570 } else {
michael@0 1571 backsteps = 0;
michael@0 1572 }
michael@0 1573 guess = std::max(guess, startOffset + startLength);
michael@0 1574
michael@0 1575 SEEK_LOG(PR_LOG_DEBUG, ("Seek loop start[o=%lld..%lld t=%lld] "
michael@0 1576 "end[o=%lld t=%lld] "
michael@0 1577 "interval=%lld target=%lf guess=%lld",
michael@0 1578 startOffset, (startOffset+startLength), startTime,
michael@0 1579 endOffset, endTime, interval, target, guess));
michael@0 1580
michael@0 1581 NS_ASSERTION(guess >= startOffset + startLength, "Guess must be after range start");
michael@0 1582 NS_ASSERTION(guess < endOffset, "Guess must be before range end");
michael@0 1583 NS_ASSERTION(guess != previousGuess, "Guess should be different to previous");
michael@0 1584 previousGuess = guess;
michael@0 1585
michael@0 1586 hops++;
michael@0 1587
michael@0 1588 // Locate the next page after our seek guess, and then figure out the
michael@0 1589 // granule time of the audio and video bitstreams there. We can then
michael@0 1590 // make a bisection decision based on our location in the media.
michael@0 1591 PageSyncResult res = PageSync(resource,
michael@0 1592 &mOggState,
michael@0 1593 false,
michael@0 1594 guess,
michael@0 1595 endOffset,
michael@0 1596 &page,
michael@0 1597 skippedBytes);
michael@0 1598 NS_ENSURE_TRUE(res != PAGE_SYNC_ERROR, NS_ERROR_FAILURE);
michael@0 1599
michael@0 1600 if (res == PAGE_SYNC_END_OF_RANGE) {
michael@0 1601 // Our guess was too close to the end, we've ended up reading the end
michael@0 1602 // page. Backoff exponentially from the end point, in case the last
michael@0 1603 // page/frame/sample is huge.
michael@0 1604 mustBackoff = true;
michael@0 1605 SEEK_LOG(PR_LOG_DEBUG, ("Hit the end of range, backing off"));
michael@0 1606 continue;
michael@0 1607 }
michael@0 1608
michael@0 1609 // We've located a page of length |ret| at |guess + skippedBytes|.
michael@0 1610 // Remember where the page is located.
michael@0 1611 pageOffset = guess + skippedBytes;
michael@0 1612 pageLength = page.header_len + page.body_len;
michael@0 1613
michael@0 1614 // Read pages until we can determine the granule time of the audio and
michael@0 1615 // video bitstream.
michael@0 1616 ogg_int64_t audioTime = -1;
michael@0 1617 ogg_int64_t videoTime = -1;
michael@0 1618 do {
michael@0 1619 // Add the page to its codec state, determine its granule time.
michael@0 1620 uint32_t serial = ogg_page_serialno(&page);
michael@0 1621 OggCodecState* codecState = mCodecStore.Get(serial);
michael@0 1622 if (codecState && codecState->mActive) {
michael@0 1623 int ret = ogg_stream_pagein(&codecState->mState, &page);
michael@0 1624 NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
michael@0 1625 }
michael@0 1626
michael@0 1627 ogg_int64_t granulepos = ogg_page_granulepos(&page);
michael@0 1628
michael@0 1629 if (HasAudio() && granulepos > 0 && audioTime == -1) {
michael@0 1630 if (mVorbisState && serial == mVorbisState->mSerial) {
michael@0 1631 audioTime = mVorbisState->Time(granulepos);
michael@0 1632 #ifdef MOZ_OPUS
michael@0 1633 } else if (mOpusState && serial == mOpusState->mSerial) {
michael@0 1634 audioTime = mOpusState->Time(granulepos);
michael@0 1635 #endif
michael@0 1636 }
michael@0 1637 }
michael@0 1638
michael@0 1639 if (HasVideo() &&
michael@0 1640 granulepos > 0 &&
michael@0 1641 serial == mTheoraState->mSerial &&
michael@0 1642 videoTime == -1) {
michael@0 1643 videoTime = mTheoraState->Time(granulepos);
michael@0 1644 }
michael@0 1645
michael@0 1646 if (pageOffset + pageLength >= endOffset) {
michael@0 1647 // Hit end of readable data.
michael@0 1648 break;
michael@0 1649 }
michael@0 1650
michael@0 1651 if (!ReadOggPage(&page)) {
michael@0 1652 break;
michael@0 1653 }
michael@0 1654
michael@0 1655 } while ((HasAudio() && audioTime == -1) ||
michael@0 1656 (HasVideo() && videoTime == -1));
michael@0 1657
michael@0 1658
michael@0 1659 if ((HasAudio() && audioTime == -1) ||
michael@0 1660 (HasVideo() && videoTime == -1))
michael@0 1661 {
michael@0 1662 // We don't have timestamps for all active tracks...
michael@0 1663 if (pageOffset == startOffset + startLength &&
michael@0 1664 pageOffset + pageLength >= endOffset) {
michael@0 1665 // We read the entire interval without finding timestamps for all
michael@0 1666 // active tracks. We know the interval start offset is before the seek
michael@0 1667 // target, and the interval end is after the seek target, and we can't
michael@0 1668 // terminate inside the interval, so we terminate the seek at the
michael@0 1669 // start of the interval.
michael@0 1670 interval = 0;
michael@0 1671 break;
michael@0 1672 }
michael@0 1673
michael@0 1674 // We should backoff; cause the guess to back off from the end, so
michael@0 1675 // that we've got more room to capture.
michael@0 1676 mustBackoff = true;
michael@0 1677 continue;
michael@0 1678 }
michael@0 1679
michael@0 1680 // We've found appropriate time stamps here. Proceed to bisect
michael@0 1681 // the search space.
michael@0 1682 granuleTime = std::max(audioTime, videoTime);
michael@0 1683 NS_ASSERTION(granuleTime > 0, "Must get a granuletime");
michael@0 1684 break;
michael@0 1685 } // End of "until we determine time at guess offset" loop.
michael@0 1686
michael@0 1687 if (interval == 0) {
michael@0 1688 // Seek termination condition; we've found the page boundary of the
michael@0 1689 // last page before the target, and the first page after the target.
michael@0 1690 SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", startOffset));
michael@0 1691 NS_ASSERTION(startTime < aTarget, "Start time must always be less than target");
michael@0 1692 res = resource->Seek(nsISeekableStream::NS_SEEK_SET, startOffset);
michael@0 1693 NS_ENSURE_SUCCESS(res,res);
michael@0 1694 if (NS_FAILED(ResetDecode())) {
michael@0 1695 return NS_ERROR_FAILURE;
michael@0 1696 }
michael@0 1697 break;
michael@0 1698 }
michael@0 1699
michael@0 1700 SEEK_LOG(PR_LOG_DEBUG, ("Time at offset %lld is %lld", guess, granuleTime));
michael@0 1701 if (granuleTime < seekTarget && granuleTime > seekLowerBound) {
michael@0 1702 // We're within the fuzzy region in which we want to terminate the search.
michael@0 1703 res = resource->Seek(nsISeekableStream::NS_SEEK_SET, pageOffset);
michael@0 1704 NS_ENSURE_SUCCESS(res,res);
michael@0 1705 if (NS_FAILED(ResetDecode())) {
michael@0 1706 return NS_ERROR_FAILURE;
michael@0 1707 }
michael@0 1708 SEEK_LOG(PR_LOG_DEBUG, ("Terminating seek at offset=%lld", pageOffset));
michael@0 1709 break;
michael@0 1710 }
michael@0 1711
michael@0 1712 if (granuleTime >= seekTarget) {
michael@0 1713 // We've landed after the seek target.
michael@0 1714 NS_ASSERTION(pageOffset < endOffset, "offset_end must decrease");
michael@0 1715 endOffset = pageOffset;
michael@0 1716 endTime = granuleTime;
michael@0 1717 } else if (granuleTime < seekTarget) {
michael@0 1718 // Landed before seek target.
michael@0 1719 NS_ASSERTION(pageOffset >= startOffset + startLength,
michael@0 1720 "Bisection point should be at or after end of first page in interval");
michael@0 1721 startOffset = pageOffset;
michael@0 1722 startLength = pageLength;
michael@0 1723 startTime = granuleTime;
michael@0 1724 }
michael@0 1725 NS_ASSERTION(startTime < seekTarget, "Must be before seek target");
michael@0 1726 NS_ASSERTION(endTime >= seekTarget, "End must be after seek target");
michael@0 1727 }
michael@0 1728
michael@0 1729 SEEK_LOG(PR_LOG_DEBUG, ("Seek complete in %d bisections.", hops));
michael@0 1730
michael@0 1731 return NS_OK;
michael@0 1732 }
michael@0 1733
michael@0 1734 nsresult OggReader::GetBuffered(dom::TimeRanges* aBuffered, int64_t aStartTime)
michael@0 1735 {
michael@0 1736 {
michael@0 1737 mozilla::ReentrantMonitorAutoEnter mon(mMonitor);
michael@0 1738 if (mIsChained)
michael@0 1739 return NS_ERROR_FAILURE;
michael@0 1740 }
michael@0 1741 #ifdef OGG_ESTIMATE_BUFFERED
michael@0 1742 return MediaDecoderReader::GetBuffered(aBuffered, aStartTime);
michael@0 1743 #else
michael@0 1744 // HasAudio and HasVideo are not used here as they take a lock and cause
michael@0 1745 // a deadlock. Accessing mInfo doesn't require a lock - it doesn't change
michael@0 1746 // after metadata is read.
michael@0 1747 if (!mInfo.HasValidMedia()) {
michael@0 1748 // No need to search through the file if there are no audio or video tracks
michael@0 1749 return NS_OK;
michael@0 1750 }
michael@0 1751
michael@0 1752 MediaResource* resource = mDecoder->GetResource();
michael@0 1753 nsTArray<MediaByteRange> ranges;
michael@0 1754 nsresult res = resource->GetCachedRanges(ranges);
michael@0 1755 NS_ENSURE_SUCCESS(res, res);
michael@0 1756
michael@0 1757 // Traverse across the buffered byte ranges, determining the time ranges
michael@0 1758 // they contain. MediaResource::GetNextCachedData(offset) returns -1 when
michael@0 1759 // offset is after the end of the media resource, or there's no more cached
michael@0 1760 // data after the offset. This loop will run until we've checked every
michael@0 1761 // buffered range in the media, in increasing order of offset.
michael@0 1762 nsAutoOggSyncState sync;
michael@0 1763 for (uint32_t index = 0; index < ranges.Length(); index++) {
michael@0 1764 // Ensure the offsets are after the header pages.
michael@0 1765 int64_t startOffset = ranges[index].mStart;
michael@0 1766 int64_t endOffset = ranges[index].mEnd;
michael@0 1767
michael@0 1768 // Because the granulepos time is actually the end time of the page,
michael@0 1769 // we special-case (startOffset == 0) so that the first
michael@0 1770 // buffered range always appears to be buffered from the media start
michael@0 1771 // time, rather than from the end-time of the first page.
michael@0 1772 int64_t startTime = (startOffset == 0) ? aStartTime : -1;
michael@0 1773
michael@0 1774 // Find the start time of the range. Read pages until we find one with a
michael@0 1775 // granulepos which we can convert into a timestamp to use as the time of
michael@0 1776 // the start of the buffered range.
michael@0 1777 ogg_sync_reset(&sync.mState);
michael@0 1778 while (startTime == -1) {
michael@0 1779 ogg_page page;
michael@0 1780 int32_t discard;
michael@0 1781 PageSyncResult res = PageSync(resource,
michael@0 1782 &sync.mState,
michael@0 1783 true,
michael@0 1784 startOffset,
michael@0 1785 endOffset,
michael@0 1786 &page,
michael@0 1787 discard);
michael@0 1788 if (res == PAGE_SYNC_ERROR) {
michael@0 1789 return NS_ERROR_FAILURE;
michael@0 1790 } else if (res == PAGE_SYNC_END_OF_RANGE) {
michael@0 1791 // Hit the end of range without reading a page, give up trying to
michael@0 1792 // find a start time for this buffered range, skip onto the next one.
michael@0 1793 break;
michael@0 1794 }
michael@0 1795
michael@0 1796 int64_t granulepos = ogg_page_granulepos(&page);
michael@0 1797 if (granulepos == -1) {
michael@0 1798 // Page doesn't have an end time, advance to the next page
michael@0 1799 // until we find one.
michael@0 1800 startOffset += page.header_len + page.body_len;
michael@0 1801 continue;
michael@0 1802 }
michael@0 1803
michael@0 1804 uint32_t serial = ogg_page_serialno(&page);
michael@0 1805 if (mVorbisState && serial == mVorbisSerial) {
michael@0 1806 startTime = VorbisState::Time(&mVorbisInfo, granulepos);
michael@0 1807 NS_ASSERTION(startTime > 0, "Must have positive start time");
michael@0 1808 }
michael@0 1809 #ifdef MOZ_OPUS
michael@0 1810 else if (mOpusState && serial == mOpusSerial) {
michael@0 1811 startTime = OpusState::Time(mOpusPreSkip, granulepos);
michael@0 1812 NS_ASSERTION(startTime > 0, "Must have positive start time");
michael@0 1813 }
michael@0 1814 #endif /* MOZ_OPUS */
michael@0 1815 else if (mTheoraState && serial == mTheoraSerial) {
michael@0 1816 startTime = TheoraState::Time(&mTheoraInfo, granulepos);
michael@0 1817 NS_ASSERTION(startTime > 0, "Must have positive start time");
michael@0 1818 }
michael@0 1819 else if (mCodecStore.Contains(serial)) {
michael@0 1820 // Stream is not the theora or vorbis stream we're playing,
michael@0 1821 // but is one that we have header data for.
michael@0 1822 startOffset += page.header_len + page.body_len;
michael@0 1823 continue;
michael@0 1824 }
michael@0 1825 else {
michael@0 1826 // Page is for a stream we don't know about (possibly a chained
michael@0 1827 // ogg), return OK to abort the finding any further ranges. This
michael@0 1828 // prevents us searching through the rest of the media when we
michael@0 1829 // may not be able to extract timestamps from it.
michael@0 1830 SetChained(true);
michael@0 1831 return NS_OK;
michael@0 1832 }
michael@0 1833 }
michael@0 1834
michael@0 1835 if (startTime != -1) {
michael@0 1836 // We were able to find a start time for that range, see if we can
michael@0 1837 // find an end time.
michael@0 1838 int64_t endTime = RangeEndTime(startOffset, endOffset, true);
michael@0 1839 if (endTime != -1) {
michael@0 1840 aBuffered->Add((startTime - aStartTime) / static_cast<double>(USECS_PER_S),
michael@0 1841 (endTime - aStartTime) / static_cast<double>(USECS_PER_S));
michael@0 1842 }
michael@0 1843 }
michael@0 1844 }
michael@0 1845
michael@0 1846 return NS_OK;
michael@0 1847 #endif
michael@0 1848 }
michael@0 1849
michael@0 1850 OggCodecStore::OggCodecStore()
michael@0 1851 : mMonitor("CodecStore")
michael@0 1852 {
michael@0 1853 }
michael@0 1854
michael@0 1855 void OggCodecStore::Add(uint32_t serial, OggCodecState* codecState)
michael@0 1856 {
michael@0 1857 MonitorAutoLock mon(mMonitor);
michael@0 1858 mCodecStates.Put(serial, codecState);
michael@0 1859 }
michael@0 1860
michael@0 1861 bool OggCodecStore::Contains(uint32_t serial)
michael@0 1862 {
michael@0 1863 MonitorAutoLock mon(mMonitor);
michael@0 1864 return mCodecStates.Get(serial, nullptr);
michael@0 1865 }
michael@0 1866
michael@0 1867 OggCodecState* OggCodecStore::Get(uint32_t serial)
michael@0 1868 {
michael@0 1869 MonitorAutoLock mon(mMonitor);
michael@0 1870 return mCodecStates.Get(serial);
michael@0 1871 }
michael@0 1872
michael@0 1873 } // namespace mozilla
michael@0 1874

mercurial