/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(MediaQueue_h_) # define MediaQueue_h_ # include # include "mozilla/RecursiveMutex.h" # include "mozilla/TaskQueue.h" # include "nsDeque.h" # include "MediaEventSource.h" # include "TimeUnits.h" namespace mozilla { extern LazyLogModule gMediaDecoderLog; # define QLOG(msg, ...) \ MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, \ ("MediaQueue=%p " msg, this, ##__VA_ARGS__)) class AudioData; class VideoData; template struct TimestampAdjustmentTrait { static const bool mValue = false; }; template <> struct TimestampAdjustmentTrait { static const bool mValue = true; }; template <> struct TimestampAdjustmentTrait { static const bool mValue = true; }; template struct NonTimestampAdjustmentTrait { static const bool mValue = !TimestampAdjustmentTrait::mValue; }; template class MediaQueue : private nsRefPtrDeque { public: MediaQueue() : nsRefPtrDeque(), mRecursiveMutex("mediaqueue"), mEndOfStream(false) {} ~MediaQueue() { Reset(); } inline size_t GetSize() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return nsRefPtrDeque::GetSize(); } template ::mValue, bool> = true> inline void AdjustTimeStampIfNeeded(U* aItem) { static_assert(std::is_same_v || std::is_same_v); if (mOffset != media::TimeUnit::Zero()) { const auto prev = aItem->mTime, prevEndTime = aItem->GetEndTime(); aItem->mTime += mOffset; if (!aItem->mTime.IsValid()) { NS_WARNING("Reverting timestamp adjustment due to sample overflow!"); aItem->mTime = prev; } else { QLOG("adjusted %s sample [%" PRId64 ",%" PRId64 "] -> [%" PRId64 ",%" PRId64 "]", std::is_same_v ? "audio" : "video", prev.ToMicroseconds(), prevEndTime.ToMicroseconds(), aItem->mTime.ToMicroseconds(), aItem->GetEndTime().ToMicroseconds()); } } } template ::mValue, bool> = true> inline void AdjustTimeStampIfNeeded(U* aItem) {} inline void PushFront(T* aItem) { RecursiveMutexAutoLock lock(mRecursiveMutex); AdjustTimeStampIfNeeded(aItem); nsRefPtrDeque::PushFront(aItem); } inline void Push(T* aItem) { MOZ_DIAGNOSTIC_ASSERT(aItem); Push(do_AddRef(aItem)); } inline void Push(already_AddRefed aItem) { RecursiveMutexAutoLock lock(mRecursiveMutex); T* item = aItem.take(); MOZ_DIAGNOSTIC_ASSERT(item); MOZ_DIAGNOSTIC_ASSERT(item->GetEndTime() >= item->mTime); AdjustTimeStampIfNeeded(item); nsRefPtrDeque::Push(dont_AddRef(item)); mPushEvent.Notify(RefPtr(item)); // Pushing new data after queue has ended means that the stream is active // again, so we should not mark it as ended. if (mEndOfStream) { mEndOfStream = false; } } inline already_AddRefed PopFront() { RecursiveMutexAutoLock lock(mRecursiveMutex); RefPtr rv = nsRefPtrDeque::PopFront(); if (rv) { MOZ_DIAGNOSTIC_ASSERT(rv->GetEndTime() >= rv->mTime); mPopFrontEvent.Notify(RefPtr(rv)); } return rv.forget(); } inline already_AddRefed PopBack() { RecursiveMutexAutoLock lock(mRecursiveMutex); return nsRefPtrDeque::Pop(); } inline RefPtr PeekFront() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return nsRefPtrDeque::PeekFront(); } inline RefPtr PeekBack() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return nsRefPtrDeque::Peek(); } void Reset() { RecursiveMutexAutoLock lock(mRecursiveMutex); nsRefPtrDeque::Erase(); SetOffset(media::TimeUnit::Zero()); mEndOfStream = false; } bool AtEndOfStream() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return GetSize() == 0 && mEndOfStream; } // Returns true if the media queue has had its last item added to it. // This happens when the media stream has been completely decoded. Note this // does not mean that the corresponding stream has finished playback. bool IsFinished() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return mEndOfStream; } // Informs the media queue that it won't be receiving any more items. void Finish() { RecursiveMutexAutoLock lock(mRecursiveMutex); if (!mEndOfStream) { mEndOfStream = true; mFinishEvent.Notify(); } } // Returns the approximate number of microseconds of items in the queue. int64_t Duration() const { RecursiveMutexAutoLock lock(mRecursiveMutex); if (GetSize() == 0) { return 0; } T* last = nsRefPtrDeque::Peek(); T* first = nsRefPtrDeque::PeekFront(); return (last->GetEndTime() - first->mTime).ToMicroseconds(); } void LockedForEach(nsDequeFunctor& aFunctor) const { RecursiveMutexAutoLock lock(mRecursiveMutex); nsRefPtrDeque::ForEach(aFunctor); } // Fill aResult with the elements which end later than the given time aTime. void GetElementsAfter(const media::TimeUnit& aTime, nsTArray>* aResult) { GetElementsAfterStrict(aTime.ToMicroseconds(), aResult); } void GetFirstElements(uint32_t aMaxElements, nsTArray>* aResult) { RecursiveMutexAutoLock lock(mRecursiveMutex); for (size_t i = 0; i < aMaxElements && i < GetSize(); ++i) { *aResult->AppendElement() = nsRefPtrDeque::ObjectAt(i); } } uint32_t AudioFramesCount() { static_assert(std::is_same_v, "Only usable with MediaQueue"); RecursiveMutexAutoLock lock(mRecursiveMutex); uint32_t frames = 0; for (size_t i = 0; i < GetSize(); ++i) { T* v = nsRefPtrDeque::ObjectAt(i); frames += v->Frames(); } return frames; } bool SetOffset(const media::TimeUnit& aOffset) { if (!aOffset.IsValid()) { QLOG("Invalid offset!"); return false; } RecursiveMutexAutoLock lock(mRecursiveMutex); mOffset = aOffset; QLOG("Set media queue offset %" PRId64, mOffset.ToMicroseconds()); return true; } media::TimeUnit GetOffset() const { RecursiveMutexAutoLock lock(mRecursiveMutex); return mOffset; } MediaEventSource>& PopFrontEvent() { return mPopFrontEvent; } MediaEventSource>& PushEvent() { return mPushEvent; } MediaEventSource& FinishEvent() { return mFinishEvent; } private: // Extracts elements from the queue into aResult, in order. // Elements whose end time is before or equal to aTime are ignored. void GetElementsAfterStrict(int64_t aTime, nsTArray>* aResult) { RecursiveMutexAutoLock lock(mRecursiveMutex); if (GetSize() == 0) return; size_t i; for (i = GetSize() - 1; i > 0; --i) { T* v = nsRefPtrDeque::ObjectAt(i); if (v->GetEndTime().ToMicroseconds() < aTime) break; } for (; i < GetSize(); ++i) { RefPtr elem = nsRefPtrDeque::ObjectAt(i); if (elem->GetEndTime().ToMicroseconds() > aTime) { aResult->AppendElement(elem); } } } mutable RecursiveMutex mRecursiveMutex MOZ_UNANNOTATED; MediaEventProducer> mPopFrontEvent; MediaEventProducer> mPushEvent; MediaEventProducer mFinishEvent; // True when we've decoded the last frame of data in the // bitstream for which we're queueing frame data. bool mEndOfStream; // This offset will be added to any data pushed into the queue. We use it when // the media queue starts receiving looped data, which timestamp needs to be // modified. media::TimeUnit mOffset; }; } // namespace mozilla # undef QLOG #endif