1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
media / gpu / android / frame_info_helper.cc [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/gpu/android/frame_info_helper.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "gpu/command_buffer/service/shared_image/android_video_image_backing.h"
#include "gpu/ipc/service/command_buffer_stub.h"
#include "gpu/ipc/service/gpu_channel.h"
#include "gpu/ipc/service/gpu_channel_manager.h"
#include "media/base/media_switches.h"
#include "media/gpu/android/codec_output_buffer_renderer.h"
namespace media {
FrameInfoHelper::FrameInfo::FrameInfo() = default;
FrameInfoHelper::FrameInfo::~FrameInfo() = default;
FrameInfoHelper::FrameInfo::FrameInfo(FrameInfo&&) = default;
FrameInfoHelper::FrameInfo::FrameInfo(const FrameInfoHelper::FrameInfo&) =
default;
FrameInfoHelper::FrameInfo& FrameInfoHelper::FrameInfo::operator=(
const FrameInfoHelper::FrameInfo&) = default;
// Concrete implementation of FrameInfoHelper that renders output buffers and
// gets the FrameInfo they need.
class FrameInfoHelperImpl : public FrameInfoHelper,
public gpu::RefCountedLockHelperDrDc {
public:
FrameInfoHelperImpl(scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(drdc_lock) {
on_gpu_ = base::SequenceBound<OnGpu>(std::move(gpu_task_runner),
std::move(get_stub_cb),
std::move(drdc_lock));
}
~FrameInfoHelperImpl() override = default;
void GetFrameInfo(std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
FrameInfoReadyCB callback) override {
Request request = {.buffer_renderer = std::move(buffer_renderer),
.callback = std::move(callback)};
requests_.push(std::move(request));
// If there were no pending requests start processing queue now.
if (requests_.size() == 1)
ProcessRequestsQueue();
}
bool IsStalled() const override { return waiting_for_real_frame_info_; }
private:
struct Request {
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer;
FrameInfoReadyCB callback;
};
class OnGpu : public gpu::RefCountedLockHelperDrDc {
public:
OnGpu(SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)),
frame_info_helper_holder_(
base::MakeRefCounted<FrameInfoHelperHolder>(this)) {
auto* stub = get_stub_cb.Run();
if (stub) {
gpu::ContextResult result;
shared_context_ =
stub->channel()->gpu_channel_manager()->GetSharedContextState(
&result);
if (result == gpu::ContextResult::kSuccess) {
DCHECK(shared_context_);
if (shared_context_->GrContextIsVulkan()) {
vulkan_context_provider_ = shared_context_->vk_context_provider();
} else if (shared_context_->IsGraphiteDawnVulkan()) {
dawn_context_provider_ = shared_context_->dawn_context_provider();
}
}
}
}
~OnGpu() {
DCHECK(frame_info_helper_holder_);
frame_info_helper_holder_->SetFrameInfoHelperOnGpuToNull();
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
AssertAcquiredDrDcLock();
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
std::optional<FrameInfo> info;
if (buffer_renderer->RenderToTextureOwnerFrontBuffer()) {
gfx::Size coded_size;
gfx::Rect visible_rect;
if (texture_owner->GetCodedSizeAndVisibleRect(
buffer_renderer->size(), &coded_size, &visible_rect)) {
info.emplace();
info->coded_size = coded_size;
info->visible_rect = visible_rect;
info->ycbcr_info = gpu::AndroidVideoImageBacking::GetYcbcrInfo(
texture_owner.get(), vulkan_context_provider_,
dawn_context_provider_);
}
}
std::move(cb).Run(std::move(buffer_renderer), info);
}
void GetFrameInfo(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
// Note that we need to ensure that no other thread renders another buffer
// in between while we are getting frame info here. Otherwise we will get
// wrong frame info. This is ensured by holding |drdc_lock| from all the
// places from where GetFrameInfoImpl() call can be triggered. It can be
// called from here via |texture_owner->RunWhenBufferIsAvailable()| below
// or from |ImageReaderGLOwner::ReleaseRefOnImageLocked()| when the
// buffer_vailable_cb is cached and triggered. So we lock here as well as
// ensure lock is held during
// |ImageReaderGLOwner::ReleaseRefOnImageLocked|.
base::AutoLockMaybe auto_lock(GetDrDcLockPtr());
DCHECK(buffer_renderer);
auto texture_owner = buffer_renderer->texture_owner();
DCHECK(texture_owner);
auto buffer_available_cb =
base::BindOnce(&FrameInfoHelperHolder::GetFrameInfoImpl,
base::RetainedRef(frame_info_helper_holder_),
std::move(buffer_renderer), std::move(cb));
texture_owner->RunWhenBufferIsAvailable(std::move(buffer_available_cb));
}
private:
// OnGpu::GetFrameInfoImpl can be called from any gpu thread (gpu main or
// DrDc), hence we can not use WeakPtr to it in |buffer_available_cb|.
// FrameInfoHelperHolder is used instead to mimic this weakPtr behavior of
// OnGpu. FrameInfoHelperHolder is RefCountedThreadSafe, and has a pointer
// to the OnGpu. OnGpu owns the FrameInfoHelperHolder and sets this pointer
// to null in its destructor so that it can't be used once OnGpu is
// destroyed. Note that since OnGpu::GetFrameInfoImpl needed to be called
// from any gpu thread, we could not use WeakPtr to it.
class FrameInfoHelperHolder
: public base::RefCountedThreadSafe<FrameInfoHelperHolder> {
public:
explicit FrameInfoHelperHolder(raw_ptr<OnGpu> frame_info_helper_on_gpu)
: frame_info_helper_on_gpu_(frame_info_helper_on_gpu) {
DCHECK(frame_info_helper_on_gpu_);
}
void GetFrameInfoImpl(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
base::OnceCallback<void(std::unique_ptr<CodecOutputBufferRenderer>,
std::optional<FrameInfo>)> cb) {
base::AutoLock l(lock_);
if (frame_info_helper_on_gpu_) {
frame_info_helper_on_gpu_->GetFrameInfoImpl(
std::move(buffer_renderer), std::move(cb));
}
}
void SetFrameInfoHelperOnGpuToNull() {
base::AutoLock l(lock_);
frame_info_helper_on_gpu_ = nullptr;
}
private:
friend class base::RefCountedThreadSafe<FrameInfoHelperHolder>;
~FrameInfoHelperHolder() = default;
// |lock_| for thread safe access to |frame_info_helper_on_gpu_|.
base::Lock lock_;
raw_ptr<OnGpu> frame_info_helper_on_gpu_ GUARDED_BY(lock_) = nullptr;
};
// Note that |shared_context_| is to just keep ref on it until
// context provider raw_ptrs are being used.
scoped_refptr<gpu::SharedContextState> shared_context_;
raw_ptr<viz::VulkanContextProvider> vulkan_context_provider_ = nullptr;
raw_ptr<gpu::DawnContextProvider> dawn_context_provider_ = nullptr;
scoped_refptr<FrameInfoHelperHolder> frame_info_helper_holder_;
};
bool CanGuessCodedSize(
const CodecOutputBufferRenderer& buffer_renderer) const {
// We never guess on the first frame since we can always render instead.
if (!frame_info_) {
return false;
}
// Coded size guessing will be disabled if the feature isn't enabled or we
// didn't correctly guess the initial coded size or failed to guess a coded
// size during a size change.
if (disable_coded_size_guessing_) {
return false;
}
// We can't guess non-origin visible rects.
if (!frame_info_->visible_rect.origin().IsOrigin()) {
return false;
}
return buffer_renderer.CanGuessCodedSize();
}
FrameInfo GuessFrameInfo(
const CodecOutputBufferRenderer& buffer_renderer) const {
FrameInfo info;
info.coded_size = CanGuessCodedSize(buffer_renderer)
? buffer_renderer.GuessCodedSize()
: buffer_renderer.size();
info.visible_rect = gfx::Rect(buffer_renderer.size());
return info;
}
void DisableCodedSizeGuessing(const gfx::Size& guessed_coded_size,
const gfx::Size& actual_coded_size) {
disable_coded_size_guessing_ = true;
LOG(ERROR) << "Guessed coded size incorrectly. Expected "
<< guessed_coded_size.ToString() << ", got "
<< actual_coded_size.ToString();
}
void OnRealFrameInfoAvailable(gfx::Size visible_size,
gfx::Size guessed_coded_size,
std::optional<gfx::Size> coded_size,
std::optional<gfx::Rect> visible_rect) {
DVLOG(1) << __func__
<< ": coded_size=" << (coded_size ? coded_size->ToString() : "")
<< ", visible_rect="
<< (visible_rect ? visible_rect->ToString() : "");
DCHECK(waiting_for_real_frame_info_);
waiting_for_real_frame_info_ = false;
if (coded_size && visible_rect) {
const bool guessed_coded_size_correctly =
guessed_coded_size == *coded_size;
base::UmaHistogramBoolean("Media.FrameInfo.GuessedCodedSizeChangeSuccess",
guessed_coded_size_correctly);
if (!guessed_coded_size_correctly) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
frame_info_->coded_size = *coded_size;
frame_info_->visible_rect = *visible_rect;
visible_size_ = visible_size;
// There's no way to get ycbr_info at this point. The TextureOwner can't
// provide it since it doesn't have a vulkan context and we can't get it
// here since there may not be a TextureOwner anymore. We're not even on
// the right thread anymore.
} else {
// This means the buffer was destroyed without being rendered to the front
// buffer, so we must emit another frame to try and get real size info.
}
ProcessRequestsQueue();
}
void OnFrameInfoReady(
std::unique_ptr<CodecOutputBufferRenderer> buffer_renderer,
std::optional<FrameInfo> frame_info) {
DCHECK(buffer_renderer);
DCHECK(!requests_.empty());
auto& request = requests_.front();
if (frame_info) {
const bool is_first_frame = !frame_info_;
visible_size_ = buffer_renderer->size();
frame_info_ = *frame_info;
// We always render the first frame, so we compare the real coded size
// against what we would have guessed. We then turn off guessing for
// future coded size changes if we guessed wrong.
if (is_first_frame && CanGuessCodedSize(*buffer_renderer)) {
const auto guessed_coded_size = buffer_renderer->GuessCodedSize();
const bool guessed_coded_size_correctly =
frame_info_->coded_size == guessed_coded_size;
base::UmaHistogramBoolean(
"Media.FrameInfo.GuessedInitialCodedSizeSuccess",
guessed_coded_size_correctly);
if (!guessed_coded_size_correctly) {
DisableCodedSizeGuessing(guessed_coded_size, frame_info_->coded_size);
}
}
std::move(request.callback).Run(std::move(buffer_renderer), *frame_info_);
} else {
// It's possible that we will fail to render frame and so weren't able to
// obtain FrameInfo. In this case we don't cache new values and complete
// current request with guessed values.
auto info = GuessFrameInfo(*buffer_renderer);
std::move(request.callback)
.Run(std::move(buffer_renderer), std::move(info));
}
requests_.pop();
ProcessRequestsQueue();
}
void ProcessRequestsQueue() {
while (!requests_.empty()) {
auto& request = requests_.front();
if (!request.buffer_renderer) {
// If we don't have buffer_renderer we can Run callback immediately.
std::move(request.callback).Run(nullptr, FrameInfo());
} else if (!request.buffer_renderer->texture_owner()) {
// If there is no texture_owner (SurfaceView case), we can't render
// frame and get proper size. But as Display Compositor won't render
// this frame the actual size is not important, so just guess.
auto info = GuessFrameInfo(*request.buffer_renderer);
std::move(request.callback)
.Run(std::move(request.buffer_renderer), std::move(info));
} else if (waiting_for_real_frame_info_) {
// Only allow emitting one frame at a time when we're waiting for frame
// information so that any glitches caused by a wrong guess are limited
// to a single visible frame.
break;
} else if (visible_size_ == request.buffer_renderer->size()) {
// We have cached the results of last frame info request with the same
// size. We assume that coded_size doesn't change if the visible_size
// stays the same.
std::move(request.callback)
.Run(std::move(request.buffer_renderer), *frame_info_);
} else if (CanGuessCodedSize(*request.buffer_renderer)) {
DCHECK(frame_info_); // We never guess on the first frame.
// To avoid glitches during size changes, guess a likely coded size.
auto info = GuessFrameInfo(*request.buffer_renderer);
info.ycbcr_info = frame_info_->ycbcr_info;
waiting_for_real_frame_info_ = true;
// Ensure we get the real coded size for the next frame.
request.buffer_renderer->set_frame_info_callback(
base::BindPostTaskToCurrentDefault(base::BindOnce(
&FrameInfoHelperImpl::OnRealFrameInfoAvailable,
weak_factory_.GetWeakPtr(), request.buffer_renderer->size(),
info.coded_size)));
std::move(request.callback)
.Run(std::move(request.buffer_renderer), info);
} else {
// We have texture_owner and we don't have cached value, so we need to
// hop to GPU thread and render the frame to get proper size.
auto cb = base::BindPostTaskToCurrentDefault(
base::BindOnce(&FrameInfoHelperImpl::OnFrameInfoReady,
weak_factory_.GetWeakPtr()));
on_gpu_.AsyncCall(&OnGpu::GetFrameInfo)
.WithArgs(std::move(request.buffer_renderer), std::move(cb));
// We didn't complete this request quite yet, so we can't process queue
// any further.
break;
}
requests_.pop();
}
}
base::SequenceBound<OnGpu> on_gpu_;
base::queue<Request> requests_;
// Cached values.
std::optional<FrameInfo> frame_info_;
gfx::Size visible_size_;
bool waiting_for_real_frame_info_ = false;
bool disable_coded_size_guessing_ =
!base::FeatureList::IsEnabled(kMediaCodecCodedSizeGuessing);
base::WeakPtrFactory<FrameInfoHelperImpl> weak_factory_{this};
};
// static
std::unique_ptr<FrameInfoHelper> FrameInfoHelper::Create(
scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
SharedImageVideoProvider::GetStubCB get_stub_cb,
scoped_refptr<gpu::RefCountedLock> drdc_lock) {
return std::make_unique<FrameInfoHelperImpl>(
std::move(gpu_task_runner), std::move(get_stub_cb), std::move(drdc_lock));
}
} // namespace media