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
media / filters / offloading_video_decoder.cc [blame]
// Copyright 2017 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/filters/offloading_video_decoder.h"
#include <memory>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "media/base/cdm_context.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_frame.h"
namespace media {
// Helper class which manages cancellation of Decode() after Reset() and makes
// it easier to destruct on the proper thread.
class CancellationHelper {
public:
CancellationHelper(std::unique_ptr<OffloadableVideoDecoder> decoder)
: cancellation_flag_(std::make_unique<base::AtomicFlag>()),
decoder_(std::move(decoder)) {}
CancellationHelper(const CancellationHelper&) = delete;
CancellationHelper& operator=(const CancellationHelper&) = delete;
// Safe to call from any thread.
void Cancel() { cancellation_flag_->Set(); }
void Decode(scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_cb) {
if (cancellation_flag_->IsSet()) {
std::move(decode_cb).Run(DecoderStatus::Codes::kAborted);
return;
}
decoder_->Decode(std::move(buffer), std::move(decode_cb));
}
void Reset(base::OnceClosure reset_cb) {
// OffloadableVideoDecoders are required to have a synchronous Reset(), so
// we don't need to wait for the Reset to complete. Despite this, we don't
// want to run |reset_cb| before we've reset the cancellation flag or the
// client may end up issuing another Reset() before this code runs.
decoder_->Reset(base::DoNothing());
cancellation_flag_ = std::make_unique<base::AtomicFlag>();
std::move(reset_cb).Run();
}
OffloadableVideoDecoder* decoder() const { return decoder_.get(); }
private:
std::unique_ptr<base::AtomicFlag> cancellation_flag_;
std::unique_ptr<OffloadableVideoDecoder> decoder_;
};
OffloadingVideoDecoder::OffloadingVideoDecoder(
int min_offloading_width,
std::vector<VideoCodec> supported_codecs,
std::unique_ptr<OffloadableVideoDecoder> decoder)
: min_offloading_width_(min_offloading_width),
supported_codecs_(std::move(supported_codecs)),
helper_(std::make_unique<CancellationHelper>(std::move(decoder))) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
OffloadingVideoDecoder::~OffloadingVideoDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The |helper_| must always be destroyed on the |offload_task_runner_| since
// we may still have tasks posted to it.
if (offload_task_runner_)
offload_task_runner_->DeleteSoon(FROM_HERE, std::move(helper_));
}
VideoDecoderType OffloadingVideoDecoder::GetDecoderType() const {
// This call is expected to be static and safe to call from any thread.
return helper_->decoder()->GetDecoderType();
}
void OffloadingVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(config.IsValidConfig());
const bool disable_offloading =
config.is_encrypted() ||
config.coded_size().width() < min_offloading_width_ ||
!base::Contains(supported_codecs_, config.codec());
if (initialized_) {
initialized_ = false;
// We're transitioning from offloading to no offloading, so detach from the
// offloading thread so we can run on the media thread.
if (disable_offloading && offload_task_runner_) {
offload_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&OffloadableVideoDecoder::Detach,
base::Unretained(helper_->decoder())),
// We must trampoline back through OffloadingVideoDecoder because it's
// possible for this class to be destroyed during Initialize().
base::BindOnce(&OffloadingVideoDecoder::Initialize,
weak_factory_.GetWeakPtr(), config, low_delay,
cdm_context, std::move(init_cb), output_cb,
waiting_cb));
return;
}
// We're transitioning from no offloading to offloading, so detach from the
// media thread so we can run on the offloading thread.
if (!disable_offloading && !offload_task_runner_)
helper_->decoder()->Detach();
}
DCHECK(!initialized_);
initialized_ = true;
// Offloaded decoders expect asynchronous execution of callbacks; even if we
// aren't currently using the offload thread.
InitCB bound_init_cb = base::BindPostTaskToCurrentDefault(std::move(init_cb));
OutputCB bound_output_cb = base::BindPostTaskToCurrentDefault(output_cb);
// If we're not offloading just pass through to the wrapped decoder.
if (disable_offloading) {
offload_task_runner_ = nullptr;
helper_->decoder()->Initialize(config, low_delay, cdm_context,
std::move(bound_init_cb), bound_output_cb,
waiting_cb);
return;
}
if (!offload_task_runner_) {
offload_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING});
}
offload_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OffloadableVideoDecoder::Initialize,
base::Unretained(helper_->decoder()), config, low_delay,
cdm_context, std::move(bound_init_cb), bound_output_cb,
waiting_cb));
}
void OffloadingVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
DecodeCB decode_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer);
DCHECK(decode_cb);
DecodeCB bound_decode_cb =
base::BindPostTaskToCurrentDefault(std::move(decode_cb));
if (!offload_task_runner_) {
helper_->decoder()->Decode(std::move(buffer), std::move(bound_decode_cb));
return;
}
offload_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CancellationHelper::Decode,
base::Unretained(helper_.get()),
std::move(buffer), std::move(bound_decode_cb)));
}
void OffloadingVideoDecoder::Reset(base::OnceClosure reset_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::OnceClosure bound_reset_cb =
base::BindPostTaskToCurrentDefault(std::move(reset_cb));
if (!offload_task_runner_) {
helper_->Reset(std::move(bound_reset_cb));
} else {
helper_->Cancel();
offload_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CancellationHelper::Reset,
base::Unretained(helper_.get()),
std::move(bound_reset_cb)));
}
}
int OffloadingVideoDecoder::GetMaxDecodeRequests() const {
// If we're offloading, try to parallelize decodes as well. Take care when
// adjusting this number as it may dramatically increase memory usage and
// reduce seek times. See http://crbug.com/731841.
//
// The current value of 2 was determined via experimental adjustment until a
// 4K60 VP9 playback dropped zero frames.
return offload_task_runner_ ? 2 : 1;
}
} // namespace media