summaryrefslogtreecommitdiff
path: root/chromium/media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.cc
blob: 6f15f1ec92231cabcb79db324fd5de95365132e3 (plain)
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
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/library_cdm/clear_key_cdm/cdm_video_decoder.h"

#include <memory>
#include <utility>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/queue.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/no_destructor.h"
#include "build/build_config.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
// Necessary to convert async media::VideoDecoder to sync CdmVideoDecoder.
// Typically not recommended for production code, but is ok here since
// ClearKeyCdm is only for testing.
#include "base/run_loop.h"
#include "base/task/single_thread_task_executor.h"
#include "base/threading/thread_task_runner_handle.h"
#include "media/base/decode_status.h"
#include "media/base/media_switches.h"
#include "media/base/media_util.h"
#include "media/cdm/cdm_type_conversion.h"
#include "media/cdm/library_cdm/cdm_host_proxy.h"
#include "media/media_buildflags.h"
#include "third_party/libyuv/include/libyuv/planar_functions.h"

#if BUILDFLAG(ENABLE_LIBVPX)
#include "media/filters/vpx_video_decoder.h"
#endif

#if BUILDFLAG(ENABLE_DAV1D_DECODER)
#include "media/filters/dav1d_video_decoder.h"
#endif

#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
#include "media/filters/ffmpeg_video_decoder.h"
#endif

#if BUILDFLAG(ENABLE_LIBGAV1_DECODER)
#include "media/filters/gav1_video_decoder.h"
#endif

namespace media {

namespace {

media::VideoDecoderConfig ToClearMediaVideoDecoderConfig(
    const cdm::VideoDecoderConfig_3& config) {
  gfx::Size coded_size(config.coded_size.width, config.coded_size.width);

  VideoDecoderConfig media_config(
      ToMediaVideoCodec(config.codec), ToMediaVideoCodecProfile(config.profile),
      VideoDecoderConfig::AlphaMode::kIsOpaque,
      ToMediaColorSpace(config.color_space), kNoTransformation, coded_size,
      gfx::Rect(coded_size), coded_size,
      std::vector<uint8_t>(config.extra_data,
                           config.extra_data + config.extra_data_size),
      EncryptionScheme::kUnencrypted);

  return media_config;
}

bool ToCdmVideoFrame(const VideoFrame& video_frame,
                     CdmHostProxy* cdm_host_proxy,
                     CdmVideoDecoder::CdmVideoFrame* cdm_video_frame) {
  DCHECK(cdm_video_frame);

  if (!video_frame.IsMappable()) {
    DVLOG(1) << "VideoFrame is not mappable";
    return false;
  }

  if (!IsYuvPlanar(video_frame.format())) {
    DVLOG(1) << "Only YUV planar format supported";
    return false;
  }

  if (VideoFrame::NumPlanes(video_frame.format()) != 3u) {
    DVLOG(1) << "Only 3-plane format supported";
    return false;
  }

  auto cdm_video_format = ToCdmVideoFormat(video_frame.format());
  if (cdm_video_format == cdm::kUnknownVideoFormat) {
    DVLOG(1) << "VideoFrame has unsupported format: " << video_frame.format();
    return false;
  }

  // Get required allocation size for a tightly packed frame.
  auto space_required = VideoFrame::AllocationSize(video_frame.format(),
                                                   video_frame.coded_size());
  auto* buffer = cdm_host_proxy->Allocate(space_required);
  if (!buffer) {
    LOG(ERROR) << __func__ << ": Buffer allocation failed.";
    return false;
  }

  buffer->SetSize(base::checked_cast<uint32_t>(space_required));
  cdm_video_frame->SetFrameBuffer(buffer);
  cdm_video_frame->SetFormat(cdm_video_format);
  cdm_video_frame->SetSize(
      {video_frame.coded_size().width(), video_frame.coded_size().height()});
  cdm_video_frame->SetTimestamp(video_frame.timestamp().InMicroseconds());
  // TODO(crbug.com/707127): Set ColorSpace here. It's not trivial to convert
  // a gfx::ColorSpace (from VideoFrame) to another other ColorSpace like
  // cdm::ColorSpace.

  static_assert(VideoFrame::kYPlane == cdm::kYPlane && cdm::kYPlane == 0, "");
  static_assert(VideoFrame::kUPlane == cdm::kUPlane && cdm::kUPlane == 1, "");
  static_assert(VideoFrame::kVPlane == cdm::kVPlane && cdm::kVPlane == 2, "");

  uint8_t* dst = buffer->Data();
  uint32_t offset = 0;
  for (int plane = 0; plane < 3; ++plane) {
    const uint8_t* src = video_frame.data(plane);
    int src_stride = video_frame.stride(plane);
    int row_bytes = video_frame.row_bytes(plane);
    int rows = video_frame.rows(plane);

    auto cdm_plane = static_cast<cdm::VideoPlane>(plane);
    cdm_video_frame->SetPlaneOffset(cdm_plane, offset);
    // Since it's tightly packed, the stride is the same as row bytes.
    cdm_video_frame->SetStride(cdm_plane, row_bytes);

    libyuv::CopyPlane(src, src_stride, dst, row_bytes, row_bytes, rows);
    dst += row_bytes * rows;
    offset += row_bytes * rows;
  }

  DCHECK_GE(space_required, offset) << ": Space mismatch";
  return true;
}

// Media VideoDecoders typically assumes a global environment where a lot of
// things are already setup in the process, e.g. base::ThreadTaskRunnerHandle
// and base::CommandLine. These will be available in the component build because
// the CDM and the host is depending on the same base/ target. In static build,
// they will not be available and we have to setup it by ourselves.
void SetupGlobalEnvironmentIfNeeded() {
  // Creating a base::SingleThreadTaskExecutor to setup
  // base::ThreadTaskRunnerHandle.
  if (!base::ThreadTaskRunnerHandle::IsSet()) {
    static base::NoDestructor<base::SingleThreadTaskExecutor> task_executor;
  }

  // Initialize CommandLine if not already initialized. Since this is a DLL,
  // just use empty arguments.
  if (!base::CommandLine::InitializedForCurrentProcess()) {
#if defined(OS_WIN)
    // Use InitUsingArgvForTesting() instead of Init() to avoid dependency on
    // shell32 API which might not work in the sandbox. See crbug.com/1242710.
    base::CommandLine::InitUsingArgvForTesting(0, nullptr);
#else
    base::CommandLine::Init(0, nullptr);
#endif
  }
}

// Adapts a media::VideoDecoder to a CdmVideoDecoder. Media VideoDecoders
// operations are asynchronous, often posting callbacks to the task runner. The
// CdmVideoDecoder operations are synchronous. Therefore, after calling
// media::VideoDecoder, we need to run a RunLoop manually and wait for the
// asynchronous operation to finish. The RunLoop must be of type
// |kNestableTasksAllowed| because we could be running the RunLoop in a task,
// e.g. in component builds when we share the same task runner as the host. In
// a static build, this is not necessary.
class VideoDecoderAdapter final : public CdmVideoDecoder {
 public:
  VideoDecoderAdapter(CdmHostProxy* cdm_host_proxy,
                      std::unique_ptr<VideoDecoder> video_decoder)
      : cdm_host_proxy_(cdm_host_proxy),
        video_decoder_(std::move(video_decoder)) {
    DCHECK(cdm_host_proxy_);
  }

