1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/filters/offloading_video_decoder.h"
6
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/sequenced_task_runner.h"
10 #include "base/synchronization/atomic_flag.h"
11 #include "base/task/post_task.h"
12 #include "base/task/thread_pool.h"
13 #include "media/base/bind_to_current_loop.h"
14 #include "media/base/decoder_buffer.h"
15 #include "media/base/video_frame.h"
16
17 namespace media {
18
19 // Helper class which manages cancellation of Decode() after Reset() and makes
20 // it easier to destruct on the proper thread.
21 class CancellationHelper {
22 public:
CancellationHelper(std::unique_ptr<OffloadableVideoDecoder> decoder)23 CancellationHelper(std::unique_ptr<OffloadableVideoDecoder> decoder)
24 : cancellation_flag_(std::make_unique<base::AtomicFlag>()),
25 decoder_(std::move(decoder)) {}
26
27 // Safe to call from any thread.
Cancel()28 void Cancel() { cancellation_flag_->Set(); }
29
Decode(scoped_refptr<DecoderBuffer> buffer,VideoDecoder::DecodeCB decode_cb)30 void Decode(scoped_refptr<DecoderBuffer> buffer,
31 VideoDecoder::DecodeCB decode_cb) {
32 if (cancellation_flag_->IsSet()) {
33 std::move(decode_cb).Run(DecodeStatus::ABORTED);
34 return;
35 }
36
37 decoder_->Decode(std::move(buffer), std::move(decode_cb));
38 }
39
Reset(base::OnceClosure reset_cb)40 void Reset(base::OnceClosure reset_cb) {
41 // OffloadableVideoDecoders are required to have a synchronous Reset(), so
42 // we don't need to wait for the Reset to complete. Despite this, we don't
43 // want to run |reset_cb| before we've reset the cancellation flag or the
44 // client may end up issuing another Reset() before this code runs.
45 decoder_->Reset(base::DoNothing());
46 cancellation_flag_.reset(new base::AtomicFlag());
47 std::move(reset_cb).Run();
48 }
49
decoder() const50 OffloadableVideoDecoder* decoder() const { return decoder_.get(); }
51
52 private:
53 std::unique_ptr<base::AtomicFlag> cancellation_flag_;
54 std::unique_ptr<OffloadableVideoDecoder> decoder_;
55
56 DISALLOW_COPY_AND_ASSIGN(CancellationHelper);
57 };
58
OffloadingVideoDecoder(int min_offloading_width,std::vector<VideoCodec> supported_codecs,std::unique_ptr<OffloadableVideoDecoder> decoder)59 OffloadingVideoDecoder::OffloadingVideoDecoder(
60 int min_offloading_width,
61 std::vector<VideoCodec> supported_codecs,
62 std::unique_ptr<OffloadableVideoDecoder> decoder)
63 : min_offloading_width_(min_offloading_width),
64 supported_codecs_(std::move(supported_codecs)),
65 helper_(std::make_unique<CancellationHelper>(std::move(decoder))) {
66 DETACH_FROM_SEQUENCE(sequence_checker_);
67 }
68
~OffloadingVideoDecoder()69 OffloadingVideoDecoder::~OffloadingVideoDecoder() {
70 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
71
72 // The |helper_| must always be destroyed on the |offload_task_runner_| since
73 // we may still have tasks posted to it.
74 if (offload_task_runner_)
75 offload_task_runner_->DeleteSoon(FROM_HERE, std::move(helper_));
76 }
77
GetDisplayName() const78 std::string OffloadingVideoDecoder::GetDisplayName() const {
79 // This call is expected to be static and safe to call from any thread.
80 return helper_->decoder()->GetDisplayName();
81 }
82
Initialize(const VideoDecoderConfig & config,bool low_delay,CdmContext * cdm_context,InitCB init_cb,const OutputCB & output_cb,const WaitingCB & waiting_cb)83 void OffloadingVideoDecoder::Initialize(const VideoDecoderConfig& config,
84 bool low_delay,
85 CdmContext* cdm_context,
86 InitCB init_cb,
87 const OutputCB& output_cb,
88 const WaitingCB& waiting_cb) {
89 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
90 DCHECK(config.IsValidConfig());
91
92 const bool disable_offloading =
93 config.is_encrypted() ||
94 config.coded_size().width() < min_offloading_width_ ||
95 std::find(supported_codecs_.begin(), supported_codecs_.end(),
96 config.codec()) == supported_codecs_.end();
97
98 if (initialized_) {
99 initialized_ = false;
100
101 // We're transitioning from offloading to no offloading, so detach from the
102 // offloading thread so we can run on the media thread.
103 if (disable_offloading && offload_task_runner_) {
104 offload_task_runner_->PostTaskAndReply(
105 FROM_HERE,
106 base::BindOnce(&OffloadableVideoDecoder::Detach,
107 base::Unretained(helper_->decoder())),
108 // We must trampoline back trough OffloadingVideoDecoder because it's
109 // possible for this class to be destroyed during Initialize().
110 base::BindOnce(&OffloadingVideoDecoder::Initialize,
111 weak_factory_.GetWeakPtr(), config, low_delay,
112 cdm_context, std::move(init_cb), output_cb,
113 waiting_cb));
114 return;
115 }
116
117 // We're transitioning from no offloading to offloading, so detach from the
118 // media thread so we can run on the offloading thread.
119 if (!disable_offloading && !offload_task_runner_)
120 helper_->decoder()->Detach();
121 }
122
123 DCHECK(!initialized_);
124 initialized_ = true;
125
126 // Offloaded decoders expect asynchronous execution of callbacks; even if we
127 // aren't currently using the offload thread.
128 InitCB bound_init_cb = BindToCurrentLoop(std::move(init_cb));
129 OutputCB bound_output_cb = BindToCurrentLoop(output_cb);
130
131 // If we're not offloading just pass through to the wrapped decoder.
132 if (disable_offloading) {
133 offload_task_runner_ = nullptr;
134 helper_->decoder()->Initialize(config, low_delay, cdm_context,
135 std::move(bound_init_cb), bound_output_cb,
136 waiting_cb);
137 return;
138 }
139
140 if (!offload_task_runner_) {
141 offload_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
142 {base::TaskPriority::USER_BLOCKING});
143 }
144
145 offload_task_runner_->PostTask(
146 FROM_HERE,
147 base::BindOnce(&OffloadableVideoDecoder::Initialize,
148 base::Unretained(helper_->decoder()), config, low_delay,
149 cdm_context, std::move(bound_init_cb), bound_output_cb,
150 waiting_cb));
151 }
152
Decode(scoped_refptr<DecoderBuffer> buffer,DecodeCB decode_cb)153 void OffloadingVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
154 DecodeCB decode_cb) {
155 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
156 DCHECK(buffer);
157 DCHECK(decode_cb);
158
159 DecodeCB bound_decode_cb = BindToCurrentLoop(std::move(decode_cb));
160 if (!offload_task_runner_) {
161 helper_->decoder()->Decode(std::move(buffer), std::move(bound_decode_cb));
162 return;
163 }
164
165 offload_task_runner_->PostTask(
166 FROM_HERE, base::BindOnce(&CancellationHelper::Decode,
167 base::Unretained(helper_.get()),
168 std::move(buffer), std::move(bound_decode_cb)));
169 }
170
Reset(base::OnceClosure reset_cb)171 void OffloadingVideoDecoder::Reset(base::OnceClosure reset_cb) {
172 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
173
174 base::OnceClosure bound_reset_cb = BindToCurrentLoop(std::move(reset_cb));
175 if (!offload_task_runner_) {
176 helper_->Reset(std::move(bound_reset_cb));
177 } else {
178 helper_->Cancel();
179 offload_task_runner_->PostTask(
180 FROM_HERE, base::BindOnce(&CancellationHelper::Reset,
181 base::Unretained(helper_.get()),
182 std::move(bound_reset_cb)));
183 }
184 }
185
GetMaxDecodeRequests() const186 int OffloadingVideoDecoder::GetMaxDecodeRequests() const {
187 // If we're offloading, try to parallelize decodes as well. Take care when
188 // adjusting this number as it may dramatically increase memory usage and
189 // reduce seek times. See http://crbug.com/731841.
190 //
191 // The current value of 2 was determined via experimental adjustment until a
192 // 4K60 VP9 playback dropped zero frames.
193 return offload_task_runner_ ? 2 : 1;
194 }
195
196 } // namespace media
197