зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1194518 - Part 3: Delaying decoder wrapper, ensures a decoder appears consistently slow. r=jya
--HG-- extra : histedit_source : 56b8bcfdccceae4baa063f771ec0266348c5ede0
This commit is contained in:
Родитель
252bc4441c
Коммит
1c67bdca71
|
@ -55,7 +55,12 @@ DecoderFuzzingWrapper::Flush()
|
||||||
{
|
{
|
||||||
DFW_LOGV("");
|
DFW_LOGV("");
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
return mDecoder->Flush();
|
// Flush may output some frames (though unlikely).
|
||||||
|
// Flush may block a bit, it's ok if we output some frames in the meantime.
|
||||||
|
nsresult result = mDecoder->Flush();
|
||||||
|
// Clear any delayed output we may have.
|
||||||
|
mCallbackWrapper->ClearDelayedOutput();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
|
@ -63,6 +68,8 @@ DecoderFuzzingWrapper::Drain()
|
||||||
{
|
{
|
||||||
DFW_LOGV("");
|
DFW_LOGV("");
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
|
// Note: The decoder should callback DrainComplete(), we'll drain the
|
||||||
|
// delayed output (if any) then.
|
||||||
return mDecoder->Drain();
|
return mDecoder->Drain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +78,10 @@ DecoderFuzzingWrapper::Shutdown()
|
||||||
{
|
{
|
||||||
DFW_LOGV("");
|
DFW_LOGV("");
|
||||||
MOZ_ASSERT(mDecoder);
|
MOZ_ASSERT(mDecoder);
|
||||||
return mDecoder->Shutdown();
|
// Both shutdowns below may block a bit.
|
||||||
|
nsresult result = mDecoder->Shutdown();
|
||||||
|
mCallbackWrapper->Shutdown();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@ -93,6 +103,9 @@ DecoderFuzzingWrapper::ConfigurationChanged(const TrackInfo& aConfig)
|
||||||
|
|
||||||
DecoderCallbackFuzzingWrapper::DecoderCallbackFuzzingWrapper(MediaDataDecoderCallback* aCallback)
|
DecoderCallbackFuzzingWrapper::DecoderCallbackFuzzingWrapper(MediaDataDecoderCallback* aCallback)
|
||||||
: mCallback(aCallback)
|
: mCallback(aCallback)
|
||||||
|
, mDontDelayInputExhausted(false)
|
||||||
|
, mDraining(false)
|
||||||
|
, mTaskQueue(new TaskQueue(SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaFuzzingWrapper"), 1)))
|
||||||
{
|
{
|
||||||
CFW_LOGV("aCallback=%p", aCallback);
|
CFW_LOGV("aCallback=%p", aCallback);
|
||||||
}
|
}
|
||||||
|
@ -102,25 +115,96 @@ DecoderCallbackFuzzingWrapper::~DecoderCallbackFuzzingWrapper()
|
||||||
CFW_LOGV("");
|
CFW_LOGV("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::SetVideoOutputMinimumInterval(
|
||||||
|
TimeDuration aFrameOutputMinimumInterval)
|
||||||
|
{
|
||||||
|
CFW_LOGD("aFrameOutputMinimumInterval=%fms",
|
||||||
|
aFrameOutputMinimumInterval.ToMilliseconds());
|
||||||
|
mFrameOutputMinimumInterval = aFrameOutputMinimumInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::SetDontDelayInputExhausted(
|
||||||
|
bool aDontDelayInputExhausted)
|
||||||
|
{
|
||||||
|
CFW_LOGD("aDontDelayInputExhausted=%d",
|
||||||
|
aDontDelayInputExhausted);
|
||||||
|
mDontDelayInputExhausted = aDontDelayInputExhausted;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DecoderCallbackFuzzingWrapper::Output(MediaData* aData)
|
DecoderCallbackFuzzingWrapper::Output(MediaData* aData)
|
||||||
{
|
{
|
||||||
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethodWithArg<StorensRefPtrPassByPtr<MediaData>>(
|
||||||
|
this, &DecoderCallbackFuzzingWrapper::Output, aData);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
CFW_LOGV("aData.mTime=%lld", aData->mTime);
|
CFW_LOGV("aData.mTime=%lld", aData->mTime);
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
|
if (mFrameOutputMinimumInterval) {
|
||||||
|
if (!mPreviousOutput.IsNull()) {
|
||||||
|
if (!mDelayedOutput.IsEmpty()) {
|
||||||
|
// We already have some delayed frames, just add this one to the queue.
|
||||||
|
mDelayedOutput.AppendElement(MakePair<nsRefPtr<MediaData>, bool>(aData, false));
|
||||||
|
CFW_LOGD("delaying output of sample@%lld, total queued:%d",
|
||||||
|
aData->mTime, int(mDelayedOutput.Length()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TimeStamp::Now() < mPreviousOutput + mFrameOutputMinimumInterval) {
|
||||||
|
// Frame arriving too soon after the previous one, start queuing.
|
||||||
|
mDelayedOutput.AppendElement(MakePair<nsRefPtr<MediaData>, bool>(aData, false));
|
||||||
|
CFW_LOGD("delaying output of sample@%lld, first queued", aData->mTime);
|
||||||
|
if (!mDelayedOutputTimer) {
|
||||||
|
mDelayedOutputTimer = new MediaTimer();
|
||||||
|
}
|
||||||
|
ScheduleOutputDelayedFrame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we're here, we're going to actually output a frame -> Record time.
|
||||||
|
mPreviousOutput = TimeStamp::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Passing the data straight through, no need to dispatch to another queue,
|
||||||
|
// callback should deal with that.
|
||||||
mCallback->Output(aData);
|
mCallback->Output(aData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DecoderCallbackFuzzingWrapper::Error()
|
DecoderCallbackFuzzingWrapper::Error()
|
||||||
{
|
{
|
||||||
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::Error);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
CFW_LOGV("");
|
CFW_LOGV("");
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
|
ClearDelayedOutput();
|
||||||
mCallback->Error();
|
mCallback->Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DecoderCallbackFuzzingWrapper::InputExhausted()
|
DecoderCallbackFuzzingWrapper::InputExhausted()
|
||||||
{
|
{
|
||||||
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::InputExhausted);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mDontDelayInputExhausted && !mDelayedOutput.IsEmpty()) {
|
||||||
|
MediaDataAndInputExhausted& last = mDelayedOutput.LastElement();
|
||||||
|
CFW_LOGD("InputExhausted delayed until after output of sample@%lld",
|
||||||
|
last.first()->mTime);
|
||||||
|
last.second() = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
CFW_LOGV("");
|
CFW_LOGV("");
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
mCallback->InputExhausted();
|
mCallback->InputExhausted();
|
||||||
|
@ -129,14 +213,33 @@ DecoderCallbackFuzzingWrapper::InputExhausted()
|
||||||
void
|
void
|
||||||
DecoderCallbackFuzzingWrapper::DrainComplete()
|
DecoderCallbackFuzzingWrapper::DrainComplete()
|
||||||
{
|
{
|
||||||
CFW_LOGV("");
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::DrainComplete);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
mCallback->DrainComplete();
|
if (mDelayedOutput.IsEmpty()) {
|
||||||
|
// No queued output -> Draining is complete now.
|
||||||
|
CFW_LOGV("No delayed output -> DrainComplete now");
|
||||||
|
mCallback->DrainComplete();
|
||||||
|
} else {
|
||||||
|
// Queued output waiting -> Make sure we call DrainComplete when it's empty.
|
||||||
|
CFW_LOGD("Delayed output -> DrainComplete later");
|
||||||
|
mDraining = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DecoderCallbackFuzzingWrapper::ReleaseMediaResources()
|
DecoderCallbackFuzzingWrapper::ReleaseMediaResources()
|
||||||
{
|
{
|
||||||
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::ReleaseMediaResources);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
CFW_LOGV("");
|
CFW_LOGV("");
|
||||||
MOZ_ASSERT(mCallback);
|
MOZ_ASSERT(mCallback);
|
||||||
mCallback->ReleaseMediaResources();
|
mCallback->ReleaseMediaResources();
|
||||||
|
@ -150,4 +253,72 @@ DecoderCallbackFuzzingWrapper::OnReaderTaskQueue()
|
||||||
return mCallback->OnReaderTaskQueue();
|
return mCallback->OnReaderTaskQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::ScheduleOutputDelayedFrame()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||||
|
nsRefPtr<DecoderCallbackFuzzingWrapper> self = this;
|
||||||
|
mDelayedOutputTimer->WaitUntil(
|
||||||
|
mPreviousOutput + mFrameOutputMinimumInterval,
|
||||||
|
__func__)
|
||||||
|
->Then(mTaskQueue, __func__,
|
||||||
|
[self] () -> void { self->OutputDelayedFrame(); },
|
||||||
|
[self] () -> void { self->OutputDelayedFrame(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::OutputDelayedFrame()
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
|
||||||
|
if (mDelayedOutput.IsEmpty()) {
|
||||||
|
if (mDraining) {
|
||||||
|
// No more output, and we were draining -> Send DrainComplete.
|
||||||
|
mDraining = false;
|
||||||
|
mCallback->DrainComplete();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MediaDataAndInputExhausted& data = mDelayedOutput[0];
|
||||||
|
CFW_LOGD("Outputting delayed sample@%lld, remaining:%d",
|
||||||
|
data.first()->mTime, int(mDelayedOutput.Length() - 1));
|
||||||
|
mPreviousOutput = TimeStamp::Now();
|
||||||
|
mCallback->Output(data.first());
|
||||||
|
if (data.second()) {
|
||||||
|
CFW_LOGD("InputExhausted after delayed sample@%lld", data.first()->mTime);
|
||||||
|
mCallback->InputExhausted();
|
||||||
|
}
|
||||||
|
mDelayedOutput.RemoveElementAt(0);
|
||||||
|
if (!mDelayedOutput.IsEmpty()) {
|
||||||
|
// More output -> Send it later.
|
||||||
|
ScheduleOutputDelayedFrame();
|
||||||
|
} else if (mDraining) {
|
||||||
|
// No more output, and we were draining -> Send DrainComplete.
|
||||||
|
CFW_LOGD("DrainComplete");
|
||||||
|
mDraining = false;
|
||||||
|
mCallback->DrainComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::ClearDelayedOutput()
|
||||||
|
{
|
||||||
|
if (!mTaskQueue->IsCurrentThreadIn()) {
|
||||||
|
nsCOMPtr<nsIRunnable> task =
|
||||||
|
NS_NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::ClearDelayedOutput);
|
||||||
|
mTaskQueue->Dispatch(task.forget());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mDelayedOutputTimer = nullptr;
|
||||||
|
mDelayedOutput.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
DecoderCallbackFuzzingWrapper::Shutdown()
|
||||||
|
{
|
||||||
|
DFW_LOGV("Shutting down mTaskQueue");
|
||||||
|
mTaskQueue->BeginShutdown();
|
||||||
|
mTaskQueue->AwaitIdle();
|
||||||
|
DFW_LOGV("mTaskQueue shut down");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
|
@ -7,10 +7,36 @@
|
||||||
#if !defined(FuzzingWrapper_h_)
|
#if !defined(FuzzingWrapper_h_)
|
||||||
#define FuzzingWrapper_h_
|
#define FuzzingWrapper_h_
|
||||||
|
|
||||||
|
#include "mozilla/Pair.h"
|
||||||
#include "PlatformDecoderModule.h"
|
#include "PlatformDecoderModule.h"
|
||||||
|
|
||||||
namespace mozilla {
|
namespace mozilla {
|
||||||
|
|
||||||
|
// Fuzzing wrapper for media decoders.
|
||||||
|
//
|
||||||
|
// DecoderFuzzingWrapper owns the DecoderCallbackFuzzingWrapper, and inserts
|
||||||
|
// itself between the reader and the decoder.
|
||||||
|
// DecoderCallbackFuzzingWrapper inserts itself between a decoder and its
|
||||||
|
// callback.
|
||||||
|
// Together they are used to introduce some fuzzing, (e.g. delay output).
|
||||||
|
//
|
||||||
|
// Normally:
|
||||||
|
// ====================================>
|
||||||
|
// reader decoder
|
||||||
|
// <------------------------------------
|
||||||
|
//
|
||||||
|
// With fuzzing:
|
||||||
|
// ======> DecoderFuzzingWrapper ======>
|
||||||
|
// reader v decoder
|
||||||
|
// <-- DecoderCallbackFuzzingWrapper <--
|
||||||
|
//
|
||||||
|
// Creation order should be:
|
||||||
|
// 1. Create DecoderCallbackFuzzingWrapper, give the expected callback target.
|
||||||
|
// 2. Create actual decoder, give DecoderCallbackFuzzingWrapper as callback.
|
||||||
|
// 3. Create DecoderFuzzingWrapper, give decoder and DecoderCallbackFuzzingWrapper.
|
||||||
|
// DecoderFuzzingWrapper is what the reader sees as decoder, it owns the
|
||||||
|
// real decoder and the DecoderCallbackFuzzingWrapper.
|
||||||
|
|
||||||
class DecoderCallbackFuzzingWrapper : public MediaDataDecoderCallback
|
class DecoderCallbackFuzzingWrapper : public MediaDataDecoderCallback
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -18,6 +44,15 @@ public:
|
||||||
|
|
||||||
explicit DecoderCallbackFuzzingWrapper(MediaDataDecoderCallback* aCallback);
|
explicit DecoderCallbackFuzzingWrapper(MediaDataDecoderCallback* aCallback);
|
||||||
|
|
||||||
|
// Enforce a minimum interval between output frames (i.e., limit frame rate).
|
||||||
|
// Of course, if the decoder is even slower, this won't have any effect.
|
||||||
|
void SetVideoOutputMinimumInterval(TimeDuration aFrameOutputMinimumInterval);
|
||||||
|
// If false (default), if frames are delayed, any InputExhausted is delayed to
|
||||||
|
// be later sent after the corresponding delayed frame.
|
||||||
|
// If true, InputExhausted are passed through immediately; This could result
|
||||||
|
// in lots of frames being decoded and queued for delayed output!
|
||||||
|
void SetDontDelayInputExhausted(bool aDontDelayInputExhausted);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual ~DecoderCallbackFuzzingWrapper();
|
virtual ~DecoderCallbackFuzzingWrapper();
|
||||||
|
|
||||||
|
@ -30,6 +65,31 @@ private:
|
||||||
bool OnReaderTaskQueue() override;
|
bool OnReaderTaskQueue() override;
|
||||||
|
|
||||||
MediaDataDecoderCallback* mCallback;
|
MediaDataDecoderCallback* mCallback;
|
||||||
|
|
||||||
|
// Settings for minimum frame output interval & InputExhausted,
|
||||||
|
// should be set during init and then only read on mTaskQueue.
|
||||||
|
TimeDuration mFrameOutputMinimumInterval;
|
||||||
|
bool mDontDelayInputExhausted;
|
||||||
|
// Members for minimum frame output interval & InputExhausted,
|
||||||
|
// should only be accessed on mTaskQueue.
|
||||||
|
TimeStamp mPreviousOutput;
|
||||||
|
// First member is the frame to be delayed.
|
||||||
|
// Second member is true if an 'InputExhausted' arrived after that frame; in
|
||||||
|
// which case an InputExhausted will be sent after finally outputting the frame.
|
||||||
|
typedef Pair<nsRefPtr<MediaData>, bool> MediaDataAndInputExhausted;
|
||||||
|
nsTArray<MediaDataAndInputExhausted> mDelayedOutput;
|
||||||
|
nsRefPtr<MediaTimer> mDelayedOutputTimer;
|
||||||
|
// If draining, a 'DrainComplete' will be sent after all delayed frames have
|
||||||
|
// been output.
|
||||||
|
bool mDraining;
|
||||||
|
// All callbacks are redirected through this task queue, both to avoid locking
|
||||||
|
// and to have a consistent sequencing of callbacks.
|
||||||
|
nsRefPtr<TaskQueue> mTaskQueue;
|
||||||
|
void ScheduleOutputDelayedFrame();
|
||||||
|
void OutputDelayedFrame();
|
||||||
|
public: // public for the benefit of DecoderFuzzingWrapper.
|
||||||
|
void ClearDelayedOutput();
|
||||||
|
void Shutdown();
|
||||||
};
|
};
|
||||||
|
|
||||||
class DecoderFuzzingWrapper : public MediaDataDecoder
|
class DecoderFuzzingWrapper : public MediaDataDecoder
|
||||||
|
|
Загрузка…
Ссылка в новой задаче