Files
palemoon27/dom/media/MediaDecoderReader.cpp
T
roytam1 0c01ffebb4 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1197669 - Part1 - 1.Enable the testcase on B2G. 2. fix the mimetype check in testcase. r=jwwang (9bbae092f)
- Bug 1197669 - Part2 - 1. Ensure the EOS is sent to MediaCodec. 2. mEndOfStream should be protected by monitor. r=sotaro (62b8e66b7)
- bug 1193922 skip SetIsOnlyNodeForContext after shutdown r=padenot (df991d0af)
- Minor (1d695f7c4)
- Bug 1197051 - Don't try to decode ahead while seeking - r=jya (7d8d877ad)
- Bug 1197075: P1. Revert "Bug 1197051 - Don't try to decode ahead while seeking - r=jya". r=edwin (e837b12e3)
- Bug 1143575. Push all available frames to the compositor. r=cpearce (109e54ae8)
- partial of  Bug 1197075: P2. Revert "Bug 1171257 - Add force decode ahead to MediaFormatReader r=jya,bholley". r=edwin (5f4859e47)
- Minor (a841ca407)
- Bug 1199155. Part 2 - fix includes and forward declarations. r=roc. (ca523595a)
- Bug 1199155. Part 1 - move dom/media/DecodedStream.* to dom/media/mediasink/ and implement the interface of MediaSink. r=roc. (90b376a24)
- Bug 1199155. Part 3 - rename mDecodedStream to mStreamSink. r=roc. (1aae54328)
- Bug 1203374. Part 1 - extract the code of computing canplaythrough so it is reusable. r=jya. (acc5fc8c6)
- Bug 1203374. Part 2 - duplicate the implementation of MediaDecoder::CanPlayThrough so MDSM can call its own CanPlayThrough() on its own thread. r=jya. (a042541a8)
- Bug 1182928 - Disable dormant mode for EME videos in Firefox Beta and Release. r=sotaro (ea5964ae9)
- Bug 1197022 - [EME] Disable dormant mode for EME videos on all channels. r=kentuckyfriedtakahe (742d00672)
- Bug 1179110 - Fix ComputePlaybackRate. r=jww (a43272af5)
- Bug 1178622 - Fix enum-to-string mismatch in MediaDecoder.cpp. r=jya. (b9da7ebf3)
- Bug 1203418. Part 1 - cache the results of ComputePlaybackRate() so they can be mirrored by MDSM. r=cpearce. (95f7ac068)
- Bug 1203418. Part 2 - duplicate the implementation of MediaDecoder::GetStatistics so MDSM can call it on its own thread. r=cpearce. (2755b85a7)
- Bug 1203418. Part 3 - ensure MDSM::mPlaybackOffset and MediaDecoder::mPlaybackPosition are mono-increasing to avoid "jitter" in calculating playback statistics. r=cpearce. (54dcb6219)
- Bug 1104616 - Proxy video capture access to the main process. r=jesup,glandium,mrbkap (cfdd08459)
- Bug 1200614 - Protect the capture engines array from concurrent access (during shutdown). r=jesup (c4dbd6e10)
- Bug 1200614 - Check whether engines are still alive when webrtc ops run. r=jesup (d42d3474f)
- Bug 1194640 - add NSPR logging of camera capabilities, r=jesup (c7365b916)
- Bug 1204413 - Make MediaDecoder::IsTransportSeekable run on the main thread. r=kinetik. (098868b0d)
- Bug 1198202 - Increase hardware video decoding fallback threshold. r=ajones (1059f9b0f)
- Bug 1201197 - add dedicated listener to enumerateDevices. r=jesup (a51477957)
- Bug 1199562. Part 1 - rename mAudioSink to mMediaSink as well as related member names. r=roc. (c2db1b4d4)
- Bug 1199562. Part 2 - replace usage of mStreamSink with mMediaSink in most cases. r=roc. (7aa0754c4)
- Bug 1199562. Part 3 - remove unused code. r=roc. (343c5be2f)
- Bug 1203877 - Remove MediaDecoder::UpdatePlaybackOffset. r=kinetik. (4476dd1f8)
- Bug 1204430. Part 2 - mirror MediaDecoder::mMediaSeekable. r=kinetik. (51ca1197d)
- Bug 1206576 - Dispatch some MDSM functions to hide its internal thread model. r=jya. (74b9a169c)
- Bug 1206574 - Remove AbstractMediaDecoder::IsShutdown(). r=cpearce. (4ed17a27f)
- Bug 1207017. Part 1 - fix coding styles. r=kinetik. (cc10a28c3)
- Bug 1207017. Part 2 - remove duplicated GetStateMachine(). r=kinetik. (19b663098)
- Bug 1200477 - Allow building with WebRTC disabled. r=glandium,mrbkap (56055c3c7)
2021-10-14 09:25:28 +08:00

