blob: c9fcb70d07fd18739f036a623c837a897838fb10 [file] [log] [blame]
// Copyright 2019 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/filters/dav1d_video_decoder.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/decoder_buffer.h"
#include "media/base/limits.h"
#include "media/base/media_log.h"
#include "media/base/video_util.h"
extern "C" {
#include "third_party/dav1d/libdav1d/include/dav1d/dav1d.h"
}
namespace media {
static void GetDecoderThreadCounts(const int coded_height,
int* tile_threads,
int* frame_threads) {
// Tile thread counts based on currently available content. Recommended by
// YouTube, while frame thread values fit within limits::kMaxVideoThreads.
if (coded_height >= 700) {
*tile_threads =
4; // Current 720p content is encoded in 5 tiles and 1080p content with
// 8 tiles, but we'll exceed limits::kMaxVideoThreads with 5+ tile
// threads with 3 frame threads (5 * 3 + 3 = 18 threads vs 16 max).
//
// Since 720p playback isn't smooth without 3 frame threads, we've
// chosen a slightly lower tile thread count.
*frame_threads = 3;
} else if (coded_height >= 300) {
*tile_threads = 3;
*frame_threads = 2;
} else {
*tile_threads = 2;
*frame_threads = 2;
}
}
static VideoPixelFormat Dav1dImgFmtToVideoPixelFormat(
const Dav1dPictureParameters* pic) {
switch (pic->layout) {
case DAV1D_PIXEL_LAYOUT_I420:
switch (pic->bpc) {
case 8:
return PIXEL_FORMAT_I420;
case 10:
return PIXEL_FORMAT_YUV420P10;
case 12:
return PIXEL_FORMAT_YUV420P12;
default:
DLOG(ERROR) << "Unsupported bit depth: " << pic->bpc;
return PIXEL_FORMAT_UNKNOWN;
}
case DAV1D_PIXEL_LAYOUT_I422:
switch (pic->bpc) {
case 8:
return PIXEL_FORMAT_I422;
case 10:
return PIXEL_FORMAT_YUV422P10;
case 12:
return PIXEL_FORMAT_YUV422P12;
default:
DLOG(ERROR) << "Unsupported bit depth: " << pic->bpc;
return PIXEL_FORMAT_UNKNOWN;
}
case DAV1D_PIXEL_LAYOUT_I444:
switch (pic->bpc) {
case 8:
return PIXEL_FORMAT_I444;
case 10:
return PIXEL_FORMAT_YUV444P10;
case 12:
return PIXEL_FORMAT_YUV444P12;
default:
DLOG(ERROR) << "Unsupported bit depth: " << pic->bpc;
return PIXEL_FORMAT_UNKNOWN;
}
default:
DLOG(ERROR) << "Unsupported pixel format: " << pic->layout;
return PIXEL_FORMAT_UNKNOWN;
}
}
static void ReleaseDecoderBuffer(const uint8_t* buffer, void* opaque) {
if (opaque)
static_cast<DecoderBuffer*>(opaque)->Release();
}
static void LogDav1dMessage(void* cookie, const char* format, va_list ap) {
auto log = base::StringPrintV(format, ap);
if (log.empty())
return;
if (log.back() == '\n')
log.pop_back();
DLOG(ERROR) << log;
}
// std::unique_ptr release helpers. We need to release both the containing
// structs as well as refs held within the structures.
struct ScopedDav1dDataFree {
void operator()(void* x) const {
auto* data = static_cast<Dav1dData*>(x);
dav1d_data_unref(data);
delete data;
}
};
struct ScopedDav1dPictureFree {
void operator()(void* x) const {
auto* pic = static_cast<Dav1dPicture*>(x);
dav1d_picture_unref(pic);
delete pic;
}
};
Dav1dVideoDecoder::Dav1dVideoDecoder(MediaLog* media_log,
OffloadState offload_state)
: media_log_(media_log),
bind_callbacks_(offload_state == OffloadState::kNormal) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
Dav1dVideoDecoder::~Dav1dVideoDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CloseDecoder();
}
std::string Dav1dVideoDecoder::GetDisplayName() const {
return "Dav1dVideoDecoder";
}
void Dav1dVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* /* cdm_context */,
const InitCB& init_cb,
const OutputCB& output_cb,
const WaitingCB& /* waiting_cb */) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(config.IsValidConfig());
InitCB bound_init_cb = bind_callbacks_ ? BindToCurrentLoop(init_cb) : init_cb;
if (config.is_encrypted() || config.codec() != kCodecAV1) {
bound_init_cb.Run(false);
return;
}
// Clear any previously initialized decoder.
CloseDecoder();
Dav1dSettings s;
dav1d_default_settings(&s);
// Compute the ideal thread count values. We'll then clamp these based on the
// maximum number of recommended threads (using number of processors, etc).
//
// dav1d will spawn |n_tile_threads| per frame thread.
GetDecoderThreadCounts(config.coded_size().height(), &s.n_tile_threads,
&s.n_frame_threads);
const int max_threads = VideoDecoder::GetRecommendedThreadCount(
s.n_frame_threads * (s.n_tile_threads + 1));
// First clamp tile threads to the allowed maximum. We prefer tile threads
// over frame threads since dav1d folk indicate they are more efficient. In an
// ideal world this would be auto-detected by dav1d from the content.
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=1536783#c0
s.n_tile_threads = std::min(max_threads, s.n_tile_threads);
// Now clamp frame threads based on the number of total threads that would be
// created with the given |n_tile_threads| value. Note: A thread count of 1
// generates no additional threads since the calling thread (this thread) is
// counted as a thread.
//
// We only want 1 frame thread in low delay mode, since otherwise we'll
// require at least two buffers before the first frame can be output.
//
// If a system has the cores for it, we'll end up using the following:
// <300p: 2 tile threads, 2 frame threads = 2 * 2 + 2 = 6 total threads.
// <700p: 3 tile threads, 2 frame threads = 3 * 2 + 2 = 8 total threads.
//
// For higher resolutions we hit limits::kMaxVideoThreads (16):
// >700p: 4 tile threads, 3 frame threads = 4 * 3 + 3 = 15 total threads.
//
// Due to the (surprising) performance issues which occurred when setting
// |n_frame_threads|=1 (https://crbug.com/957511) the minimum total number of
// threads is 6 (two tile and two frame) regardless of core count. The maximum
// is min(2 * base::SysInfo::NumberOfProcessors(), limits::kMaxVideoThreads).
if (low_delay)
s.n_frame_threads = 1;
else if (s.n_frame_threads * (s.n_tile_threads + 1) > max_threads)
s.n_frame_threads = std::max(2, max_threads / (s.n_tile_threads + 1));
// Route dav1d internal logs through Chrome's DLOG system.
s.logger = {nullptr, &LogDav1dMessage};
// Set a maximum frame size limit to avoid OOM'ing fuzzers.
s.frame_size_limit = limits::kMaxCanvas;
if (dav1d_open(&dav1d_decoder_, &s) < 0) {
bound_init_cb.Run(false);
return;
}
config_ = config;
state_ = DecoderState::kNormal;
output_cb_ = output_cb;
bound_init_cb.Run(true);
}
void Dav1dVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
const DecodeCB& decode_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(buffer);
DCHECK(decode_cb);
DCHECK_NE(state_, DecoderState::kUninitialized)
<< "Called Decode() before successful Initialize()";
DecodeCB bound_decode_cb =
bind_callbacks_ ? BindToCurrentLoop(decode_cb) : decode_cb;
if (state_ == DecoderState::kError) {
bound_decode_cb.Run(DecodeStatus::DECODE_ERROR);
return;
}
if (!DecodeBuffer(std::move(buffer))) {
state_ = DecoderState::kError;
bound_decode_cb.Run(DecodeStatus::DECODE_ERROR);
return;
}
// VideoDecoderShim expects |decode_cb| call after |output_cb_|.
bound_decode_cb.Run(DecodeStatus::OK);
}
void Dav1dVideoDecoder::Reset(const base::RepeatingClosure& reset_cb) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
state_ = DecoderState::kNormal;
dav1d_flush(dav1d_decoder_);
if (bind_callbacks_)
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, reset_cb);
else
reset_cb.Run();
}
void Dav1dVideoDecoder::Detach() {
// Even though we offload all resolutions of AV1, this may be called in a
// transition from clear to encrypted content. Which will subsequently fail
// Initialize() since encrypted content isn't supported by this decoder.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!bind_callbacks_);
CloseDecoder();
DETACH_FROM_SEQUENCE(sequence_checker_);
}
void Dav1dVideoDecoder::CloseDecoder() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!dav1d_decoder_)
return;
dav1d_close(&dav1d_decoder_);
DCHECK(!dav1d_decoder_);
}
bool Dav1dVideoDecoder::DecodeBuffer(scoped_refptr<DecoderBuffer> buffer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
using ScopedPtrDav1dData = std::unique_ptr<Dav1dData, ScopedDav1dDataFree>;
ScopedPtrDav1dData input_buffer;
if (!buffer->end_of_stream()) {
input_buffer.reset(new Dav1dData{0});
if (dav1d_data_wrap(input_buffer.get(), buffer->data(), buffer->data_size(),
&ReleaseDecoderBuffer, buffer.get()) < 0) {
return false;
}
input_buffer->m.timestamp = buffer->timestamp().InMicroseconds();
buffer->AddRef();
}
// Used to DCHECK that dav1d_send_data() actually takes the packet. If we exit
// this function without sending |input_buffer| that packet will be lost. We
// have no packet to send at end of stream.
bool send_data_completed = buffer->end_of_stream();
while (!input_buffer || input_buffer->sz) {
if (input_buffer) {
const int res = dav1d_send_data(dav1d_decoder_, input_buffer.get());
if (res < 0 && res != -EAGAIN) {
MEDIA_LOG(ERROR, media_log_) << "dav1d_send_data() failed on "
<< buffer->AsHumanReadableString();
return false;
}
if (res != -EAGAIN)
send_data_completed = true;
// Even if dav1d_send_data() returned EAGAIN, try dav1d_get_picture().
}
using ScopedPtrDav1dPicture =
std::unique_ptr<Dav1dPicture, ScopedDav1dPictureFree>;
ScopedPtrDav1dPicture p(new Dav1dPicture{0});
const int res = dav1d_get_picture(dav1d_decoder_, p.get());
if (res < 0) {
if (res != -EAGAIN) {
MEDIA_LOG(ERROR, media_log_) << "dav1d_get_picture() failed on "
<< buffer->AsHumanReadableString();
return false;
}
// We've reached end of stream and no frames remain to drain.
if (!input_buffer) {
DCHECK(send_data_completed);
return true;
}
continue;
}
auto frame = CopyImageToVideoFrame(p.get());
if (!frame) {
MEDIA_LOG(DEBUG, media_log_)
<< "Failed to produce video frame from Dav1dPicture.";
return false;
}
// AV1 color space defines match ISO 23001-8:2016 via ISO/IEC 23091-4/ITU-T
// H.273. https://aomediacodec.github.io/av1-spec/#color-config-semantics
media::VideoColorSpace color_space(
p->seq_hdr->pri, p->seq_hdr->trc, p->seq_hdr->mtrx,
p->seq_hdr->color_range ? gfx::ColorSpace::RangeID::FULL
: gfx::ColorSpace::RangeID::LIMITED);
// If the frame doesn't specify a color space, use the container's.
if (!color_space.IsSpecified())
color_space = config_.color_space_info();
frame->set_color_space(color_space.ToGfxColorSpace());
frame->metadata()->SetBoolean(VideoFrameMetadata::POWER_EFFICIENT, false);
frame->AddDestructionObserver(base::BindOnce(
base::DoNothing::Once<ScopedPtrDav1dPicture>(), std::move(p)));
output_cb_.Run(std::move(frame));
}
DCHECK(send_data_completed);
return true;
}
scoped_refptr<VideoFrame> Dav1dVideoDecoder::CopyImageToVideoFrame(
const Dav1dPicture* pic) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VideoPixelFormat pixel_format = Dav1dImgFmtToVideoPixelFormat(&pic->p);
if (pixel_format == PIXEL_FORMAT_UNKNOWN)
return nullptr;
// Since we're making a copy, only copy the visible area.
const gfx::Size visible_size(pic->p.w, pic->p.h);
return VideoFrame::WrapExternalYuvData(
pixel_format, visible_size, gfx::Rect(visible_size),
config_.natural_size(), pic->stride[0], pic->stride[1], pic->stride[1],
static_cast<uint8_t*>(pic->data[0]), static_cast<uint8_t*>(pic->data[1]),
static_cast<uint8_t*>(pic->data[2]),
base::TimeDelta::FromMicroseconds(pic->m.timestamp));
}
} // namespace media