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