455 lines
13 KiB
C++

/* -*- 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 "MediaDecoderReader.h"
#include "AbstractMediaDecoder.h"
#include "MediaResource.h"
#include "VideoUtils.h"
#include "ImageContainer.h"
#include "nsPrintfCString.h"
#include "mozilla/mozalloc.h"
#include <stdint.h>
#include <algorithm>
using namespace mozilla::media;
namespace mozilla {
// Un-comment to enable logging of seek bisections.
//#define SEEK_LOGGING
extern PRLogModuleInfo* gMediaDecoderLog;
#define DECODER_LOG(x, ...) \
MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, mDecoder, ##__VA_ARGS__))
// Same workaround as MediaDecoderStateMachine.cpp.
#define DECODER_WARN_HELPER(a, b) NS_WARNING b
#define DECODER_WARN(x, ...) \
DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder, ##__VA_ARGS__).get()))
class VideoQueueMemoryFunctor : public nsDequeFunctor {
public:
VideoQueueMemoryFunctor() : mSize(0) {}
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
virtual void* operator()(void* aObject) {
const VideoData* v = static_cast<const VideoData*>(aObject);
mSize += v->SizeOfIncludingThis(MallocSizeOf);
return nullptr;
}
size_t mSize;
};
class AudioQueueMemoryFunctor : public nsDequeFunctor {
public:
AudioQueueMemoryFunctor() : mSize(0) {}
MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
virtual void* operator()(void* aObject) {
const AudioData* audioData = static_cast<const AudioData*>(aObject);
mSize += audioData->SizeOfIncludingThis(MallocSizeOf);
return nullptr;
}
size_t mSize;
};
MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder,
TaskQueue* aBorrowedTaskQueue)
: mAudioCompactor(mAudioQueue)
, mDecoder(aDecoder)
, mTaskQueue(aBorrowedTaskQueue ? aBorrowedTaskQueue
: new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
/* aSupportsTailDispatch = */ true))
, mWatchManager(this, mTaskQueue)
, mTimer(new MediaTimer())
, mBuffered(mTaskQueue, TimeIntervals(), "MediaDecoderReader::mBuffered (Canonical)")
, mDuration(mTaskQueue, NullableTimeUnit(), "MediaDecoderReader::mDuration (Mirror)")
, mThrottleDuration(TimeDuration::FromMilliseconds(500))
, mLastThrottledNotify(TimeStamp::Now() - mThrottleDuration)
, mIgnoreAudioOutputFormat(false)
, mHitAudioDecodeError(false)
, mShutdown(false)
, mTaskQueueIsBorrowed(!!aBorrowedTaskQueue)
, mAudioDiscontinuity(false)
, mVideoDiscontinuity(false)
{
MOZ_COUNT_CTOR(MediaDecoderReader);
MOZ_ASSERT(NS_IsMainThread());
// Dispatch initialization that needs to happen on that task queue.
nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderReader::InitializationTask);
mTaskQueue->Dispatch(r.forget());
}
void
MediaDecoderReader::InitializationTask()
{
if (mDecoder->CanonicalDurationOrNull()) {
mDuration.Connect(mDecoder->CanonicalDurationOrNull());
}
// Initialize watchers.
mWatchManager.Watch(mDuration, &MediaDecoderReader::UpdateBuffered);
}
MediaDecoderReader::~MediaDecoderReader()
{
MOZ_ASSERT(mShutdown);
MOZ_ASSERT(!mDecoder);
ResetDecode();
MOZ_COUNT_DTOR(MediaDecoderReader);
}
size_t MediaDecoderReader::SizeOfVideoQueueInBytes() const
{
VideoQueueMemoryFunctor functor;
mVideoQueue.LockedForEach(functor);
return functor.mSize;
}
size_t MediaDecoderReader::SizeOfAudioQueueInBytes() const
{
AudioQueueMemoryFunctor functor;
mAudioQueue.LockedForEach(functor);
return functor.mSize;
}
size_t MediaDecoderReader::SizeOfVideoQueueInFrames()
{
return mVideoQueue.GetSize();
}
size_t MediaDecoderReader::SizeOfAudioQueueInFrames()
{
return mAudioQueue.GetSize();
}
nsresult MediaDecoderReader::ResetDecode()
{
VideoQueue().Reset();
AudioQueue().Reset();
mAudioDiscontinuity = true;
mVideoDiscontinuity = true;
mBaseAudioPromise.RejectIfExists(CANCELED, __func__);
mBaseVideoPromise.RejectIfExists(CANCELED, __func__);
return NS_OK;
}
nsRefPtr<MediaDecoderReader::VideoDataPromise>
MediaDecoderReader::DecodeToFirstVideoData()
{
MOZ_ASSERT(OnTaskQueue());
typedef MediaDecoderReader::VideoDataPromise PromiseType;
nsRefPtr<PromiseType::Private> p = new PromiseType::Private(__func__);
nsRefPtr<MediaDecoderReader> self = this;
InvokeUntil([self] () -> bool {
MOZ_ASSERT(self->OnTaskQueue());
NS_ENSURE_TRUE(!self->mShutdown, false);
bool skip = false;
if (!self->DecodeVideoFrame(skip, 0)) {
self->VideoQueue().Finish();
return !!self->VideoQueue().PeekFront();
}
return true;
}, [self] () -> bool {
MOZ_ASSERT(self->OnTaskQueue());
return self->VideoQueue().GetSize();
})->Then(OwnerThread(), __func__, [self, p] () {
p->Resolve(self->VideoQueue().PeekFront(), __func__);
}, [p] () {
// We don't have a way to differentiate EOS, error, and shutdown here. :-(
p->Reject(END_OF_STREAM, __func__);
});
return p.forget();
}
void
MediaDecoderReader::UpdateBuffered()
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
mBuffered = GetBuffered();
}
void
MediaDecoderReader::ThrottledNotifyDataArrived(const Interval<int64_t>& aInterval)
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE_VOID(!mShutdown);
if (mThrottledInterval.isNothing()) {
mThrottledInterval.emplace(aInterval);
} else if (!mThrottledInterval.ref().Contiguous(aInterval)) {
DoThrottledNotify();
mThrottledInterval.emplace(aInterval);
} else {
mThrottledInterval = Some(mThrottledInterval.ref().Span(aInterval));
}
// If it's been long enough since our last update, do it.
if (TimeStamp::Now() - mLastThrottledNotify > mThrottleDuration) {
DoThrottledNotify();
} else if (!mThrottledNotify.Exists()) {
// Otherwise, schedule an update if one isn't scheduled already.
nsRefPtr<MediaDecoderReader> self = this;
mThrottledNotify.Begin(
mTimer->WaitUntil(mLastThrottledNotify + mThrottleDuration, __func__)
->Then(OwnerThread(), __func__,
[self] () -> void {
self->mThrottledNotify.Complete();
NS_ENSURE_TRUE_VOID(!self->mShutdown);
self->DoThrottledNotify();
},
[self] () -> void {
self->mThrottledNotify.Complete();
NS_WARNING("throttle callback rejected");
})
);
}
}
void
MediaDecoderReader::DoThrottledNotify()
{
MOZ_ASSERT(OnTaskQueue());
mLastThrottledNotify = TimeStamp::Now();
mThrottledNotify.DisconnectIfExists();
Interval<int64_t> interval = mThrottledInterval.ref();
mThrottledInterval.reset();
NotifyDataArrived(interval);
}
media::TimeIntervals
MediaDecoderReader::GetBuffered()
{
MOZ_ASSERT(OnTaskQueue());
NS_ENSURE_TRUE(HaveStartTime(), media::TimeIntervals());
AutoPinned<MediaResource> stream(mDecoder->GetResource());
if (!mDuration.Ref().isSome()) {
return TimeIntervals();
}
return GetEstimatedBufferedTimeRanges(stream, mDuration.Ref().ref().ToMicroseconds());
}
nsRefPtr<MediaDecoderReader::MetadataPromise>
MediaDecoderReader::AsyncReadMetadata()
{
typedef ReadMetadataFailureReason Reason;
MOZ_ASSERT(OnTaskQueue());
mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
if (IsWaitingMediaResources()) {
return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
}
// Attempt to read the metadata.
nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags));
// Reading metadata can cause us to discover that we need resources (a hardware
// resource initialized but not yet ready for use).
if (IsWaitingMediaResources()) {
return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
}
// We're not waiting for anything. If we didn't get the metadata, that's an error.
if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) {
DECODER_WARN("ReadMetadata failed, rv=%x HasValidMedia=%d", rv, metadata->mInfo.HasValidMedia());
return MetadataPromise::CreateAndReject(Reason::METADATA_ERROR, __func__);
}
// Success!
return MetadataPromise::CreateAndResolve(metadata, __func__);
}
class ReRequestVideoWithSkipTask : public nsRunnable
{
public:
ReRequestVideoWithSkipTask(MediaDecoderReader* aReader,
int64_t aTimeThreshold)
: mReader(aReader)
, mTimeThreshold(aTimeThreshold)
{
}
NS_METHOD Run()
{
MOZ_ASSERT(mReader->OnTaskQueue());
// Make sure ResetDecode hasn't been called in the mean time.
if (!mReader->mBaseVideoPromise.IsEmpty()) {
mReader->RequestVideoData(/* aSkip = */ true, mTimeThreshold);
}
return NS_OK;
}
private:
nsRefPtr<MediaDecoderReader> mReader;
const int64_t mTimeThreshold;
};
class ReRequestAudioTask : public nsRunnable
{
public:
explicit ReRequestAudioTask(MediaDecoderReader* aReader)
: mReader(aReader)
{
}
NS_METHOD Run()
{
MOZ_ASSERT(mReader->OnTaskQueue());
// Make sure ResetDecode hasn't been called in the mean time.
if (!mReader->mBaseAudioPromise.IsEmpty()) {
mReader->RequestAudioData();
}
return NS_OK;
}
private:
nsRefPtr<MediaDecoderReader> mReader;
};
nsRefPtr<MediaDecoderReader::VideoDataPromise>
MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe,
int64_t aTimeThreshold)
{
nsRefPtr<VideoDataPromise> p = mBaseVideoPromise.Ensure(__func__);
bool skip = aSkipToNextKeyframe;
while (VideoQueue().GetSize() == 0 &&
!VideoQueue().IsFinished()) {
if (!DecodeVideoFrame(skip, aTimeThreshold)) {
VideoQueue().Finish();
} else if (skip) {
// We still need to decode more data in order to skip to the next
// keyframe. Post another task to the decode task queue to decode
// again. We don't just decode straight in a loop here, as that
// would hog the decode task queue.
RefPtr<nsIRunnable> task(new ReRequestVideoWithSkipTask(this, aTimeThreshold));
mTaskQueue->Dispatch(task.forget());
return p;
}
}
if (VideoQueue().GetSize() > 0) {
nsRefPtr<VideoData> v = VideoQueue().PopFront();
if (v && mVideoDiscontinuity) {
v->mDiscontinuity = true;
mVideoDiscontinuity = false;
}
mBaseVideoPromise.Resolve(v, __func__);
} else if (VideoQueue().IsFinished()) {
mBaseVideoPromise.Reject(END_OF_STREAM, __func__);
} else {
MOZ_ASSERT(false, "Dropping this promise on the floor");
}
return p;
}
nsRefPtr<MediaDecoderReader::AudioDataPromise>
MediaDecoderReader::RequestAudioData()
{
nsRefPtr<AudioDataPromise> p = mBaseAudioPromise.Ensure(__func__);
while (AudioQueue().GetSize() == 0 &&
!AudioQueue().IsFinished()) {
if (!DecodeAudioData()) {
AudioQueue().Finish();
break;
}
// AudioQueue size is still zero, post a task to try again.
// (|mVideoSinkBufferCount| > 0)
if (AudioQueue().GetSize() == 0 && mTaskQueue) {
RefPtr<nsIRunnable> task(new ReRequestAudioTask(this));
mTaskQueue->Dispatch(task.forget());
return p;
}
}
if (AudioQueue().GetSize() > 0) {
nsRefPtr<AudioData> a = AudioQueue().PopFront();
if (mAudioDiscontinuity) {
a->mDiscontinuity = true;
mAudioDiscontinuity = false;
}
mBaseAudioPromise.Resolve(a, __func__);
} else if (AudioQueue().IsFinished()) {
mBaseAudioPromise.Reject(mHitAudioDecodeError ? DECODE_ERROR : END_OF_STREAM, __func__);
mHitAudioDecodeError = false;
} else {
MOZ_ASSERT(false, "Dropping this promise on the floor");
}
return p;
}
void
MediaDecoderReader::BreakCycles()
{
// Nothing left to do here these days. We keep this method around so that, if
// we need it, we don't have to make all of the subclass implementations call
// the superclass method again.
}
nsRefPtr<ShutdownPromise>
MediaDecoderReader::Shutdown()
{
MOZ_ASSERT(OnTaskQueue());
mShutdown = true;
mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
mThrottledNotify.DisconnectIfExists();
ReleaseMediaResources();
mDuration.DisconnectIfConnected();
mBuffered.DisconnectAll();
// Shut down the watch manager before shutting down our task queue.
mWatchManager.Shutdown();
nsRefPtr<ShutdownPromise> p;
// Spin down the task queue if necessary. We wait until BreakCycles to null
// out mTaskQueue, since otherwise any remaining tasks could crash when they
// invoke OnTaskQueue().
if (mTaskQueue && !mTaskQueueIsBorrowed) {
// If we own our task queue, shutdown ends when the task queue is done.
p = mTaskQueue->BeginShutdown();
} else {
// If we don't own our task queue, we resolve immediately (though
// asynchronously).
p = ShutdownPromise::CreateAndResolve(true, __func__);
}
mTimer = nullptr;
mDecoder = nullptr;
return p;
}
} // namespace mozilla
#undef DECODER_LOG
#undef DECODER_WARN
#undef DECODER_WARN_HELPER