Dactyloidae/dom/media/VideoFrameContainer.cpp
Karl Tomlinson 9ba1057e54 Bug 1920800 - Remove oldImages from check for flushed old principal handle
The logic should be equivalent when frame IDs on aImages are contiguously
increasing from IDs on previously set frames.

There are currently no callers passing empty aImages, but the logic in this
case would now be as described in the documentation of
UpdatePrincipalHandleForFrameID():

> We will notify mElement that aPrincipalHandle has been applied when all
> FrameIDs prior to aFrameID have been flushed out.
2024-10-31 21:47:54 +08:00

354 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/dom/HTMLMediaElement.h"
#include "nsIFrame.h"
#include "nsDisplayList.h"
#include "nsSVGEffects.h"
using namespace mozilla::layers;
namespace mozilla {
static LazyLogModule gVideoFrameContainerLog("VideoFrameContainer");
#define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
already_AddRefed<ImageContainer> aContainer)
: mElement(aElement),
mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
mBlackImage(nullptr),
mFrameID(0),
mIntrinsicSizeChanged(false), mImageSizeChanged(false),
mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE), mFrameIDForPendingPrincipalHandle(0)
{
NS_ASSERTION(aElement, "aElement 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)
: mVideoFrameContainer(aVideoFrameContainer)
{}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
mVideoFrameContainer->Invalidate();
return NS_OK;
}
private:
RefPtr<VideoFrameContainer> mVideoFrameContainer;
};
void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment)
{
if (aSegment.IsEmpty()) {
return;
}
MutexAutoLock lock(mMutex);
// 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);
NS_DispatchToMainThread(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);
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);
SetCurrentFramesLocked(aIntrinsicSize, aImages);
}
void VideoFrameContainer::SetCurrentFramesLocked(const gfx::IntSize& aIntrinsicSize,
const nsTArray<ImageContainer::NonOwningImage>& aImages)
{
mMutex.AssertCurrentThreadOwns();
if (aIntrinsicSize != mIntrinsicSize) {
mIntrinsicSize = aIntrinsicSize;
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);
if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
(aImages.IsEmpty() ||
aImages[0].mFrameID >= mFrameIDForPendingPrincipalHandle)) {
// There are no FrameIDs prior to `mFrameIDForPendingPrincipalHandle`
// 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.
RefPtr<VideoFrameContainer> self = this;
PrincipalHandle principalHandle = mPendingPrincipalHandle;
mLastPrincipalHandle = mPendingPrincipalHandle;
mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
mFrameIDForPendingPrincipalHandle = 0;
NS_DispatchToMainThread(NS_NewRunnableFunction([self, principalHandle]() {
if (self->mElement) {
self->mElement->PrincipalHandleChangedForVideoFrameContainer(self, principalHandle);
}
}));
}
if (aImages.IsEmpty()) {
mImageContainer->ClearAllImages();
} else {
mImageContainer->SetCurrentImages(aImages);
}
gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
if (oldFrameSize != newFrameSize) {
mImageSizeChanged = true;
}
}
void VideoFrameContainer::ClearCurrentFrame()
{
MutexAutoLock lock(mMutex);
// 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);
// 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 (!mElement) {
// Element has been destroyed
return;
}
nsIFrame* frame = mElement->GetPrimaryFrame();
bool invalidateFrame = false;
{
MutexAutoLock lock(mMutex);
// Get mImageContainerSizeChanged while holding the lock.
invalidateFrame = mImageSizeChanged;
mImageSizeChanged = false;
if (mIntrinsicSizeChanged) {
mElement->UpdateMediaSize(mIntrinsicSize);
mIntrinsicSizeChanged = false;
if (frame) {
nsPresContext* presContext = frame->PresContext();
nsIPresShell *presShell = presContext->PresShell();
presShell->FrameNeedsReflow(frame,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
}
}
bool asyncInvalidate = mImageContainer &&
mImageContainer->IsAsync() &&
!(aFlags & INVALIDATE_FORCE);
if (frame) {
if (invalidateFrame) {
frame->InvalidateFrame();
} else {
frame->InvalidateLayer(nsDisplayItem::TYPE_VIDEO, nullptr, nullptr,
asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
}
}
nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
}
} // namespace mozilla