Commit 1f23a58a authored by Jao-ke Chin-Lee's avatar Jao-ke Chin-Lee Committed by Chromium LUCI CQ

[media/gpu/vaapi/test] Add ScopedVAConfig and SharedVAContext.

Previously, VA configuration was tied to VaapiDevice, meaning
that the VAProfile was static per VaapiDevice. This also
forced users of decode_test to specify the profile and codec,
i.e. VP9Profile0, even though the decoder can deduce both.

Separate out ScopedVAConfig and introduce SharedVAContext so that
decoders can manage this configuration and the configuration can
manage its own lifetime, allowing decode_test to be used on most
streams that change profile or resolution mid-stream.

BUG=chromium:1062407,b:175909299
TEST=./decode_test -video=resolution_change_500frames-vp9.ivf -out-prefix=res_change/frame on nautilus

Change-Id: I32ad5dfab57851166ce31844f6609784fbcaf900
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2358989
Commit-Queue: Jao-ke Chin-Lee <jchinlee@chromium.org>
Reviewed-by: default avatarAndres Calderon Jaramillo <andrescj@chromium.org>
Reviewed-by: default avatarMiguel Casas <mcasas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#838985}
parent ece170fc
......@@ -343,6 +343,10 @@ executable("decode_test") {
sources = [
"test/decode.cc",
"test/macros.h",
"test/scoped_va_config.cc",
"test/scoped_va_config.h",
"test/scoped_va_context.cc",
"test/scoped_va_context.h",
"test/shared_va_surface.cc",
"test/shared_va_surface.h",
"test/vaapi_device.cc",
......
......@@ -25,27 +25,32 @@ using media::vaapi_test::Vp9Decoder;
using media_gpu_vaapi::InitializeStubs;
using media_gpu_vaapi::kModuleVa;
using media_gpu_vaapi::kModuleVa_drm;
#if BUILDFLAG(IS_CHROMEOS_ASH)
using media_gpu_vaapi::kModuleVa_prot;
#endif
using media_gpu_vaapi::StubPathMap;
#define fourcc(a, b, c, d) \
((static_cast<uint32_t>(a) << 0) | (static_cast<uint32_t>(b) << 8) | \
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24))
namespace {
constexpr char kUsageMsg[] =
"usage: decode_test\n"
" --video=<video path>\n"
" --profile=<profile of input video>\n"
" [--frames=<number of frames to decode>]\n"
" [--out-prefix=<path prefix of decoded frame PNGs>]\n"
" [--v=<log verbosity>]"
" [--help]";
" [--v=<log verbosity>]\n"
" [--help]\n";
constexpr char kHelpMsg[] =
"This binary decodes the IVF video in <video> path with specified video\n"
"<profile> via thinly wrapped libva calls.\n"
"Supported codecs: VP9 (profiles 0, 2)\n"
"\nThe following arguments are supported:\n"
" --video=<path>\n"
" Required. Path to IVF-formatted video to decode.\n"
" --profile=<VP9Profile0|VP9Profile2>\n"
" Required (case insensitive). Profile of given video.\n"
" --frames=<int>\n"
" Optional. Number of frames to decode, defaults to all.\n"
" Override with a positive integer to decode at most that many.\n"
......@@ -58,28 +63,25 @@ constexpr char kHelpMsg[] =
" --help\n"
" Display this help message and exit.\n";
// Converts the provided string to a VAProfile, returning VAProfileNone if not
// supported by the test binary.
VAProfile GetProfile(const std::string& profile_str) {
if (base::EqualsCaseInsensitiveASCII(profile_str, "vp9profile0"))
return VAProfileVP9Profile0;
if (base::EqualsCaseInsensitiveASCII(profile_str, "vp9profile2"))
return VAProfileVP9Profile2;
return VAProfileNone;
// Creates the appropriate decoder for the given |fourcc|.
std::unique_ptr<VideoDecoder> CreateDecoder(
uint32_t fourcc,
std::unique_ptr<media::IvfParser> ivf_parser,
const VaapiDevice& va_device) {
if (fourcc == fourcc('V', 'P', '9', '0'))
return std::make_unique<Vp9Decoder>(std::move(ivf_parser), va_device);
return nullptr;
}
// Gets the appropriate decoder for the given |va_profile|.
std::unique_ptr<VideoDecoder> GetDecoder(
VAProfile va_profile,
std::unique_ptr<media::IvfParser> ivf_parser,
const VaapiDevice& va) {
switch (va_profile) {
case VAProfileVP9Profile0:
case VAProfileVP9Profile2:
return std::make_unique<Vp9Decoder>(std::move(ivf_parser), va);
default:
return nullptr;
}
// Returns string representation of |fourcc|.
std::string FourccStr(uint32_t fourcc) {
std::stringstream s;
s << static_cast<char>(fourcc & 0xFF)
<< static_cast<char>((fourcc >> 8) & 0xFF)
<< static_cast<char>((fourcc >> 16) & 0xFF)
<< static_cast<char>((fourcc >> 24) & 0xFF);
return s.str();
}
} // namespace
......@@ -105,17 +107,6 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
const std::string profile_str = cmd->GetSwitchValueASCII("profile");
if (profile_str.empty()) {
std::cout << "No profile provided with which to decode.\n" << kUsageMsg;
return EXIT_FAILURE;
}
const VAProfile profile = GetProfile(profile_str);
if (profile == VAProfileNone) {
std::cout << "Profile " << profile_str << " not supported.\n" << kUsageMsg;
return EXIT_FAILURE;
}
std::string output_prefix = cmd->GetSwitchValueASCII("out-prefix");
const std::string frames = cmd->GetSwitchValueASCII("frames");
......@@ -141,28 +132,34 @@ int main(int argc, char** argv) {
LOG(ERROR) << "Couldn't initialize IVF parser for file: " << video_path;
return EXIT_FAILURE;
}
const gfx::Size size(file_header.width, file_header.height);
VLOG(1) << "video size: " << size.ToString();
// Initialize VA stubs.
StubPathMap paths;
const std::string va_suffix(base::NumberToString(VA_MAJOR_VERSION + 1));
paths[kModuleVa].push_back(std::string("libva.so.") + va_suffix);
paths[kModuleVa_drm].push_back(std::string("libva-drm.so.") + va_suffix);
#if BUILDFLAG(IS_CHROMEOS_ASH)
paths[kModuleVa_prot].push_back(std::string("libva.so.") + va_suffix);
#endif
if (!InitializeStubs(paths)) {
LOG(ERROR) << "Failed to initialize VA stubs";
return EXIT_FAILURE;
}
// Initialize VA with profile as provided in cmdline args.
VLOG(1) << "Creating VaapiDevice with profile " << profile;
const VaapiDevice va(profile);
const VaapiDevice va_device;
std::unique_ptr<VideoDecoder> dec =
GetDecoder(profile, std::move(ivf_parser), va);
CHECK(dec);
CreateDecoder(file_header.fourcc, std::move(ivf_parser), va_device);
if (!dec) {
LOG(ERROR) << "Codec " << FourccStr(file_header.fourcc)
<< " not supported.\n"
<< kUsageMsg;
return EXIT_FAILURE;
}
VLOG(1) << "Created decoder for codec " << FourccStr(file_header.fourcc);
VideoDecoder::Result res;
int i = 0;
bool errored = false;
while (true) {
LOG(INFO) << "Frame " << i << "...";
res = dec->DecodeNextFrame();
......@@ -174,6 +171,7 @@ int main(int argc, char** argv) {
if (res == VideoDecoder::kFailed) {
LOG(ERROR) << "Failed to decode.";
errored = true;
continue;
}
......@@ -188,5 +186,7 @@ int main(int argc, char** argv) {
LOG(INFO) << "Done reading.";
if (errored)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
// Copyright 2020 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 <va/va.h>
#include <va/va_str.h>
#include "media/gpu/vaapi/test/macros.h"
#include "media/gpu/vaapi/test/scoped_va_config.h"
namespace media {
namespace vaapi_test {
ScopedVAConfig::ScopedVAConfig(const VaapiDevice& device,
VAProfile profile,
unsigned int va_rt_format)
: device_(device),
config_id_(VA_INVALID_ID),
profile_(profile),
va_rt_format_(va_rt_format) {
// We rely on vaCreateConfig to specify the error mode if decode is not
// supported for the given profile.
std::vector<VAConfigAttrib> attribs;
attribs.push_back(
{VAConfigAttribRTFormat, base::strict_cast<uint32_t>(va_rt_format_)});
const VAStatus res =
vaCreateConfig(device_.display(), profile_, VAEntrypointVLD,
attribs.data(), attribs.size(), &config_id_);
VA_LOG_ASSERT(res, "vaCreateConfig");
LOG_ASSERT(config_id_ != VA_INVALID_ID)
<< "vaCreateConfig created invalid config ID";
VLOG(1) << "Created config with ID " << config_id_ << " and profile "
<< vaProfileStr(profile_);
}
ScopedVAConfig::~ScopedVAConfig() {
VLOG(1) << "Destroying config " << config_id_ << " with profile "
<< vaProfileStr(profile_);
DCHECK_NE(config_id_, VA_INVALID_ID);
const VAStatus res = vaDestroyConfig(device_.display(), config_id_);
VA_LOG_ASSERT(res, "vaDestroyConfig");
}
} // namespace vaapi_test
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_GPU_VAAPI_TEST_SCOPED_VA_CONFIG_H_
#define MEDIA_GPU_VAAPI_TEST_SCOPED_VA_CONFIG_H_
#include "media/gpu/vaapi/test/vaapi_device.h"
namespace media {
namespace vaapi_test {
// This class holds configuration information for a VaapiDevice. The VaapiDevice
// must be externally guaranteed to outlive the ScopedVAConfig.
class ScopedVAConfig {
public:
// Initializes the VA handles for a VAConfig with |profile| and
// |va_rt_format|. Requires an initialized |device|. Success is ASSERTed.
ScopedVAConfig(const VaapiDevice& device,
VAProfile profile,
unsigned int va_rt_format);
ScopedVAConfig(const ScopedVAConfig&) = delete;
ScopedVAConfig& operator=(const ScopedVAConfig&) = delete;
~ScopedVAConfig();
VAConfigID id() const { return config_id_; }
VAProfile profile() const { return profile_; }
unsigned int va_rt_format() const { return va_rt_format_; }
private:
// Non-owned.
const VaapiDevice& device_;
VAConfigID config_id_;
const VAProfile profile_;
const unsigned int va_rt_format_;
};
} // namespace vaapi_test
} // namespace media
#endif // MEDIA_GPU_VAAPI_TEST_SCOPED_VA_CONFIG_H_
// Copyright 2020 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 <va/va.h>
#include "media/gpu/vaapi/test/macros.h"
#include "media/gpu/vaapi/test/scoped_va_context.h"
namespace media {
namespace vaapi_test {
ScopedVAContext::ScopedVAContext(const VaapiDevice& device,
const ScopedVAConfig& config,
const gfx::Size& size)
: device_(device),
config_(config),
context_id_(VA_INVALID_ID),
size_(size) {
const VAStatus res =
vaCreateContext(device_.display(), config_.id(), size_.width(),
size_.height(), VA_PROGRESSIVE,
/*render_targets=*/nullptr,
/*num_render_targets=*/0, &context_id_);
VA_LOG_ASSERT(res, "vaCreateContext");
LOG_ASSERT(context_id_ != VA_INVALID_ID)
<< "vaCreateContext created invalid context ID";
VLOG(1) << "Created context with ID " << context_id_;
}
ScopedVAContext::~ScopedVAContext() {
VLOG(1) << "Destroying context " << context_id_;
DCHECK_NE(context_id_, VA_INVALID_ID);
const VAStatus res = vaDestroyContext(device_.display(), context_id_);
VA_LOG_ASSERT(res, "vaDestroyContext");
}
} // namespace vaapi_test
} // namespace media
// Copyright 2020 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.
#ifndef MEDIA_GPU_VAAPI_TEST_SCOPED_VA_CONTEXT_H_
#define MEDIA_GPU_VAAPI_TEST_SCOPED_VA_CONTEXT_H_
#include "media/gpu/vaapi/test/scoped_va_config.h"
#include "media/gpu/vaapi/test/vaapi_device.h"
#include "ui/gfx/geometry/size.h"
namespace media {
namespace vaapi_test {
// Provides a wrapper around a VAContext that properly handles creation and
// destruction. Decoders should use this to recreate a context when the VAConfig
// or size changes.
// The associated VaapiDevice and ScopedVAConfig must be guaranteed externally
// to be alive beyond the lifetime of the ScopedVAContext.
class ScopedVAContext {
public:
// Constructs a VAContext with given |size|. Requires an initialized |device|.
// Success is ASSERTed.
ScopedVAContext(const VaapiDevice& device,
const ScopedVAConfig& config,
const gfx::Size& size);
ScopedVAContext(const ScopedVAContext&) = delete;
ScopedVAContext& operator=(const ScopedVAContext&) = delete;
~ScopedVAContext();
VAContextID id() const { return context_id_; }
const gfx::Size& size() const { return size_; }
private:
// Non-owned.
const VaapiDevice& device_;
const ScopedVAConfig& config_;
VAContextID context_id_;
const gfx::Size size_;
};
} // namespace vaapi_test
} // namespace media
#endif
......@@ -26,6 +26,7 @@ bool DeriveImage(VADisplay display,
VLOG_IF(2, (res != VA_STATUS_SUCCESS))
<< "vaDeriveImage failed, VA error: " << vaErrorStr(res);
// TODO(jchinlee): Support derivation into 10-bit fourcc.
if (image->format.fourcc != VA_FOURCC_NV12) {
VLOG(2) << "Test decoder binary does not support derived surface format "
<< "with fourcc " << media::FourccToString(image->format.fourcc);
......@@ -39,12 +40,32 @@ bool DeriveImage(VADisplay display,
return true;
}
// Returns image format to use given the surface's internal VA format.
VAImageFormat GetImageFormat(unsigned int va_rt_format) {
constexpr VAImageFormat kImageFormatNV12{.fourcc = VA_FOURCC_NV12,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 12};
constexpr VAImageFormat kImageFormatP010{.fourcc = VA_FOURCC_P010,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 16};
switch (va_rt_format) {
case VA_RT_FORMAT_YUV420:
return kImageFormatNV12;
case VA_RT_FORMAT_YUV420_10:
return kImageFormatP010;
default:
LOG_ASSERT(false) << "Unknown VA format " << std::hex << va_rt_format;
return VAImageFormat{};
}
}
// Maps the image data from |surface_id| in |display| with given |size| by
// attempting to derive into |image| and |image_data|, or creating an NV12
// attempting to derive into |image| and |image_data|, or creating a
// VAImage to use with vaGetImage as fallback and setting |image| and
// |image_data| accordingly.
void GetSurfaceImage(VADisplay display,
VASurfaceID surface_id,
unsigned int va_rt_format,
const gfx::Size size,
VAImage* image,
uint8_t** image_data) {
......@@ -52,12 +73,8 @@ void GetSurfaceImage(VADisplay display,
if (DeriveImage(display, surface_id, image, image_data))
return;
// Fall back to getting the image as NV12.
VAImageFormat format{
.fourcc = VA_FOURCC_NV12,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 12,
};
// Fall back to getting the image with manually passed format.
VAImageFormat format = GetImageFormat(va_rt_format);
VAStatus res =
vaCreateImage(display, &format, size.width(), size.height(), image);
VA_LOG_ASSERT(res, "vaCreateImage");
......@@ -70,33 +87,41 @@ void GetSurfaceImage(VADisplay display,
VA_LOG_ASSERT(res, "vaMapBuffer");
}
uint16_t JoinUint8(uint8_t first, uint8_t second) {
// P010 uses the 10 most significant bits; H010 the 10 least.
const uint16_t joined = ((second << 8u) | first);
return joined >> 6u;
}
} // namespace
SharedVASurface::SharedVASurface(const VaapiDevice& va,
SharedVASurface::SharedVASurface(const VaapiDevice& va_device,
VASurfaceID id,
const gfx::Size& size,
unsigned int format)
: va_(va), id_(id), size_(size), internal_va_format_(format) {}
: va_device_(va_device), id_(id), size_(size), va_rt_format_(format) {}
// static
scoped_refptr<SharedVASurface> SharedVASurface::Create(const VaapiDevice& va,
const gfx::Size& size,
VASurfaceAttrib attrib) {
scoped_refptr<SharedVASurface> SharedVASurface::Create(
const VaapiDevice& va_device,
unsigned int va_rt_format,
const gfx::Size& size,
VASurfaceAttrib attrib) {
VASurfaceID surface_id;
VAStatus res =
vaCreateSurfaces(va.display(), va.internal_va_format(),
vaCreateSurfaces(va_device.display(), va_rt_format,
base::checked_cast<unsigned int>(size.width()),
base::checked_cast<unsigned int>(size.height()),
&surface_id, 1u, &attrib, 1u);
VA_LOG_ASSERT(res, "vaCreateSurfaces");
VLOG(1) << "created surface: " << surface_id;
return base::WrapRefCounted(
new SharedVASurface(va, surface_id, size, va.internal_va_format()));
new SharedVASurface(va_device, surface_id, size, va_rt_format));
}
SharedVASurface::~SharedVASurface() {
VAStatus res =
vaDestroySurfaces(va_.display(), const_cast<VASurfaceID*>(&id_), 1u);
VAStatus res = vaDestroySurfaces(va_device_.display(),
const_cast<VASurfaceID*>(&id_), 1u);
VA_LOG_ASSERT(res, "vaDestroySurfaces");
VLOG(1) << "destroyed surface " << id_;
}
......@@ -105,31 +130,73 @@ void SharedVASurface::SaveAsPNG(const std::string& path) {
VAImage image;
uint8_t* image_data;
GetSurfaceImage(va_.display(), id_, size_, &image, &image_data);
GetSurfaceImage(va_device_.display(), id_, va_rt_format_, size_, &image,
&image_data);
// Convert the NV12 image data to ARGB and write to |path|.
// Convert the image data to ARGB and write to |path|.
const size_t argb_stride = image.width * 4;
auto argb_data = std::make_unique<uint8_t[]>(argb_stride * image.height);
const int convert_res = libyuv::NV12ToARGB(
(uint8_t*)(image_data + image.offsets[0]), image.pitches[0],
(uint8_t*)(image_data + image.offsets[1]), image.pitches[1],
argb_data.get(), argb_stride, image.width, image.height);
DCHECK(convert_res == 0);
int convert_res = 0;
const uint32_t fourcc = image.format.fourcc;
DCHECK(fourcc == VA_FOURCC_NV12 || fourcc == VA_FOURCC_P010);
if (fourcc == VA_FOURCC_NV12) {
convert_res = libyuv::NV12ToARGB(image_data + image.offsets[0],
base::checked_cast<int>(image.pitches[0]),
image_data + image.offsets[1],
base::checked_cast<int>(image.pitches[1]),
argb_data.get(),
base::checked_cast<int>(argb_stride),
base::strict_cast<int>(image.width),
base::strict_cast<int>(image.height));
} else if (fourcc == VA_FOURCC_P010) {
LOG_ASSERT(image.width * 2 <= image.pitches[0]);
LOG_ASSERT(4 * ((image.width + 1) / 2) <= image.pitches[1]);
uint8_t* y_8b = image_data + image.offsets[0];
std::vector<uint16_t> y_plane;
for (uint32_t row = 0u; row < image.height; row++) {
for (uint32_t col = 0u; col < image.width * 2; col += 2) {
y_plane.push_back(JoinUint8(y_8b[col], y_8b[col + 1]));
}
y_8b += image.pitches[0];
}
// Split the interleaved UV plane.
uint8_t* uv_8b = image_data + image.offsets[1];
std::vector<uint16_t> u_plane, v_plane;
for (uint32_t row = 0u; row < (image.height + 1) / 2; row++) {
for (uint32_t col = 0u; col < 4 * ((image.width + 1) / 2); col += 4) {
u_plane.push_back(JoinUint8(uv_8b[col], uv_8b[col + 1]));
v_plane.push_back(JoinUint8(uv_8b[col + 2], uv_8b[col + 3]));
}
uv_8b += image.pitches[1];
}
convert_res = libyuv::H010ToARGB(
y_plane.data(), base::strict_cast<int>(image.width), u_plane.data(),
base::checked_cast<int>((image.width + 1) / 2), v_plane.data(),
base::checked_cast<int>((image.width + 1) / 2), argb_data.get(),
base::checked_cast<int>(argb_stride),
base::strict_cast<int>(image.width),
base::strict_cast<int>(image.height));
}
LOG_ASSERT(convert_res == 0) << "Failed to convert to ARGB";
std::vector<unsigned char> image_buffer;
const bool result = gfx::PNGCodec::Encode(
argb_data.get(), gfx::PNGCodec::FORMAT_BGRA, size_, argb_stride,
true /* discard_transparency */, std::vector<gfx::PNGCodec::Comment>(),
&image_buffer);
DCHECK(result);
LOG_ASSERT(result) << "Failed to encode to PNG";
LOG_ASSERT(base::WriteFile(base::FilePath(path), image_buffer));
// Clean up VA handles.
VAStatus res = vaUnmapBuffer(va_.display(), image.buf);
VAStatus res = vaUnmapBuffer(va_device_.display(), image.buf);
VA_LOG_ASSERT(res, "vaUnmapBuffer");
res = vaDestroyImage(va_.display(), image.image_id);
res = vaDestroyImage(va_device_.display(), image.image_id);
VA_LOG_ASSERT(res, "vaDestroyImage");
}
......
......@@ -14,30 +14,33 @@ namespace vaapi_test {
constexpr unsigned int kInvalidVaRtFormat = 0u;
// Provides a wrapper around VASurfaces that properly handles creation and
// Provides a wrapper around a VASurface that properly handles creation and
// destruction.
// The associated VaapiDevice must be guaranteed externally to be alive beyond
// the lifetime of the SharedVASurface.
class SharedVASurface : public base::RefCounted<SharedVASurface> {
public:
// Constructs a VASurface with given |size| and |attribute|.
static scoped_refptr<SharedVASurface> Create(const VaapiDevice& va,
static scoped_refptr<SharedVASurface> Create(const VaapiDevice& va_device,
unsigned int va_rt_format,
const gfx::Size& size,
VASurfaceAttrib attribute);
// Saves this surface into a png at the given |path|. The image data is
// retrieved by first attempting to call vaDeriveImage on the surface;
// if that fails or returns an unsupported format, fall back to
// vaCreateImage + vaGetImage with NV12.
// vaCreateImage + vaGetImage with NV12 or P010 as appropriate.
// NB: vaDeriveImage may succeed but fetch garbage output in AMD.
void SaveAsPNG(const std::string& path);
VASurfaceID id() const { return id_; }
const gfx::Size& size() const { return size_; }
unsigned int internal_va_format() const { return internal_va_format_; }
unsigned int va_rt_format() const { return va_rt_format_; }
private:
friend class base::RefCounted<SharedVASurface>;
SharedVASurface(const VaapiDevice& va,
SharedVASurface(const VaapiDevice& va_device,
VASurfaceID id,
const gfx::Size& size,
unsigned int format);
......@@ -46,10 +49,12 @@ class SharedVASurface : public base::RefCounted<SharedVASurface> {
SharedVASurface& operator=(const SharedVASurface&) = delete;
~SharedVASurface();
const VaapiDevice& va_;
// Non-owned.
const VaapiDevice& va_device_;
const VASurfaceID id_;
const gfx::Size size_;
const unsigned int internal_va_format_;
const unsigned int va_rt_format_;
};
} // namespace vaapi_test
......
......@@ -14,37 +14,7 @@
namespace media {
namespace vaapi_test {
// Returns the preferred VA_RT_FORMAT for the given |profile|.
unsigned int GetFormatForProfile(VAProfile profile) {
if (profile == VAProfileVP9Profile2)
return VA_RT_FORMAT_YUV420_10BPP;
return VA_RT_FORMAT_YUV420;
}
VaapiDevice::VaapiDevice(VAProfile profile)
: display_(nullptr),
config_id_(VA_INVALID_ID),
profile_(profile),
internal_va_format_(GetFormatForProfile(profile_)) {
Initialize();
}
VaapiDevice::~VaapiDevice() {
VLOG(1) << "Tearing down...";
VAStatus res;
if (config_id_ != VA_INVALID_ID) {
res = vaDestroyConfig(display_, config_id_);
VA_LOG_ASSERT(res, "vaDestroyConfig");
}
if (display_ != nullptr) {
res = vaTerminate(display_);
VA_LOG_ASSERT(res, "vaTerminate");
}
VLOG(1) << "Teardown done.";
}
void VaapiDevice::Initialize() {
VaapiDevice::VaapiDevice() : display_(nullptr) {
constexpr char kDriRenderNode0Path[] = "/dev/dri/renderD128";
display_file_ = base::File(
base::FilePath::FromUTF8Unsafe(kDriRenderNode0Path),
......@@ -56,21 +26,17 @@ void VaapiDevice::Initialize() {
LOG_ASSERT(display_ != nullptr) << "vaGetDisplayDRM failed";
int major, minor;
VAStatus res = vaInitialize(display_, &major, &minor);
const VAStatus res = vaInitialize(display_, &major, &minor);
VA_LOG_ASSERT(res, "vaInitialize");
VLOG(1) << "VA major version: " << major << ", minor version: " << minor;
}
// Create config.
// We rely on vaCreateConfig to specify the error mode if decode is not
// supported for the given profile.
// TODO(jchinlee): Refactor configuration management to be owned by decoders
// (this will also allow decoders to adjust the VAConfig as needed, e.g. if
// the profile changes part-way).
std::vector<VAConfigAttrib> attribs;
attribs.push_back({VAConfigAttribRTFormat, internal_va_format_});
res = vaCreateConfig(display_, profile_, VAEntrypointVLD, attribs.data(),
attribs.size(), &config_id_);
VA_LOG_ASSERT(res, "vaCreateConfig");
VaapiDevice::~VaapiDevice() {
VLOG(1) << "Tearing down...";
const VAStatus res = vaTerminate(display_);
VA_LOG_ASSERT(res, "vaTerminate");
VLOG(1) << "Teardown done.";
}
} // namespace vaapi_test
......
......@@ -10,35 +10,23 @@
namespace media {
namespace vaapi_test {
// This class holds shared VA handles used by the various test decoders.
// The decoders themselves may still make direct libva calls.
// This class manages the lifetime of a VADisplay in a RAII fashion from
// vaGetDisplayDRM() to vaTerminate(). Decoders may use the display() method to
// access the VADisplay and issue direct libva calls.
class VaapiDevice {
public:
// Initializes a VaapiDevice for |profile|. Success is ASSERTed.
explicit VaapiDevice(VAProfile profile);
// Initializes the VADisplay. Success is ASSERTed.
VaapiDevice();
VaapiDevice(const VaapiDevice&) = delete;
VaapiDevice& operator=(const VaapiDevice&) = delete;
~VaapiDevice();
VADisplay display() const { return display_; }
VAConfigID config_id() const { return config_id_; }
VAProfile profile() const { return profile_; }
unsigned int internal_va_format() const { return internal_va_format_; }
private:
// Initializes VA handles and display descriptors, checking that HW decode
// with the expected profile is supported. Success is ASSERTed.
void Initialize();
base::File display_file_;
// VA info and handles
// Populated on Initialize().
VADisplay display_;
VAConfigID config_id_;
const VAProfile profile_;
const unsigned int internal_va_format_;
};
} // namespace vaapi_test
......
......@@ -11,20 +11,58 @@
namespace media {
namespace vaapi_test {
namespace {
// Returns the VAProfile from |frame_hdr|.
VAProfile GetProfile(Vp9FrameHeader frame_hdr) {
switch (frame_hdr.profile) {
case 0:
return VAProfileVP9Profile0;
case 1:
break;
case 2:
LOG_ASSERT(frame_hdr.bit_depth == 10)
<< "Only 10-bit streams are supported for VP9 profile 2";
return VAProfileVP9Profile2;
case 3:
break;
default:
break;
}
LOG_ASSERT(false) << "Unsupported VP9 profile " << frame_hdr.profile;
return VAProfileNone;
}
// Returns the preferred VA_RT_FORMAT for the given |profile|.
// TODO(jchinlee): Have format dependent on bit depth, not profile.
unsigned int GetFormatForProfile(VAProfile profile) {
if (profile == VAProfileVP9Profile2 || profile == VAProfileVP9Profile3)
return VA_RT_FORMAT_YUV420_10;
return VA_RT_FORMAT_YUV420;
}
} // namespace
Vp9Decoder::Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser,
const VaapiDevice& va)
const VaapiDevice& va_device)
: ivf_parser_(std::move(ivf_parser)),
va_(va),
context_id_(VA_INVALID_ID),
va_device_(va_device),
vp9_parser_(
std::make_unique<Vp9Parser>(/*parsing_compressed_header=*/false)),
ref_frames_(kVp9NumRefFrames) {}
Vp9Decoder::~Vp9Decoder() {
if (context_id_ != VA_INVALID_ID) {
VAStatus res = vaDestroyContext(va_.display(), context_id_);
VA_LOG_ASSERT(res, "vaDestroyContext");
}
// We destroy the VA handles explicitly to ensure the correct order.
// The configuration must be destroyed after the context so that the
// configuration reference remains valid in the context, and surfaces can only
// be destroyed after the context as per
// https://github.com/intel/libva/blob/8c6126e67c446f4c7808cb51b609077e4b9bd8fe/va/va.h#L1549
va_context_.reset();
va_config_.reset();
ref_frames_.clear();
last_decoded_surface_.reset();
}
Vp9Parser::Result Vp9Decoder::ReadNextFrame(Vp9FrameHeader& vp9_frame_header,
......@@ -80,14 +118,19 @@ VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
return VideoDecoder::kOk;
}
// Create context for decode.
VAStatus res;
if (context_id_ == VA_INVALID_ID) {
res = vaCreateContext(va_.display(), va_.config_id(), size.width(),
size.height(), VA_PROGRESSIVE,
/*render_targets=*/nullptr,
/*num_render_targets=*/0, &context_id_);
VA_LOG_ASSERT(res, "vaCreateContext");
const VAProfile profile = GetProfile(frame_hdr);
// Note: some streams may fail to decode; see
// https://source.chromium.org/chromium/chromium/src/+/master:media/gpu/vp9_decoder.cc;l=249-285;drc=3893688a88eb1b4cf39e346fd8f8c743ad255469
if (!va_config_ || va_config_->profile() != profile) {
va_context_.reset();
va_config_ = std::make_unique<ScopedVAConfig>(va_device_, profile,
GetFormatForProfile(profile));
}
// [Re]create context for decode.
if (!va_context_ || va_context_->size() != size) {
va_context_ =
std::make_unique<ScopedVAContext>(va_device_, *va_config_, size);
}
// Create surfaces for decode.
......@@ -96,8 +139,8 @@ VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
attribute.flags = VA_SURFACE_ATTRIB_SETTABLE;
attribute.value.type = VAGenericValueTypeInteger;
attribute.value.value.i = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER;
scoped_refptr<SharedVASurface> surface =
SharedVASurface::Create(va_, size, attribute);
scoped_refptr<SharedVASurface> surface = SharedVASurface::Create(
va_device_, va_config_->va_rt_format(), size, attribute);
const Vp9Parser::Context& context = vp9_parser_->context();
const Vp9SegmentationParams& seg = context.segmentation();
......@@ -162,9 +205,9 @@ VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
std::vector<VABufferID> buffers;
VABufferID buffer_id;
res = vaCreateBuffer(va_.display(), context_id_, VAPictureParameterBufferType,
sizeof(VADecPictureParameterBufferVP9), 1u, &pic_param,
&buffer_id);
VAStatus res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VAPictureParameterBufferType,
sizeof(VADecPictureParameterBufferVP9), 1u, &pic_param, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
......@@ -194,27 +237,27 @@ VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
seg_param.chroma_ac_quant_scale = seg.uv_dequant[i][1];
}
res = vaCreateBuffer(va_.display(), context_id_, VASliceParameterBufferType,
sizeof(VASliceParameterBufferVP9), 1u, &slice_param,
&buffer_id);
res = vaCreateBuffer(
va_device_.display(), va_context_->id(), VASliceParameterBufferType,
sizeof(VASliceParameterBufferVP9), 1u, &slice_param, &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
// Set up buffer for frame header.
res = vaCreateBuffer(va_.display(), context_id_, VASliceDataBufferType,
frame_hdr.frame_size, 1u,
res = vaCreateBuffer(va_device_.display(), va_context_->id(),
VASliceDataBufferType, frame_hdr.frame_size, 1u,
const_cast<uint8_t*>(frame_hdr.data), &buffer_id);
VA_LOG_ASSERT(res, "vaCreateBuffer");
buffers.push_back(buffer_id);
res = vaBeginPicture(va_.display(), context_id_, surface->id());
res = vaBeginPicture(va_device_.display(), va_context_->id(), surface->id());
VA_LOG_ASSERT(res, "vaBeginPicture");
res = vaRenderPicture(va_.display(), context_id_, buffers.data(),
res = vaRenderPicture(va_device_.display(), va_context_->id(), buffers.data(),
buffers.size());
VA_LOG_ASSERT(res, "vaRenderPicture");
res = vaEndPicture(va_.display(), context_id_);
res = vaEndPicture(va_device_.display(), va_context_->id());
VA_LOG_ASSERT(res, "vaEndPicture");
last_decoded_surface_ = surface;
......@@ -222,7 +265,7 @@ VideoDecoder::Result Vp9Decoder::DecodeNextFrame() {
RefreshReferenceSlots(frame_hdr.refresh_frame_flags, surface);
for (auto id : buffers)
vaDestroyBuffer(va_.display(), id);
vaDestroyBuffer(va_device_.display(), id);
buffers.clear();
return VideoDecoder::kOk;
......
......@@ -7,6 +7,8 @@
#include "media/filters/ivf_parser.h"
#include "media/filters/vp9_parser.h"
#include "media/gpu/vaapi/test/scoped_va_config.h"
#include "media/gpu/vaapi/test/scoped_va_context.h"
#include "media/gpu/vaapi/test/shared_va_surface.h"
#include "media/gpu/vaapi/test/vaapi_device.h"
#include "media/gpu/vaapi/test/video_decoder.h"
......@@ -17,7 +19,8 @@ namespace vaapi_test {
// A Vp9Decoder decodes VP9-encoded IVF streams using direct libva calls.
class Vp9Decoder : public VideoDecoder {
public:
Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser, const VaapiDevice& va);
Vp9Decoder(std::unique_ptr<IvfParser> ivf_parser,
const VaapiDevice& va_device);
Vp9Decoder(const Vp9Decoder&) = delete;
Vp9Decoder& operator=(const Vp9Decoder&) = delete;
~Vp9Decoder() override;
......@@ -41,8 +44,9 @@ class Vp9Decoder : public VideoDecoder {
const std::unique_ptr<IvfParser> ivf_parser_;
// VA handles.
const VaapiDevice& va_;
VAContextID context_id_;
const VaapiDevice& va_device_;
std::unique_ptr<ScopedVAConfig> va_config_;
std::unique_ptr<ScopedVAContext> va_context_;
scoped_refptr<SharedVASurface> last_decoded_surface_;
// VP9-specific data.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment