Coverage Report

Created: 2018-09-25 14:53

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