gecko-dev/dom/media/VideoFrameContainer.cpp

360 строки
12 KiB
C++

/* -*- 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/. */
#include "VideoFrameContainer.h"
#include "mozilla/Telemetry.h"
#include "MediaDecoderOwner.h"
#include "Tracing.h"
using namespace mozilla::layers;
namespace mozilla {
static LazyLogModule gVideoFrameContainerLog("VideoFrameContainer");
#define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
namespace {
template <Telemetry::HistogramID ID>
class AutoTimer {
// Set a threshold to reduce performance overhead
// for we're measuring hot spots.
static const uint32_t sThresholdMS = 1000;
public:
~AutoTimer() {
auto end = TimeStamp::Now();
auto diff = uint32_t((end - mStart).ToMilliseconds());
if (diff > sThresholdMS) {
Telemetry::Accumulate(ID, diff);
}
}
private:
const TimeStamp mStart = TimeStamp::Now();
};
} // namespace
VideoFrameContainer::VideoFrameContainer(
MediaDecoderOwner* aOwner, already_AddRefed<ImageContainer> aContainer)
: mOwner(aOwner),
mImageContainer(aContainer),
mMutex("nsVideoFrameContainer"),
mBlackImage(nullptr),
mFrameID(0),
mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE),
mFrameIDForPendingPrincipalHandle(0),
mMainThread(aOwner->AbstractMainThread()) {
NS_ASSERTION(aOwner, "aOwner must not be null");
NS_ASSERTION(mImageContainer, "aContainer must not be null");
}
VideoFrameContainer::~VideoFrameContainer() {}
PrincipalHandle VideoFrameContainer::GetLastPrincipalHandle() {
MutexAutoLock lock(mMutex);
return GetLastPrincipalHandleLocked();
}
PrincipalHandle VideoFrameContainer::GetLastPrincipalHandleLocked() {
return mLastPrincipalHandle;
}
void VideoFrameContainer::UpdatePrincipalHandleForFrameID(
const PrincipalHandle& aPrincipalHandle,
const ImageContainer::FrameID& aFrameID) {
MutexAutoLock lock(mMutex);
UpdatePrincipalHandleForFrameIDLocked(aPrincipalHandle, aFrameID);
}
void VideoFrameContainer::UpdatePrincipalHandleForFrameIDLocked(
const PrincipalHandle& aPrincipalHandle,
const ImageContainer::FrameID& aFrameID) {
if (mPendingPrincipalHandle == aPrincipalHandle) {
return;
}
mPendingPrincipalHandle = aPrincipalHandle;
mFrameIDForPendingPrincipalHandle = aFrameID;
}
static bool SetImageToBlackPixel(PlanarYCbCrImage* aImage) {
uint8_t blackPixel[] = {0x10, 0x80, 0x80};
PlanarYCbCrData data;
data.mYChannel = blackPixel;
data.mCbChannel = blackPixel + 1;
data.mCrChannel = blackPixel + 2;
data.mYStride = data.mCbCrStride = 1;
data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
return aImage->CopyData(data);
}
class VideoFrameContainerInvalidateRunnable : public Runnable {
public:
explicit VideoFrameContainerInvalidateRunnable(
VideoFrameContainer* aVideoFrameContainer)
: Runnable("VideoFrameContainerInvalidateRunnable"),
mVideoFrameContainer(aVideoFrameContainer) {}
NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread());
mVideoFrameContainer->Invalidate();
return NS_OK;
}
private:
RefPtr<VideoFrameContainer> mVideoFrameContainer;
};
void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment) {
TRACE();
if (aSegment.IsEmpty()) {
return;
}
MutexAutoLock lock(mMutex);
AutoTimer<Telemetry::VFC_SETVIDEOSEGMENT_LOCK_HOLD_MS> lockHold;
// Collect any new frames produced in this iteration.
AutoTArray<ImageContainer::NonOwningImage, 4> newImages;
PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
VideoSegment::ConstChunkIterator iter(aSegment);
while (!iter.IsEnded()) {
VideoChunk chunk = *iter;
const VideoFrame* frame = &chunk.mFrame;
if (*frame == mLastPlayedVideoFrame) {
iter.Next();
continue;
}
Image* image = frame->GetImage();
CONTAINER_LOG(
LogLevel::Verbose,
("VideoFrameContainer %p writing video frame %p (%d x %d)", this, image,
frame->GetIntrinsicSize().width, frame->GetIntrinsicSize().height));
if (frame->GetForceBlack()) {
if (!mBlackImage) {
RefPtr<Image> blackImage = GetImageContainer()->CreatePlanarYCbCrImage();
if (blackImage) {
// Sets the image to a single black pixel, which will be scaled to
// fill the rendered size.
if (SetImageToBlackPixel(blackImage->AsPlanarYCbCrImage())) {
mBlackImage = blackImage;
}
}
}
if (mBlackImage) {
image = mBlackImage;
}
}
// Don't append null image to the newImages.
if (!image) {
iter.Next();
continue;
}
newImages.AppendElement(
ImageContainer::NonOwningImage(image, chunk.mTimeStamp));
lastPrincipalHandle = chunk.GetPrincipalHandle();
mLastPlayedVideoFrame = *frame;
iter.Next();
}
// Don't update if there are no changes.
if (newImages.IsEmpty()) {
return;
}
AutoTArray<ImageContainer::NonOwningImage, 4> images;
bool principalHandleChanged =
lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
lastPrincipalHandle != GetLastPrincipalHandleLocked();
// Add the frames from this iteration.
for (auto& image : newImages) {
image.mFrameID = NewFrameID();
images.AppendElement(image);
}
if (principalHandleChanged) {
UpdatePrincipalHandleForFrameIDLocked(lastPrincipalHandle,
newImages.LastElement().mFrameID);
}
SetCurrentFramesLocked(mLastPlayedVideoFrame.GetIntrinsicSize(), images);
nsCOMPtr<nsIRunnable> event = new VideoFrameContainerInvalidateRunnable(this);
mMainThread->Dispatch(event.forget());
images.ClearAndRetainStorage();
}
void VideoFrameContainer::ClearFrames() { ClearFutureFrames(); }
void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
Image* aImage,
const TimeStamp& aTargetTime) {
if (aImage) {
MutexAutoLock lock(mMutex);
AutoTimer<Telemetry::VFC_SETCURRENTFRAME_LOCK_HOLD_MS> lockHold;
AutoTArray<ImageContainer::NonOwningImage, 1> imageList;
imageList.AppendElement(
ImageContainer::NonOwningImage(aImage, aTargetTime, ++mFrameID));
SetCurrentFramesLocked(aIntrinsicSize, imageList);
} else {
ClearCurrentFrame(aIntrinsicSize);
}
}
void VideoFrameContainer::SetCurrentFrames(
const gfx::IntSize& aIntrinsicSize,
const nsTArray<ImageContainer::NonOwningImage>& aImages) {
MutexAutoLock lock(mMutex);
AutoTimer<Telemetry::VFC_SETIMAGES_LOCK_HOLD_MS> lockHold;
SetCurrentFramesLocked(aIntrinsicSize, aImages);
}
void VideoFrameContainer::SetCurrentFramesLocked(
const gfx::IntSize& aIntrinsicSize,
const nsTArray<ImageContainer::NonOwningImage>& aImages) {
mMutex.AssertCurrentThreadOwns();
if (aIntrinsicSize != mIntrinsicSize) {
mIntrinsicSize = aIntrinsicSize;
RefPtr<VideoFrameContainer> self = this;
mMainThread->Dispatch(NS_NewRunnableFunction(
"IntrinsicSizeChanged", [this, self, aIntrinsicSize]() {
mMainThreadState.mIntrinsicSize = aIntrinsicSize;
mMainThreadState.mIntrinsicSizeChanged = true;
}));
}
gfx::IntSize oldFrameSize = mImageContainer->GetCurrentSize();
// When using the OMX decoder, destruction of the current image can indirectly
// block on main thread I/O. If we let this happen while holding onto
// |mImageContainer|'s lock, then when the main thread then tries to
// composite it can then block on |mImageContainer|'s lock, causing a
// deadlock. We use this hack to defer the destruction of the current image
// until it is safe.
nsTArray<ImageContainer::OwningImage> oldImages;
mImageContainer->GetCurrentImages(&oldImages);
PrincipalHandle principalHandle = PRINCIPAL_HANDLE_NONE;
ImageContainer::FrameID lastFrameIDForOldPrincipalHandle =
mFrameIDForPendingPrincipalHandle - 1;
if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
((!oldImages.IsEmpty() &&
oldImages.LastElement().mFrameID >= lastFrameIDForOldPrincipalHandle) ||
(!aImages.IsEmpty() &&
aImages[0].mFrameID > lastFrameIDForOldPrincipalHandle))) {
// We are releasing the last FrameID prior to
// `lastFrameIDForOldPrincipalHandle` OR there are no FrameIDs prior to
// `lastFrameIDForOldPrincipalHandle` in the new set of images. This means
// that the old principal handle has been flushed out and we can notify our
// video element about this change.
principalHandle = mPendingPrincipalHandle;
mLastPrincipalHandle = mPendingPrincipalHandle;
mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
mFrameIDForPendingPrincipalHandle = 0;
}
if (aImages.IsEmpty()) {
mImageContainer->ClearAllImages();
} else {
mImageContainer->SetCurrentImages(aImages);
}
gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
bool imageSizeChanged = (oldFrameSize != newFrameSize);
if (principalHandle != PRINCIPAL_HANDLE_NONE || imageSizeChanged) {
RefPtr<VideoFrameContainer> self = this;
mMainThread->Dispatch(NS_NewRunnableFunction(
"PrincipalHandleOrImageSizeChanged",
[this, self, principalHandle, imageSizeChanged]() {
mMainThreadState.mImageSizeChanged = imageSizeChanged;
if (mOwner && principalHandle != PRINCIPAL_HANDLE_NONE) {
mOwner->PrincipalHandleChangedForVideoFrameContainer(
this, principalHandle);
}
}));
}
}
void VideoFrameContainer::ClearCurrentFrame() {
MutexAutoLock lock(mMutex);
AutoTimer<Telemetry::VFC_CLEARCURRENTFRAME_LOCK_HOLD_MS> lockHold;
// See comment in SetCurrentFrame for the reasoning behind
// using a kungFuDeathGrip here.
nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
mImageContainer->GetCurrentImages(&kungFuDeathGrip);
mImageContainer->ClearAllImages();
mImageContainer->ClearCachedResources();
}
void VideoFrameContainer::ClearFutureFrames() {
MutexAutoLock lock(mMutex);
AutoTimer<Telemetry::VFC_CLEARFUTUREFRAMES_LOCK_HOLD_MS> lockHold;
// See comment in SetCurrentFrame for the reasoning behind
// using a kungFuDeathGrip here.
nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
mImageContainer->GetCurrentImages(&kungFuDeathGrip);
if (!kungFuDeathGrip.IsEmpty()) {
nsTArray<ImageContainer::NonOwningImage> currentFrame;
const ImageContainer::OwningImage& img = kungFuDeathGrip[0];
currentFrame.AppendElement(ImageContainer::NonOwningImage(
img.mImage, img.mTimeStamp, img.mFrameID, img.mProducerID));
mImageContainer->SetCurrentImages(currentFrame);
}
}
void VideoFrameContainer::ClearCachedResources() {
mImageContainer->ClearCachedResources();
}
ImageContainer* VideoFrameContainer::GetImageContainer() {
return mImageContainer;
}
double VideoFrameContainer::GetFrameDelay() {
return mImageContainer->GetPaintDelay().ToSeconds();
}
void VideoFrameContainer::InvalidateWithFlags(uint32_t aFlags) {
NS_ASSERTION(NS_IsMainThread(), "Must call on main thread");
if (!mOwner) {
// Owner has been destroyed
return;
}
bool imageSizeChanged = mMainThreadState.mImageSizeChanged;
mMainThreadState.mImageSizeChanged = false;
Maybe<nsIntSize> intrinsicSize;
if (mMainThreadState.mIntrinsicSizeChanged) {
intrinsicSize = Some(mMainThreadState.mIntrinsicSize);
mMainThreadState.mIntrinsicSizeChanged = false;
}
bool forceInvalidate = aFlags & INVALIDATE_FORCE;
mOwner->Invalidate(imageSizeChanged, intrinsicSize, forceInvalidate);
}
} // namespace mozilla
#undef NS_DispatchToMainThread