1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "VideoFrameContainer.h"
9 #ifdef MOZ_WIDGET_ANDROID
10 # include "GLImages.h" // for SurfaceTextureImage
12 #include "MediaDecoderOwner.h"
13 #include "mozilla/Telemetry.h"
14 #include "mozilla/AbstractThread.h"
16 using namespace mozilla::layers
;
19 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
22 template <Telemetry::HistogramID ID
>
24 // Set a threshold to reduce performance overhead
25 // for we're measuring hot spots.
26 static const uint32_t sThresholdMS
= 1000;
30 auto end
= TimeStamp::Now();
31 auto diff
= uint32_t((end
- mStart
).ToMilliseconds());
32 if (diff
> sThresholdMS
) {
33 Telemetry::Accumulate(ID
, diff
);
38 const TimeStamp mStart
= TimeStamp::Now();
42 VideoFrameContainer::VideoFrameContainer(
43 MediaDecoderOwner
* aOwner
, already_AddRefed
<ImageContainer
> aContainer
)
45 mImageContainer(aContainer
),
46 mMutex("nsVideoFrameContainer"),
48 mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE
),
49 mFrameIDForPendingPrincipalHandle(0),
50 mMainThread(aOwner
->AbstractMainThread()) {
51 NS_ASSERTION(aOwner
, "aOwner must not be null");
52 NS_ASSERTION(mImageContainer
, "aContainer must not be null");
55 VideoFrameContainer::~VideoFrameContainer() = default;
57 PrincipalHandle
VideoFrameContainer::GetLastPrincipalHandle() {
58 MutexAutoLock
lock(mMutex
);
59 return GetLastPrincipalHandleLocked();
62 PrincipalHandle
VideoFrameContainer::GetLastPrincipalHandleLocked() {
63 return mLastPrincipalHandle
;
66 void VideoFrameContainer::UpdatePrincipalHandleForFrameID(
67 const PrincipalHandle
& aPrincipalHandle
,
68 const ImageContainer::FrameID
& aFrameID
) {
69 MutexAutoLock
lock(mMutex
);
70 UpdatePrincipalHandleForFrameIDLocked(aPrincipalHandle
, aFrameID
);
73 void VideoFrameContainer::UpdatePrincipalHandleForFrameIDLocked(
74 const PrincipalHandle
& aPrincipalHandle
,
75 const ImageContainer::FrameID
& aFrameID
) {
76 if (mPendingPrincipalHandle
== aPrincipalHandle
) {
79 mPendingPrincipalHandle
= aPrincipalHandle
;
80 mFrameIDForPendingPrincipalHandle
= aFrameID
;
83 #ifdef MOZ_WIDGET_ANDROID
84 static void NotifySetCurrent(Image
* aImage
) {
85 if (aImage
== nullptr) {
89 SurfaceTextureImage
* image
= aImage
->AsSurfaceTextureImage();
90 if (image
== nullptr) {
94 image
->OnSetCurrent();
98 void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize
& aIntrinsicSize
,
100 const TimeStamp
& aTargetTime
) {
101 #ifdef MOZ_WIDGET_ANDROID
102 NotifySetCurrent(aImage
);
105 MutexAutoLock
lock(mMutex
);
106 AutoTArray
<ImageContainer::NonOwningImage
, 1> imageList
;
107 imageList
.AppendElement(
108 ImageContainer::NonOwningImage(aImage
, aTargetTime
, ++mFrameID
));
109 SetCurrentFramesLocked(aIntrinsicSize
, imageList
);
111 ClearCurrentFrame(aIntrinsicSize
);
115 void VideoFrameContainer::SetCurrentFrames(
116 const gfx::IntSize
& aIntrinsicSize
,
117 const nsTArray
<ImageContainer::NonOwningImage
>& aImages
) {
118 #ifdef MOZ_WIDGET_ANDROID
119 // When there are multiple frames, only the last one is effective
120 // (see bug 1299068 comment 4). Here I just count on VideoSink and VideoOutput
121 // to send one frame at a time and warn if not.
122 Unused
<< NS_WARN_IF(aImages
.Length() > 1);
123 for (auto& image
: aImages
) {
124 NotifySetCurrent(image
.mImage
);
127 MutexAutoLock
lock(mMutex
);
128 SetCurrentFramesLocked(aIntrinsicSize
, aImages
);
131 void VideoFrameContainer::SetCurrentFramesLocked(
132 const gfx::IntSize
& aIntrinsicSize
,
133 const nsTArray
<ImageContainer::NonOwningImage
>& aImages
) {
134 mMutex
.AssertCurrentThreadOwns();
136 if (auto size
= Some(aIntrinsicSize
); size
!= mIntrinsicSize
) {
137 mIntrinsicSize
= size
;
138 mMainThread
->Dispatch(NS_NewRunnableFunction(
139 "IntrinsicSizeChanged", [this, self
= RefPtr(this), size
]() {
140 mMainThreadState
.mNewIntrinsicSize
= size
;
144 gfx::IntSize oldFrameSize
= mImageContainer
->GetCurrentSize();
146 // When using the OMX decoder, destruction of the current image can indirectly
147 // block on main thread I/O. If we let this happen while holding onto
148 // |mImageContainer|'s lock, then when the main thread then tries to
149 // composite it can then block on |mImageContainer|'s lock, causing a
150 // deadlock. We use this hack to defer the destruction of the current image
152 nsTArray
<ImageContainer::OwningImage
> oldImages
;
153 mImageContainer
->GetCurrentImages(&oldImages
);
155 PrincipalHandle principalHandle
= PRINCIPAL_HANDLE_NONE
;
156 ImageContainer::FrameID lastFrameIDForOldPrincipalHandle
=
157 mFrameIDForPendingPrincipalHandle
- 1;
158 if (mPendingPrincipalHandle
!= PRINCIPAL_HANDLE_NONE
&&
159 ((!oldImages
.IsEmpty() &&
160 oldImages
.LastElement().mFrameID
>= lastFrameIDForOldPrincipalHandle
) ||
161 (!aImages
.IsEmpty() &&
162 aImages
[0].mFrameID
> lastFrameIDForOldPrincipalHandle
))) {
163 // We are releasing the last FrameID prior to
164 // `lastFrameIDForOldPrincipalHandle` OR there are no FrameIDs prior to
165 // `lastFrameIDForOldPrincipalHandle` in the new set of images. This means
166 // that the old principal handle has been flushed out and we can notify our
167 // video element about this change.
168 principalHandle
= mPendingPrincipalHandle
;
169 mLastPrincipalHandle
= mPendingPrincipalHandle
;
170 mPendingPrincipalHandle
= PRINCIPAL_HANDLE_NONE
;
171 mFrameIDForPendingPrincipalHandle
= 0;
174 if (aImages
.IsEmpty()) {
175 mImageContainer
->ClearAllImages();
177 mImageContainer
->SetCurrentImages(aImages
);
179 gfx::IntSize newFrameSize
= mImageContainer
->GetCurrentSize();
180 bool imageSizeChanged
= (oldFrameSize
!= newFrameSize
);
182 if (principalHandle
!= PRINCIPAL_HANDLE_NONE
|| imageSizeChanged
) {
183 RefPtr
<VideoFrameContainer
> self
= this;
184 mMainThread
->Dispatch(NS_NewRunnableFunction(
185 "PrincipalHandleOrImageSizeChanged",
186 [this, self
, principalHandle
, imageSizeChanged
]() {
187 mMainThreadState
.mImageSizeChanged
= imageSizeChanged
;
188 if (mOwner
&& principalHandle
!= PRINCIPAL_HANDLE_NONE
) {
189 mOwner
->PrincipalHandleChangedForVideoFrameContainer(
190 this, principalHandle
);
196 void VideoFrameContainer::ClearFutureFrames(TimeStamp aNow
) {
197 MutexAutoLock
lock(mMutex
);
199 // See comment in SetCurrentFrame for the reasoning behind
200 // using a kungFuDeathGrip here.
201 AutoTArray
<ImageContainer::OwningImage
, 10> kungFuDeathGrip
;
202 mImageContainer
->GetCurrentImages(&kungFuDeathGrip
);
204 if (!kungFuDeathGrip
.IsEmpty()) {
205 AutoTArray
<ImageContainer::NonOwningImage
, 1> currentFrame
;
206 ImageContainer::OwningImage
& img
= kungFuDeathGrip
[0];
207 // Find the current image in case there are several.
208 for (const auto& image
: kungFuDeathGrip
) {
209 if (image
.mTimeStamp
> aNow
) {
214 currentFrame
.AppendElement(ImageContainer::NonOwningImage(
215 img
.mImage
, img
.mTimeStamp
, img
.mFrameID
, img
.mProducerID
));
216 mImageContainer
->SetCurrentImages(currentFrame
);
220 void VideoFrameContainer::ClearCachedResources() {
221 MutexAutoLock
lock(mMutex
);
222 mImageContainer
->ClearCachedResources();
225 ImageContainer
* VideoFrameContainer::GetImageContainer() {
226 // Note - you'll need the lock to manipulate this. The pointer is not
227 // modified from multiple threads, just the data pointed to by it.
228 return mImageContainer
;
231 double VideoFrameContainer::GetFrameDelay() {
232 MutexAutoLock
lock(mMutex
);
233 return mImageContainer
->GetPaintDelay().ToSeconds();
236 void VideoFrameContainer::InvalidateWithFlags(uint32_t aFlags
) {
237 NS_ASSERTION(NS_IsMainThread(), "Must call on main thread");
240 // Owner has been destroyed
244 MediaDecoderOwner::ImageSizeChanged imageSizeChanged
{
245 mMainThreadState
.mImageSizeChanged
};
246 mMainThreadState
.mImageSizeChanged
= false;
248 auto newIntrinsicSize
= std::move(mMainThreadState
.mNewIntrinsicSize
);
250 MediaDecoderOwner::ForceInvalidate forceInvalidate
{
251 (aFlags
& INVALIDATE_FORCE
) != 0};
252 mOwner
->Invalidate(imageSizeChanged
, newIntrinsicSize
, forceInvalidate
);
255 } // namespace mozilla
257 #undef NS_DispatchToMainThread