Line data Source code
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/. */
6 :
7 : #include "VideoFrameContainer.h"
8 :
9 : #include "mozilla/dom/HTMLMediaElement.h"
10 : #include "mozilla/Telemetry.h"
11 :
12 : #include "nsIFrame.h"
13 : #include "nsDisplayList.h"
14 : #include "nsSVGEffects.h"
15 :
16 : using namespace mozilla::layers;
17 :
18 : namespace mozilla {
19 : static LazyLogModule gVideoFrameContainerLog("VideoFrameContainer");
20 : #define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
21 :
22 : #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
23 :
24 : namespace {
25 : template<Telemetry::HistogramID ID>
26 0 : class AutoTimer
27 : {
28 : // Set a threshold to reduce performance overhead
29 : // for we're measuring hot spots.
30 : static const uint32_t sThresholdMS = 1000;
31 : public:
32 0 : ~AutoTimer()
33 : {
34 0 : auto end = TimeStamp::Now();
35 0 : auto diff = uint32_t((end - mStart).ToMilliseconds());
36 0 : if (diff > sThresholdMS) {
37 0 : Telemetry::Accumulate(ID, diff);
38 : }
39 0 : }
40 : private:
41 : const TimeStamp mStart = TimeStamp::Now();
42 : };
43 : }
44 :
45 0 : VideoFrameContainer::VideoFrameContainer(
46 : dom::HTMLMediaElement* aElement,
47 0 : already_AddRefed<ImageContainer> aContainer)
48 : : mElement(aElement)
49 : , mImageContainer(aContainer)
50 : , mMutex("nsVideoFrameContainer")
51 : , mBlackImage(nullptr)
52 : , mFrameID(0)
53 : , mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE)
54 : , mFrameIDForPendingPrincipalHandle(0)
55 0 : , mMainThread(aElement->AbstractMainThread())
56 : {
57 0 : NS_ASSERTION(aElement, "aElement must not be null");
58 0 : NS_ASSERTION(mImageContainer, "aContainer must not be null");
59 0 : }
60 :
61 0 : VideoFrameContainer::~VideoFrameContainer()
62 0 : {}
63 :
64 0 : PrincipalHandle VideoFrameContainer::GetLastPrincipalHandle()
65 : {
66 0 : MutexAutoLock lock(mMutex);
67 0 : return GetLastPrincipalHandleLocked();
68 : }
69 :
70 0 : PrincipalHandle VideoFrameContainer::GetLastPrincipalHandleLocked()
71 : {
72 0 : return mLastPrincipalHandle;
73 : }
74 :
75 0 : void VideoFrameContainer::UpdatePrincipalHandleForFrameID(const PrincipalHandle& aPrincipalHandle,
76 : const ImageContainer::FrameID& aFrameID)
77 : {
78 0 : MutexAutoLock lock(mMutex);
79 0 : UpdatePrincipalHandleForFrameIDLocked(aPrincipalHandle, aFrameID);
80 0 : }
81 :
82 0 : void VideoFrameContainer::UpdatePrincipalHandleForFrameIDLocked(const PrincipalHandle& aPrincipalHandle,
83 : const ImageContainer::FrameID& aFrameID)
84 : {
85 0 : if (mPendingPrincipalHandle == aPrincipalHandle) {
86 0 : return;
87 : }
88 0 : mPendingPrincipalHandle = aPrincipalHandle;
89 0 : mFrameIDForPendingPrincipalHandle = aFrameID;
90 : }
91 :
92 : static void
93 0 : SetImageToBlackPixel(PlanarYCbCrImage* aImage)
94 : {
95 0 : uint8_t blackPixel[] = { 0x10, 0x80, 0x80 };
96 :
97 0 : PlanarYCbCrData data;
98 0 : data.mYChannel = blackPixel;
99 0 : data.mCbChannel = blackPixel + 1;
100 0 : data.mCrChannel = blackPixel + 2;
101 0 : data.mYStride = data.mCbCrStride = 1;
102 0 : data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
103 0 : aImage->CopyData(data);
104 0 : }
105 :
106 0 : class VideoFrameContainerInvalidateRunnable : public Runnable {
107 : public:
108 0 : explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
109 0 : : Runnable("VideoFrameContainerInvalidateRunnable")
110 0 : , mVideoFrameContainer(aVideoFrameContainer)
111 0 : {}
112 0 : NS_IMETHOD Run()
113 : {
114 0 : MOZ_ASSERT(NS_IsMainThread());
115 :
116 0 : mVideoFrameContainer->Invalidate();
117 :
118 0 : return NS_OK;
119 : }
120 : private:
121 : RefPtr<VideoFrameContainer> mVideoFrameContainer;
122 : };
123 :
124 0 : void VideoFrameContainer::SetCurrentFrames(const VideoSegment& aSegment)
125 : {
126 0 : if (aSegment.IsEmpty()) {
127 0 : return;
128 : }
129 :
130 0 : MutexAutoLock lock(mMutex);
131 0 : AutoTimer<Telemetry::VFC_SETVIDEOSEGMENT_LOCK_HOLD_MS> lockHold;
132 :
133 : // Collect any new frames produced in this iteration.
134 0 : AutoTArray<ImageContainer::NonOwningImage,4> newImages;
135 0 : PrincipalHandle lastPrincipalHandle = PRINCIPAL_HANDLE_NONE;
136 :
137 0 : VideoSegment::ConstChunkIterator iter(aSegment);
138 0 : while (!iter.IsEnded()) {
139 0 : VideoChunk chunk = *iter;
140 :
141 0 : const VideoFrame* frame = &chunk.mFrame;
142 0 : if (*frame == mLastPlayedVideoFrame) {
143 0 : iter.Next();
144 0 : continue;
145 : }
146 :
147 0 : Image* image = frame->GetImage();
148 0 : CONTAINER_LOG(LogLevel::Verbose,
149 : ("VideoFrameContainer %p writing video frame %p (%d x %d)",
150 : this, image, frame->GetIntrinsicSize().width,
151 : frame->GetIntrinsicSize().height));
152 :
153 0 : if (frame->GetForceBlack()) {
154 0 : if (!mBlackImage) {
155 0 : mBlackImage = GetImageContainer()->CreatePlanarYCbCrImage();
156 0 : if (mBlackImage) {
157 : // Sets the image to a single black pixel, which will be scaled to
158 : // fill the rendered size.
159 0 : SetImageToBlackPixel(mBlackImage->AsPlanarYCbCrImage());
160 : }
161 : }
162 0 : if (mBlackImage) {
163 0 : image = mBlackImage;
164 : }
165 : }
166 : // Don't append null image to the newImages.
167 0 : if (!image) {
168 0 : iter.Next();
169 0 : continue;
170 : }
171 0 : newImages.AppendElement(ImageContainer::NonOwningImage(image, chunk.mTimeStamp));
172 :
173 0 : lastPrincipalHandle = chunk.GetPrincipalHandle();
174 :
175 0 : mLastPlayedVideoFrame = *frame;
176 0 : iter.Next();
177 : }
178 :
179 : // Don't update if there are no changes.
180 0 : if (newImages.IsEmpty()) {
181 0 : return;
182 : }
183 :
184 0 : AutoTArray<ImageContainer::NonOwningImage,4> images;
185 :
186 : bool principalHandleChanged =
187 0 : lastPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
188 0 : lastPrincipalHandle != GetLastPrincipalHandleLocked();
189 :
190 : // Add the frames from this iteration.
191 0 : for (auto& image : newImages) {
192 0 : image.mFrameID = NewFrameID();
193 0 : images.AppendElement(image);
194 : }
195 :
196 0 : if (principalHandleChanged) {
197 0 : UpdatePrincipalHandleForFrameIDLocked(lastPrincipalHandle,
198 0 : newImages.LastElement().mFrameID);
199 : }
200 :
201 0 : SetCurrentFramesLocked(mLastPlayedVideoFrame.GetIntrinsicSize(), images);
202 : nsCOMPtr<nsIRunnable> event =
203 0 : new VideoFrameContainerInvalidateRunnable(this);
204 0 : mMainThread->Dispatch(event.forget());
205 :
206 0 : images.ClearAndRetainStorage();
207 : }
208 :
209 0 : void VideoFrameContainer::ClearFrames()
210 : {
211 0 : ClearFutureFrames();
212 0 : }
213 :
214 0 : void VideoFrameContainer::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize,
215 : Image* aImage,
216 : const TimeStamp& aTargetTime)
217 : {
218 0 : if (aImage) {
219 0 : MutexAutoLock lock(mMutex);
220 0 : AutoTimer<Telemetry::VFC_SETCURRENTFRAME_LOCK_HOLD_MS> lockHold;
221 0 : AutoTArray<ImageContainer::NonOwningImage,1> imageList;
222 : imageList.AppendElement(
223 0 : ImageContainer::NonOwningImage(aImage, aTargetTime, ++mFrameID));
224 0 : SetCurrentFramesLocked(aIntrinsicSize, imageList);
225 : } else {
226 0 : ClearCurrentFrame(aIntrinsicSize);
227 : }
228 0 : }
229 :
230 0 : void VideoFrameContainer::SetCurrentFrames(const gfx::IntSize& aIntrinsicSize,
231 : const nsTArray<ImageContainer::NonOwningImage>& aImages)
232 : {
233 0 : MutexAutoLock lock(mMutex);
234 0 : AutoTimer<Telemetry::VFC_SETIMAGES_LOCK_HOLD_MS> lockHold;
235 0 : SetCurrentFramesLocked(aIntrinsicSize, aImages);
236 0 : }
237 :
238 0 : void VideoFrameContainer::SetCurrentFramesLocked(const gfx::IntSize& aIntrinsicSize,
239 : const nsTArray<ImageContainer::NonOwningImage>& aImages)
240 : {
241 0 : mMutex.AssertCurrentThreadOwns();
242 :
243 0 : if (aIntrinsicSize != mIntrinsicSize) {
244 0 : mIntrinsicSize = aIntrinsicSize;
245 0 : RefPtr<VideoFrameContainer> self = this;
246 0 : mMainThread->Dispatch(NS_NewRunnableFunction(
247 0 : "IntrinsicSizeChanged", [this, self, aIntrinsicSize]() {
248 0 : mMainThreadState.mIntrinsicSize = aIntrinsicSize;
249 0 : mMainThreadState.mIntrinsicSizeChanged = true;
250 0 : }));
251 : }
252 :
253 0 : gfx::IntSize oldFrameSize = mImageContainer->GetCurrentSize();
254 :
255 : // When using the OMX decoder, destruction of the current image can indirectly
256 : // block on main thread I/O. If we let this happen while holding onto
257 : // |mImageContainer|'s lock, then when the main thread then tries to
258 : // composite it can then block on |mImageContainer|'s lock, causing a
259 : // deadlock. We use this hack to defer the destruction of the current image
260 : // until it is safe.
261 0 : nsTArray<ImageContainer::OwningImage> oldImages;
262 0 : mImageContainer->GetCurrentImages(&oldImages);
263 :
264 0 : PrincipalHandle principalHandle = PRINCIPAL_HANDLE_NONE;
265 : ImageContainer::FrameID lastFrameIDForOldPrincipalHandle =
266 0 : mFrameIDForPendingPrincipalHandle - 1;
267 0 : if (mPendingPrincipalHandle != PRINCIPAL_HANDLE_NONE &&
268 0 : ((!oldImages.IsEmpty() &&
269 0 : oldImages.LastElement().mFrameID >= lastFrameIDForOldPrincipalHandle) ||
270 0 : (!aImages.IsEmpty() &&
271 0 : aImages[0].mFrameID > lastFrameIDForOldPrincipalHandle))) {
272 : // We are releasing the last FrameID prior to `lastFrameIDForOldPrincipalHandle`
273 : // OR
274 : // there are no FrameIDs prior to `lastFrameIDForOldPrincipalHandle` in the new
275 : // set of images.
276 : // This means that the old principal handle has been flushed out and we can
277 : // notify our video element about this change.
278 0 : principalHandle = mPendingPrincipalHandle;
279 0 : mLastPrincipalHandle = mPendingPrincipalHandle;
280 0 : mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
281 0 : mFrameIDForPendingPrincipalHandle = 0;
282 : }
283 :
284 0 : if (aImages.IsEmpty()) {
285 0 : mImageContainer->ClearAllImages();
286 : } else {
287 0 : mImageContainer->SetCurrentImages(aImages);
288 : }
289 0 : gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
290 0 : bool imageSizeChanged = (oldFrameSize != newFrameSize);
291 :
292 0 : if (principalHandle != PRINCIPAL_HANDLE_NONE || imageSizeChanged) {
293 0 : RefPtr<VideoFrameContainer> self = this;
294 0 : mMainThread->Dispatch(NS_NewRunnableFunction(
295 : "PrincipalHandleOrImageSizeChanged",
296 0 : [this, self, principalHandle, imageSizeChanged]() {
297 0 : mMainThreadState.mImageSizeChanged = imageSizeChanged;
298 0 : if (mElement && principalHandle != PRINCIPAL_HANDLE_NONE) {
299 0 : mElement->PrincipalHandleChangedForVideoFrameContainer(
300 0 : this, principalHandle);
301 : }
302 0 : }));
303 : }
304 0 : }
305 :
306 0 : void VideoFrameContainer::ClearCurrentFrame()
307 : {
308 0 : MutexAutoLock lock(mMutex);
309 0 : AutoTimer<Telemetry::VFC_CLEARCURRENTFRAME_LOCK_HOLD_MS> lockHold;
310 :
311 : // See comment in SetCurrentFrame for the reasoning behind
312 : // using a kungFuDeathGrip here.
313 0 : nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
314 0 : mImageContainer->GetCurrentImages(&kungFuDeathGrip);
315 :
316 0 : mImageContainer->ClearAllImages();
317 0 : mImageContainer->ClearCachedResources();
318 0 : }
319 :
320 0 : void VideoFrameContainer::ClearFutureFrames()
321 : {
322 0 : MutexAutoLock lock(mMutex);
323 0 : AutoTimer<Telemetry::VFC_CLEARFUTUREFRAMES_LOCK_HOLD_MS> lockHold;
324 :
325 : // See comment in SetCurrentFrame for the reasoning behind
326 : // using a kungFuDeathGrip here.
327 0 : nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
328 0 : mImageContainer->GetCurrentImages(&kungFuDeathGrip);
329 :
330 0 : if (!kungFuDeathGrip.IsEmpty()) {
331 0 : nsTArray<ImageContainer::NonOwningImage> currentFrame;
332 0 : const ImageContainer::OwningImage& img = kungFuDeathGrip[0];
333 0 : currentFrame.AppendElement(ImageContainer::NonOwningImage(img.mImage,
334 0 : img.mTimeStamp, img.mFrameID, img.mProducerID));
335 0 : mImageContainer->SetCurrentImages(currentFrame);
336 : }
337 0 : }
338 :
339 : void
340 0 : VideoFrameContainer::ClearCachedResources()
341 : {
342 0 : mImageContainer->ClearCachedResources();
343 0 : }
344 :
345 0 : ImageContainer* VideoFrameContainer::GetImageContainer() {
346 0 : return mImageContainer;
347 : }
348 :
349 :
350 0 : double VideoFrameContainer::GetFrameDelay()
351 : {
352 0 : return mImageContainer->GetPaintDelay().ToSeconds();
353 : }
354 :
355 0 : void VideoFrameContainer::InvalidateWithFlags(uint32_t aFlags)
356 : {
357 0 : NS_ASSERTION(NS_IsMainThread(), "Must call on main thread");
358 :
359 0 : if (!mElement) {
360 : // Element has been destroyed
361 0 : return;
362 : }
363 :
364 0 : nsIFrame* frame = mElement->GetPrimaryFrame();
365 0 : bool invalidateFrame = mMainThreadState.mImageSizeChanged;
366 0 : mMainThreadState.mImageSizeChanged = false;
367 :
368 0 : if (mMainThreadState.mIntrinsicSizeChanged) {
369 0 : mElement->UpdateMediaSize(mMainThreadState.mIntrinsicSize);
370 0 : mMainThreadState.mIntrinsicSizeChanged = false;
371 0 : if (frame) {
372 0 : nsPresContext* presContext = frame->PresContext();
373 0 : nsIPresShell* presShell = presContext->PresShell();
374 : presShell->FrameNeedsReflow(
375 0 : frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
376 : }
377 : }
378 :
379 0 : bool asyncInvalidate = mImageContainer &&
380 0 : mImageContainer->IsAsync() &&
381 0 : !(aFlags & INVALIDATE_FORCE);
382 :
383 0 : if (frame) {
384 0 : if (invalidateFrame) {
385 0 : frame->InvalidateFrame();
386 : } else {
387 0 : frame->InvalidateLayer(nsDisplayItem::TYPE_VIDEO, nullptr, nullptr,
388 0 : asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
389 : }
390 : }
391 :
392 0 : nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
393 : }
394 :
395 : } // namespace mozilla
396 :
397 : #undef NS_DispatchToMainThread
|