  VideoDecoderAdapter(const VideoDecoderAdapter&) = delete;
  VideoDecoderAdapter& operator=(const VideoDecoderAdapter&) = delete;

  ~VideoDecoderAdapter() final = default;

  // CdmVideoDecoder implementation.
  Status Initialize(const cdm::VideoDecoderConfig_3& config) final {
    auto clear_config = ToClearMediaVideoDecoderConfig(config);
    DVLOG(1) << __func__ << ": " << clear_config.AsHumanReadableString();
    DCHECK(!last_init_result_.has_value());

    // Initialize |video_decoder_| and wait for completion.
    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
    video_decoder_->Initialize(
        clear_config,
        /* low_delay = */ false,
        /* cdm_context = */ nullptr,
        base::BindOnce(&VideoDecoderAdapter::OnInitialized,
                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()),
        base::BindRepeating(&VideoDecoderAdapter::OnVideoFrameReady,
                            weak_factory_.GetWeakPtr()),
        /* waiting_cb = */ base::DoNothing());
    run_loop.Run();

    auto result = std::move(last_init_result_.value());
    last_init_result_.reset();

    return result;
  }

  void Deinitialize() final {
    // Do nothing since |video_decoder_| supports reinitialization without
    // the need to deinitialize first.
  }

  void Reset() final {
    DVLOG(2) << __func__;

    // Reset |video_decoder_| and wait for completion.
    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
    video_decoder_->Reset(base::BindOnce(&VideoDecoderAdapter::OnReset,
                                         weak_factory_.GetWeakPtr(),
                                         run_loop.QuitClosure()));
    run_loop.Run();
  }

