/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TrackBuffer.h" #include "ContainerParser.h" #include "MediaSourceDecoder.h" #include "SharedThreadPool.h" #include "MediaTaskQueue.h" #include "SourceBufferDecoder.h" #include "SourceBufferResource.h" #include "VideoUtils.h" #include "mozilla/dom/TimeRanges.h" #include "nsError.h" #include "nsIRunnable.h" #include "nsThreadUtils.h" #include "prlog.h" #ifdef PR_LOGGING extern PRLogModuleInfo* GetMediaSourceLog(); extern PRLogModuleInfo* GetMediaSourceAPILog(); #define MSE_DEBUG(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG, (__VA_ARGS__)) #define MSE_DEBUGV(...) PR_LOG(GetMediaSourceLog(), PR_LOG_DEBUG+1, (__VA_ARGS__)) #define MSE_API(...) PR_LOG(GetMediaSourceAPILog(), PR_LOG_DEBUG, (__VA_ARGS__)) #else #define MSE_DEBUG(...) #define MSE_DEBUGV(...) #define MSE_API(...) #endif namespace mozilla { TrackBuffer::TrackBuffer(MediaSourceDecoder* aParentDecoder, const nsACString& aType) : mParentDecoder(aParentDecoder) , mType(aType) , mLastStartTimestamp(0) { MOZ_COUNT_CTOR(TrackBuffer); mParser = ContainerParser::CreateForMIMEType(aType); mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool()); aParentDecoder->AddTrackBuffer(this); } TrackBuffer::~TrackBuffer() { MOZ_COUNT_DTOR(TrackBuffer); } class ReleaseDecoderTask : public nsRunnable { public: explicit ReleaseDecoderTask(SourceBufferDecoder* aDecoder) : mDecoder(aDecoder) { } NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL { mDecoder->GetReader()->BreakCycles(); mDecoder = nullptr; return NS_OK; } private: nsRefPtr mDecoder; }; MOZ_STACK_CLASS class DecodersToInitialize MOZ_FINAL { public: explicit DecodersToInitialize(TrackBuffer* aOwner) : mOwner(aOwner) { } ~DecodersToInitialize() { for (size_t i = 0; i < mDecoders.Length(); i++) { mOwner->QueueInitializeDecoder(mDecoders[i]); } } bool NewDecoder() { nsRefPtr decoder = mOwner->NewDecoder(); if (!decoder) { return false; } mDecoders.AppendElement(decoder); return true; } private: TrackBuffer* mOwner; nsAutoTArray,2> mDecoders; }; void TrackBuffer::Shutdown() { // Finish any decoder initialization, which may add to mInitializedDecoders. // Shutdown waits for any pending events, which may require the monitor, // so we must not hold the monitor during this call. mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn(); mTaskQueue->Shutdown(); mTaskQueue = nullptr; ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mDecoders.Length(); ++i) { mDecoders[i]->GetReader()->Shutdown(); } mInitializedDecoders.Clear(); mParentDecoder = nullptr; } bool TrackBuffer::AppendData(const uint8_t* aData, uint32_t aLength) { MOZ_ASSERT(NS_IsMainThread()); DecodersToInitialize decoders(this); // TODO: Run more of the buffer append algorithm asynchronously. if (mParser->IsInitSegmentPresent(aData, aLength)) { MSE_DEBUG("TrackBuffer(%p)::AppendData: New initialization segment.", this); if (!decoders.NewDecoder()) { return false; } } else if (!mParser->HasInitData()) { MSE_DEBUG("TrackBuffer(%p)::AppendData: Non-init segment appended during initialization.", this); return false; } int64_t start, end; if (mParser->ParseStartAndEndTimestamps(aData, aLength, start, end)) { if (mParser->IsMediaSegmentPresent(aData, aLength) && mLastEndTimestamp && !mParser->TimestampsFuzzyEqual(start, mLastEndTimestamp.value())) { MSE_DEBUG("TrackBuffer(%p)::AppendData: Data last=[%lld, %lld] overlaps [%lld, %lld]", this, mLastStartTimestamp, mLastEndTimestamp.value(), start, end); // This data is earlier in the timeline than data we have already // processed, so we must create a new decoder to handle the decoding. if (!decoders.NewDecoder()) { return false; } MSE_DEBUG("TrackBuffer(%p)::AppendData: Decoder marked as initialized.", this); const nsTArray& initData = mParser->InitData(); AppendDataToCurrentResource(initData.Elements(), initData.Length()); mLastStartTimestamp = start; } else { MSE_DEBUG("TrackBuffer(%p)::AppendData: Segment last=[%lld, %lld] [%lld, %lld]", this, mLastStartTimestamp, mLastEndTimestamp ? mLastEndTimestamp.value() : 0, start, end); } mLastEndTimestamp.reset(); mLastEndTimestamp.emplace(end); } if (!AppendDataToCurrentResource(aData, aLength)) { return false; } // Schedule the state machine thread to ensure playback starts if required // when data is appended. mParentDecoder->ScheduleStateMachineThread(); return true; } bool TrackBuffer::AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength) { MOZ_ASSERT(NS_IsMainThread()); if (!mCurrentDecoder) { return false; } SourceBufferResource* resource = mCurrentDecoder->GetResource(); int64_t appendOffset = resource->GetLength(); resource->AppendData(aData, aLength); // XXX: For future reference: NDA call must run on the main thread. mCurrentDecoder->NotifyDataArrived(reinterpret_cast(aData), aLength, appendOffset); mParentDecoder->NotifyTimeRangesChanged(); return true; } bool TrackBuffer::EvictData(uint32_t aThreshold) { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); int64_t totalSize = 0; for (uint32_t i = 0; i < mDecoders.Length(); ++i) { totalSize += mDecoders[i]->GetResource()->GetSize(); } int64_t toEvict = totalSize - aThreshold; if (toEvict <= 0) { return false; } for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) { MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u threshold=%u toEvict=%lld", this, i, aThreshold, toEvict); toEvict -= mInitializedDecoders[i]->GetResource()->EvictData(toEvict); if (!mInitializedDecoders[i]->GetResource()->GetSize() && mInitializedDecoders[i] != mCurrentDecoder) { RemoveDecoder(mInitializedDecoders[i]); } } return toEvict < (totalSize - aThreshold); } void TrackBuffer::EvictBefore(double aTime) { MOZ_ASSERT(NS_IsMainThread()); ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) { int64_t endOffset = mInitializedDecoders[i]->ConvertToByteOffset(aTime); if (endOffset > 0) { MSE_DEBUG("TrackBuffer(%p)::EvictBefore decoder=%u offset=%lld", this, i, endOffset); mInitializedDecoders[i]->GetResource()->EvictBefore(endOffset); if (!mInitializedDecoders[i]->GetResource()->GetSize() && mInitializedDecoders[i] != mCurrentDecoder) { RemoveDecoder(mInitializedDecoders[i]); } } } } double TrackBuffer::Buffered(dom::TimeRanges* aRanges) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); double highestEndTime = 0; for (uint32_t i = 0; i < mDecoders.Length(); ++i) { nsRefPtr r = new dom::TimeRanges(); mDecoders[i]->GetBuffered(r); if (r->Length() > 0) { highestEndTime = std::max(highestEndTime, r->GetEndTime()); aRanges->Union(r); } } return highestEndTime; } already_AddRefed TrackBuffer::NewDecoder() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mParentDecoder); DiscardDecoder(); nsRefPtr decoder = mParentDecoder->CreateSubDecoder(mType); if (!decoder) { return nullptr; } ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); mCurrentDecoder = decoder; mDecoders.AppendElement(decoder); mLastStartTimestamp = 0; mLastEndTimestamp.reset(); decoder->SetTaskQueue(mTaskQueue); return decoder.forget(); } bool TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder) { RefPtr task = NS_NewRunnableMethodWithArg(this, &TrackBuffer::InitializeDecoder, aDecoder); if (NS_FAILED(mTaskQueue->Dispatch(task))) { MSE_DEBUG("MediaSourceReader(%p): Failed to enqueue decoder initialization task", this); RemoveDecoder(aDecoder); return false; } return true; } void TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder) { MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); // ReadMetadata may block the thread waiting on data, so it must not be // called with the monitor held. mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn(); MediaDecoderReader* reader = aDecoder->GetReader(); MSE_DEBUG("TrackBuffer(%p): Initializing subdecoder %p reader %p", this, aDecoder, reader); MediaInfo mi; nsAutoPtr tags; // TODO: Handle metadata. nsresult rv = reader->ReadMetadata(&mi, getter_Transfers(tags)); reader->SetIdle(); if (NS_SUCCEEDED(rv) && reader->IsWaitingOnCDMResource()) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); mWaitingDecoders.AppendElement(aDecoder); return; } aDecoder->SetTaskQueue(nullptr); if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) { // XXX: Need to signal error back to owning SourceBuffer. MSE_DEBUG("TrackBuffer(%p): Reader %p failed to initialize rv=%x audio=%d video=%d", this, reader, rv, mi.HasAudio(), mi.HasVideo()); RemoveDecoder(aDecoder); return; } if (mi.HasVideo()) { MSE_DEBUG("TrackBuffer(%p): Reader %p video resolution=%dx%d", this, reader, mi.mVideo.mDisplay.width, mi.mVideo.mDisplay.height); } if (mi.HasAudio()) { MSE_DEBUG("TrackBuffer(%p): Reader %p audio sampleRate=%d channels=%d", this, reader, mi.mAudio.mRate, mi.mAudio.mChannels); } if (!RegisterDecoder(aDecoder)) { // XXX: Need to signal error back to owning SourceBuffer. MSE_DEBUG("TrackBuffer(%p): Reader %p not activated", this, reader); RemoveDecoder(aDecoder); return; } MSE_DEBUG("TrackBuffer(%p): Reader %p activated", this, reader); } bool TrackBuffer::ValidateTrackFormats(const MediaInfo& aInfo) { if (mInfo.HasAudio() != aInfo.HasAudio() || mInfo.HasVideo() != aInfo.HasVideo()) { MSE_DEBUG("TrackBuffer(%p)::ValidateTrackFormats audio/video track mismatch", this); return false; } // TODO: Support dynamic audio format changes. if (mInfo.HasAudio() && (mInfo.mAudio.mRate != aInfo.mAudio.mRate || mInfo.mAudio.mChannels != aInfo.mAudio.mChannels)) { MSE_DEBUG("TrackBuffer(%p)::ValidateTrackFormats audio format mismatch", this); return false; } return true; } bool TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); const MediaInfo& info = aDecoder->GetReader()->GetMediaInfo(); // Initialize the track info since this is the first decoder. if (mInitializedDecoders.IsEmpty()) { mInfo = info; mParentDecoder->OnTrackBufferConfigured(this, mInfo); } if (!ValidateTrackFormats(info)) { MSE_DEBUG("TrackBuffer(%p)::RegisterDecoder with mismatched audio/video tracks", this); return false; } mInitializedDecoders.AppendElement(aDecoder); mParentDecoder->NotifyTimeRangesChanged(); return true; } void TrackBuffer::DiscardDecoder() { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); if (mCurrentDecoder) { mCurrentDecoder->GetResource()->Ended(); } mCurrentDecoder = nullptr; } void TrackBuffer::Detach() { MOZ_ASSERT(NS_IsMainThread()); if (mCurrentDecoder) { DiscardDecoder(); } } bool TrackBuffer::HasInitSegment() { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); return mParser->HasInitData(); } bool TrackBuffer::IsReady() { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); MOZ_ASSERT((mInfo.HasAudio() || mInfo.HasVideo()) || mInitializedDecoders.IsEmpty()); return mParser->HasInitData() && (mInfo.HasAudio() || mInfo.HasVideo()); } bool TrackBuffer::ContainsTime(int64_t aTime) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) { nsRefPtr r = new dom::TimeRanges(); mInitializedDecoders[i]->GetBuffered(r); if (r->Find(double(aTime) / USECS_PER_S) != dom::TimeRanges::NoIndex) { return true; } } return false; } void TrackBuffer::BreakCycles() { MOZ_ASSERT(NS_IsMainThread()); for (uint32_t i = 0; i < mDecoders.Length(); ++i) { mDecoders[i]->BreakCycles(); } mDecoders.Clear(); // These are cleared in Shutdown() MOZ_ASSERT(mInitializedDecoders.IsEmpty()); MOZ_ASSERT(!mParentDecoder); } void TrackBuffer::ResetDecode() { for (uint32_t i = 0; i < mDecoders.Length(); ++i) { mDecoders[i]->GetReader()->ResetDecode(); } } const nsTArray>& TrackBuffer::Decoders() { // XXX assert OnDecodeThread return mInitializedDecoders; } #ifdef MOZ_EME nsresult TrackBuffer::SetCDMProxy(CDMProxy* aProxy) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mDecoders.Length(); ++i) { nsresult rv = mDecoders[i]->SetCDMProxy(aProxy); NS_ENSURE_SUCCESS(rv, rv); } for (uint32_t i = 0; i < mWaitingDecoders.Length(); ++i) { CDMCaps::AutoLock caps(aProxy->Capabilites()); caps.CallOnMainThreadWhenCapsAvailable( NS_NewRunnableMethodWithArg(this, &TrackBuffer::QueueInitializeDecoder, mWaitingDecoders[i])); } mWaitingDecoders.Clear(); return NS_OK; } #endif #if defined(DEBUG) void TrackBuffer::Dump(const char* aPath) { char path[255]; PR_snprintf(path, sizeof(path), "%s/trackbuffer-%p", aPath, this); PR_MkDir(path, 0700); for (uint32_t i = 0; i < mDecoders.Length(); ++i) { char buf[255]; PR_snprintf(buf, sizeof(buf), "%s/reader-%p", path, mDecoders[i]->GetReader()); PR_MkDir(buf, 0700); mDecoders[i]->GetResource()->Dump(buf); } } #endif class DelayedDispatchToMainThread : public nsRunnable { public: explicit DelayedDispatchToMainThread(SourceBufferDecoder* aDecoder) : mDecoder(aDecoder) { } NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL { // Shutdown the reader, and remove its reference to the decoder // so that it can't accidentally read it after the decoder // is destroyed. mDecoder->GetReader()->Shutdown(); mDecoder->GetReader()->ClearDecoder(); RefPtr task = new ReleaseDecoderTask(mDecoder); mDecoder = nullptr; // task now holds the only ref to the decoder. NS_DispatchToMainThread(task); return NS_OK; } private: RefPtr mDecoder; }; void TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder) { RefPtr task; nsRefPtr taskQueue; { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); if (mInitializedDecoders.RemoveElement(aDecoder)) { taskQueue = aDecoder->GetReader()->GetTaskQueue(); task = new DelayedDispatchToMainThread(aDecoder); } else { task = new ReleaseDecoderTask(aDecoder); } mDecoders.RemoveElement(aDecoder); if (mCurrentDecoder == aDecoder) { DiscardDecoder(); } } // At this point, task should be holding the only reference to aDecoder. if (taskQueue) { // If we were initialized, post the task via the reader's // task queue to ensure that the reader isn't in the middle // of an existing task. taskQueue->Dispatch(task); } else { NS_DispatchToMainThread(task); } } } // namespace mozilla