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