  cdm::Status Decode(scoped_refptr<DecoderBuffer> buffer,
                     CdmVideoFrame* decoded_frame) final {
    DVLOG(3) << __func__;
    DCHECK(!last_decode_status_.has_value());

    // Call |video_decoder_| Decode() and wait for completion.
    base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
    video_decoder_->Decode(
        std::move(buffer),
        base::BindOnce(&VideoDecoderAdapter::OnDecoded,
                       weak_factory_.GetWeakPtr(), run_loop.QuitClosure()));
    run_loop.Run();

    auto decode_status = last_decode_status_.value();
    last_decode_status_.reset();

    // "kAborted" shouldn't happen during a sync decode, so treat it as an
    // error.
    DCHECK_NE(decode_status.code(), StatusCode::kAborted);

    if (!decode_status.is_ok())
      return cdm::kDecodeError;

    if (decoded_video_frames_.empty())
      return cdm::kNeedMoreData;

    auto video_frame = decoded_video_frames_.front();
    decoded_video_frames_.pop();

    return ToCdmVideoFrame(*video_frame, cdm_host_proxy_, decoded_frame)
               ? cdm::kSuccess
               : cdm::kDecodeError;
  }

 private:
  void OnInitialized(base::OnceClosure quit_closure, Status status) {
    DVLOG(1) << __func__ << " success = " << status.is_ok();
    DCHECK(!last_init_result_.has_value());
    last_init_result_ = std::move(status);
    std::move(quit_closure).Run();
  }

  void OnVideoFrameReady(scoped_refptr<VideoFrame> video_frame) {
    // Do not queue EOS frames, which is not needed.
    if (video_frame->metadata().end_of_stream)
      return;

    decoded_video_frames_.push(std::move(video_frame));
  }

  void OnReset(base::OnceClosure quit_closure) {
    VideoFrameQueue empty_queue;
    std::swap(decoded_video_frames_, empty_queue);
    std::move(quit_closure).Run();
  }

  void OnDecoded(base::OnceClosure quit_closure, Status decode_status) {
    DCHECK(!last_decode_status_.has_value());
    last_decode_status_ = std::move(decode_status);
    std::move(quit_closure).Run();
  }

  CdmHostProxy* const cdm_host_proxy_;
  std::unique_ptr<VideoDecoder> video_decoder_;

  // Results of |video_decoder_| operations. Set iff the callback of the
  // operation has been called.
  absl::optional<Status> last_init_result_;
  absl::optional<Status> last_decode_status_;

  // Queue of decoded video frames.
  using VideoFrameQueue = base::queue<scoped_refptr<VideoFrame>>;
  VideoFrameQueue decoded_video_frames_;

  base::WeakPtrFactory<VideoDecoderAdapter> weak_factory_{this};
};

}  // namespace

std::unique_ptr<CdmVideoDecoder> CreateVideoDecoder(
    CdmHostProxy* cdm_host_proxy,
    const cdm::VideoDecoderConfig_3& config) {
  SetupGlobalEnvironmentIfNeeded();

  static base::NoDestructor<media::NullMediaLog> null_media_log;
  std::unique_ptr<VideoDecoder> video_decoder;

#if BUILDFLAG(ENABLE_LIBVPX)
  if (config.codec == cdm::kCodecVp8 || config.codec == cdm::kCodecVp9)
    video_decoder = std::make_unique<VpxVideoDecoder>();
#endif

#if BUILDFLAG(ENABLE_LIBGAV1_DECODER)
  if (base::FeatureList::IsEnabled(kGav1VideoDecoder)) {
    if (config.codec == cdm::kCodecAv1)
      video_decoder.reset(new Gav1VideoDecoder(null_media_log.get()));
  } else
#endif  // BUILDFLAG(ENABLE_LIBGAV1_DECODER)
  {
#if BUILDFLAG(ENABLE_DAV1D_DECODER)
    if (config.codec == cdm::kCodecAv1)
      video_decoder = std::make_unique<Dav1dVideoDecoder>(null_media_log.get());
#endif
  }

#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
  if (!video_decoder)
    video_decoder = std::make_unique<FFmpegVideoDecoder>(null_media_log.get());
#endif

  if (!video_decoder)
    return nullptr;

  return std::make_unique<VideoDecoderAdapter>(cdm_host_proxy,
                                               std::move(video_decoder));
}

}  // namespace media