blob: 2130b6a6134d38b15fb60d72f4acf3c67136487e [file] [log] [blame]
// Copyright 2015 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 <stddef.h>
#include <stdint.h>
#include <va/va.h>
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
// This has to be included first.
// See http://code.google.com/p/googletest/issues/detail?id=371
#include "testing/gtest/include/gtest/gtest.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_util.h"
#include "media/base/video_types.h"
#include "media/gpu/test/local_gpu_memory_buffer_manager.h"
#include "media/gpu/vaapi/test_utils.h"
#include "media/gpu/vaapi/va_surface.h"
#include "media/gpu/vaapi/vaapi_image_decoder.h"
#include "media/gpu/vaapi/vaapi_image_decoder_test_common.h"
#include "media/gpu/vaapi/vaapi_jpeg_decoder.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "media/parsers/jpeg_parser.h"
#include "third_party/libyuv/include/libyuv.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#include "third_party/skia/include/core/SkPixmap.h"
#include "third_party/skia/include/encode/SkJpegEncoder.h"
#include "ui/gfx/buffer_format_util.h"
#include "ui/gfx/buffer_types.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_memory_buffer.h"
#include "ui/gfx/linux/native_pixmap_dmabuf.h"
#include "ui/gfx/native_pixmap_handle.h"
namespace media {
namespace {
using DecodedImagePtr = std::unique_ptr<vaapi_test_utils::DecodedImage>;
constexpr const char* kYuv422Filename = "pixel-1280x720.jpg";
constexpr const char* kYuv420Filename = "pixel-1280x720-yuv420.jpg";
constexpr const char* kYuv444Filename = "pixel-1280x720-yuv444.jpg";
constexpr const char* kOddHeightImageFilename = "pixel-40x23-yuv420.jpg";
constexpr const char* kOddWidthImageFilename = "pixel-41x22-yuv420.jpg";
constexpr const char* kOddDimensionsImageFilename = "pixel-41x23-yuv420.jpg";
const vaapi_test_utils::TestParam kVAImageTestCases[] = {
{"YUV422", kYuv422Filename},
{"YUV420", kYuv420Filename},
{"YUV444", kYuv444Filename},
};
const vaapi_test_utils::TestParam kDmaBufTestCases[] = {
{"YUV420", kYuv420Filename},
{"OddHeightImage40x23", kOddHeightImageFilename},
{"OddWidthImage41x22", kOddWidthImageFilename},
{"OddDimensionsImage41x23", kOddDimensionsImageFilename},
};
constexpr double kMinSsim = 0.995;
// This file is not supported by the VAAPI, so we don't define expectations on
// the decode result.
constexpr const char* kUnsupportedFilename = "pixel-1280x720-grayscale.jpg";
// The size of the minimum coded unit for a YUV 4:2:0 image (both the width and
// the height of the MCU are the same for 4:2:0).
constexpr int k420MCUSize = 16;
// The largest maximum supported surface size we expect a driver to report for
// JPEG decoding.
constexpr gfx::Size kLargestSupportedSize(16 * 1024, 16 * 1024);
// Decodes the given |encoded_image| using libyuv and returns the result as a
// DecodedImage object. The decoded planes will be stored in |dest_*|.
// Note however, that this function does not takes ownership of |dest_*| or
// manage their memory.
DecodedImagePtr GetSwDecode(base::span<const uint8_t> encoded_image,
std::vector<uint8_t>* dest_y,
std::vector<uint8_t>* dest_u,
std::vector<uint8_t>* dest_v) {
DCHECK(dest_y && dest_u && dest_v);
JpegParseResult parse_result;
const bool result = ParseJpegPicture(encoded_image.data(),
encoded_image.size(), &parse_result);
if (!result)
return nullptr;
const gfx::Size jpeg_size(
base::strict_cast<int>(parse_result.frame_header.visible_width),
base::strict_cast<int>(parse_result.frame_header.visible_height));
if (jpeg_size.IsEmpty())
return nullptr;
const gfx::Size half_jpeg_size((jpeg_size.width() + 1) / 2,
(jpeg_size.height() + 1) / 2);
dest_y->resize(jpeg_size.GetArea());
dest_u->resize(half_jpeg_size.GetArea());
dest_v->resize(half_jpeg_size.GetArea());
if (libyuv::ConvertToI420(
encoded_image.data(), encoded_image.size(), dest_y->data(),
jpeg_size.width(), dest_u->data(), half_jpeg_size.width(),
dest_v->data(), half_jpeg_size.width(), 0, 0, jpeg_size.width(),
jpeg_size.height(), jpeg_size.width(), jpeg_size.height(),
libyuv::kRotate0, libyuv::FOURCC_MJPG) != 0) {
return nullptr;
}
auto sw_decoded_jpeg = std::make_unique<vaapi_test_utils::DecodedImage>();
sw_decoded_jpeg->fourcc = VA_FOURCC_I420;
sw_decoded_jpeg->number_of_planes = 3u;
sw_decoded_jpeg->size = jpeg_size;
sw_decoded_jpeg->planes[0].data = dest_y->data();
sw_decoded_jpeg->planes[0].stride = jpeg_size.width();
sw_decoded_jpeg->planes[1].data = dest_u->data();
sw_decoded_jpeg->planes[1].stride = half_jpeg_size.width();
sw_decoded_jpeg->planes[2].data = dest_v->data();
sw_decoded_jpeg->planes[2].stride = half_jpeg_size.width();
return sw_decoded_jpeg;
}
// Generates a checkerboard pattern as a JPEG image of a specified |size| and
// |subsampling| format. Returns an empty vector on failure.
std::vector<unsigned char> GenerateJpegImage(
const gfx::Size& size,
SkJpegEncoder::Downsample subsampling = SkJpegEncoder::Downsample::k420) {
DCHECK(!size.IsEmpty());
// First build a raw RGBA image of the given size with a checkerboard pattern.
const SkImageInfo image_info = SkImageInfo::Make(
size.width(), size.height(), SkColorType::kRGBA_8888_SkColorType,
SkAlphaType::kOpaque_SkAlphaType);
const size_t byte_size = image_info.computeMinByteSize();
if (byte_size == SIZE_MAX)
return {};
const size_t stride = image_info.minRowBytes();
DCHECK_EQ(4, SkColorTypeBytesPerPixel(image_info.colorType()));
DCHECK_EQ(4 * size.width(), base::checked_cast<int>(stride));
constexpr gfx::Size kCheckerRectSize(3, 5);
std::vector<uint8_t> rgba_data(byte_size);
uint8_t* data = rgba_data.data();
for (int y = 0; y < size.height(); y++) {
const bool y_bit = (((y / kCheckerRectSize.height()) & 0x1) == 0);
for (int x = 0; x < base::checked_cast<int>(stride); x += 4) {
const bool x_bit = (((x / kCheckerRectSize.width()) & 0x1) == 0);
const SkColor color = (x_bit != y_bit) ? SK_ColorBLUE : SK_ColorMAGENTA;
data[x + 0] = SkColorGetR(color);
data[x + 1] = SkColorGetG(color);
data[x + 2] = SkColorGetB(color);
data[x + 3] = SkColorGetA(color);
}
data += stride;
}
// Now, encode it as a JPEG.
//
// TODO(andrescj): if this generates a large enough image (in terms of byte
// size), it will be decoded incorrectly in AMD Stoney Ridge (see
// b/127874877). When that's resolved, change the quality here to 100 so that
// the generated JPEG is large.
std::vector<unsigned char> jpeg_data;
if (gfx::JPEGCodec::Encode(
SkPixmap(image_info, rgba_data.data(), stride) /* input */,
95 /* quality */, subsampling /* downsample */,
&jpeg_data /* output */)) {
return jpeg_data;
}
return {};
}
// Rounds |n| to the greatest multiple of |m| that is less than or equal to |n|.
int RoundDownToMultiple(int n, int m) {
DCHECK_GE(n, 0);
DCHECK_GT(m, 0);
return (n / m) * m;
}
// Rounds |n| to the smallest multiple of |m| that is greater than or equal to
// |n|.
int RoundUpToMultiple(int n, int m) {
DCHECK_GE(n, 0);
DCHECK_GT(m, 0);
if (n % m == 0)
return n;
base::CheckedNumeric<int> safe_n(n);
safe_n += m;
return RoundDownToMultiple(safe_n.ValueOrDie(), m);
}
// Given a minimum supported surface dimension (width or height) value
// |min_surface_supported|, this function returns a non-zero coded dimension of
// a 4:2:0 JPEG image that would not be supported because the dimension is right
// below the supported value. For example, if |min_surface_supported| is 19,
// this function should return 16 because for a 4:2:0 image, both coded
// dimensions should be multiples of 16. If an unsupported dimension was found
// (i.e., |min_surface_supported| > 16), this function returns true, false
// otherwise.
bool GetMinUnsupportedDimension(int min_surface_supported,
int* min_unsupported) {
if (min_surface_supported <= k420MCUSize)
return false;
*min_unsupported =
RoundDownToMultiple(min_surface_supported - 1, k420MCUSize);
return true;
}
// Given a minimum supported surface dimension (width or height) value
// |min_surface_supported|, this function returns a non-zero coded dimension of
// a 4:2:0 JPEG image that would be supported because the dimension is at least
// the minimum. For example, if |min_surface_supported| is 35, this function
// should return 48 because for a 4:2:0 image, both coded dimensions should be
// multiples of 16.
int GetMinSupportedDimension(int min_surface_supported) {
LOG_ASSERT(min_surface_supported > 0);
return RoundUpToMultiple(min_surface_supported, k420MCUSize);
}
// Given a maximum supported surface dimension (width or height) value
// |max_surface_supported|, this function returns the coded dimension of a 4:2:0
// JPEG image that would be supported because the dimension is at most the
// maximum. For example, if |max_surface_supported| is 65, this function
// should return 64 because for a 4:2:0 image, both coded dimensions should be
// multiples of 16.
int GetMaxSupportedDimension(int max_surface_supported) {
return RoundDownToMultiple(max_surface_supported, k420MCUSize);
}
} // namespace
class VaapiJpegDecoderTest : public VaapiImageDecoderTestCommon {
protected:
VaapiJpegDecoderTest()
: VaapiImageDecoderTestCommon(std::make_unique<VaapiJpegDecoder>()) {}
std::unique_ptr<ScopedVAImage> Decode(
base::span<const uint8_t> encoded_image,
uint32_t preferred_fourcc,
VaapiImageDecodeStatus* status = nullptr);
std::unique_ptr<ScopedVAImage> Decode(
base::span<const uint8_t> encoded_image,
VaapiImageDecodeStatus* status = nullptr);
};
std::unique_ptr<ScopedVAImage> VaapiJpegDecoderTest::Decode(
base::span<const uint8_t> encoded_image,
uint32_t preferred_fourcc,
VaapiImageDecodeStatus* status) {
const VaapiImageDecodeStatus decode_status = Decoder()->Decode(encoded_image);
EXPECT_EQ(!!Decoder()->GetScopedVASurface(),
decode_status == VaapiImageDecodeStatus::kSuccess);
// Still try to get image when decode fails.
VaapiImageDecodeStatus image_status;
std::unique_ptr<ScopedVAImage> scoped_image;
scoped_image = static_cast<VaapiJpegDecoder*>(Decoder())->GetImage(
preferred_fourcc, &image_status);
EXPECT_EQ(!!scoped_image, image_status == VaapiImageDecodeStatus::kSuccess);
// Return the first fail status.
if (status) {
*status = decode_status != VaapiImageDecodeStatus::kSuccess ? decode_status
: image_status;
}
return scoped_image;
}
std::unique_ptr<ScopedVAImage> VaapiJpegDecoderTest::Decode(
base::span<const uint8_t> encoded_image,
VaapiImageDecodeStatus* status) {
return Decode(encoded_image, VA_FOURCC_I420, status);
}
// The intention of this test is to ensure that the workarounds added in
// VaapiWrapper::GetJpegDecodeSuitableImageFourCC() don't result in an
// unsupported image format.
TEST_F(VaapiJpegDecoderTest, MinimalImageFormatSupport) {
// All drivers should support at least I420.
VAImageFormat i420_format{};
i420_format.fourcc = VA_FOURCC_I420;
ASSERT_TRUE(VaapiWrapper::IsImageFormatSupported(i420_format));
// Additionally, the mesa VAAPI driver should support YV12, NV12 and YUYV.
ASSERT_NE(VAImplementation::kInvalid, VaapiWrapper::GetImplementationType());
if (VaapiWrapper::GetImplementationType() == VAImplementation::kMesaGallium) {
VAImageFormat yv12_format{};
yv12_format.fourcc = VA_FOURCC_YV12;
ASSERT_TRUE(VaapiWrapper::IsImageFormatSupported(yv12_format));
VAImageFormat nv12_format{};
nv12_format.fourcc = VA_FOURCC_NV12;
ASSERT_TRUE(VaapiWrapper::IsImageFormatSupported(nv12_format));
VAImageFormat yuyv_format{};
yuyv_format.fourcc = VA_FOURCC('Y', 'U', 'Y', 'V');
ASSERT_TRUE(VaapiWrapper::IsImageFormatSupported(yuyv_format));
}
}
TEST_P(VaapiJpegDecoderTest, DecodeSucceeds) {
base::FilePath input_file = FindTestDataFilePath(GetParam().filename);
std::string jpeg_data;
ASSERT_TRUE(base::ReadFileToString(input_file, &jpeg_data))
<< "failed to read input data from " << input_file.value();
const auto encoded_image = base::as_bytes(base::make_span(jpeg_data));
// Skip the image if the VAAPI driver doesn't claim to support its chroma
// subsampling format. However, we expect at least 4:2:0 and 4:2:2 support.
const VaapiWrapper::InternalFormats supported_internal_formats =
VaapiWrapper::GetDecodeSupportedInternalFormats(VAProfileJPEGBaseline);
ASSERT_TRUE(supported_internal_formats.yuv420);
ASSERT_TRUE(supported_internal_formats.yuv422);
JpegParseResult parse_result;
ASSERT_TRUE(ParseJpegPicture(encoded_image.data(), encoded_image.size(),
&parse_result));
const unsigned int rt_format =
VaSurfaceFormatForJpeg(parse_result.frame_header);
ASSERT_NE(kInvalidVaRtFormat, rt_format);
if (!VaapiWrapper::IsDecodingSupportedForInternalFormat(VAProfileJPEGBaseline,
rt_format)) {
GTEST_SKIP();
}
// Note that this test together with
// VaapiJpegDecoderTest.MinimalImageFormatSupport gives us two guarantees:
//
// 1) Every combination of supported internal format (returned by
// GetJpegDecodeSupportedInternalFormats()) and supported image format
// works with vaGetImage() (for JPEG decoding).
//
// 2) The FOURCC returned by VaapiWrapper::GetJpegDecodeSuitableImageFourCC()
// corresponds to a supported image format.
//
// Note that we expect VA_FOURCC_I420 and VA_FOURCC_NV12 support in all
// drivers.
const std::vector<VAImageFormat>& supported_image_formats =
VaapiWrapper::GetSupportedImageFormatsForTesting();
EXPECT_GE(supported_image_formats.size(), 2u);
VAImageFormat i420_format{};
i420_format.fourcc = VA_FOURCC_I420;
EXPECT_TRUE(VaapiWrapper::IsImageFormatSupported(i420_format));
VAImageFormat nv12_format{};
nv12_format.fourcc = VA_FOURCC_NV12;
EXPECT_TRUE(VaapiWrapper::IsImageFormatSupported(nv12_format));
// Decode the image using libyuv. Using |temp_*| for resource management.
std::vector<uint8_t> temp_y;
std::vector<uint8_t> temp_u;
std::vector<uint8_t> temp_v;
DecodedImagePtr sw_decoded_jpeg =
GetSwDecode(encoded_image, &temp_y, &temp_u, &temp_v);
ASSERT_TRUE(sw_decoded_jpeg);
// Now run the comparison between the sw and hw image decodes for the
// supported formats.
for (const auto& image_format : supported_image_formats) {
std::unique_ptr<ScopedVAImage> scoped_image =
Decode(encoded_image, image_format.fourcc);
ASSERT_TRUE(scoped_image);
ASSERT_TRUE(Decoder()->GetScopedVASurface());
EXPECT_TRUE(Decoder()->GetScopedVASurface()->IsValid());
EXPECT_EQ(Decoder()->GetScopedVASurface()->size().width(),
base::strict_cast<int>(parse_result.frame_header.visible_width));
EXPECT_EQ(Decoder()->GetScopedVASurface()->size().height(),
base::strict_cast<int>(parse_result.frame_header.visible_height));
EXPECT_EQ(rt_format, Decoder()->GetScopedVASurface()->format());
const uint32_t actual_fourcc = scoped_image->image()->format.fourcc;
// TODO(andrescj): CompareImages() only supports I420, NV12, YUY2, and YUYV.
// Make it support all the image formats we expect and call it
// unconditionally.
if (actual_fourcc == VA_FOURCC_I420 || actual_fourcc == VA_FOURCC_NV12 ||
actual_fourcc == VA_FOURCC_YUY2 ||
actual_fourcc == VA_FOURCC('Y', 'U', 'Y', 'V')) {
ASSERT_TRUE(vaapi_test_utils::CompareImages(
*sw_decoded_jpeg,
vaapi_test_utils::ScopedVAImageToDecodedImage(scoped_image.get()),
kMinSsim));
}
DVLOG(1) << "Got a " << FourccToString(scoped_image->image()->format.fourcc)
<< " VAImage (preferred " << FourccToString(image_format.fourcc)
<< ")";
}
}
// Make sure that JPEGs whose size is in the supported size range are decoded
// successfully.
//
// TODO(andrescj): for now, this assumes 4:2:0. Handle other formats.
// TODO(andrescj): consider recreating the decoder for every size so that no
// state is retained.
TEST_F(VaapiJpegDecoderTest, DecodeSucceedsForSupportedSizes) {
gfx::Size min_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMinResolution(VAProfileJPEGBaseline,
&min_supported_size));
gfx::Size max_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMaxResolution(VAProfileJPEGBaseline,
&max_supported_size));
// Ensure the maximum supported size is reasonable.
ASSERT_GE(max_supported_size.width(), min_supported_size.width());
ASSERT_GE(max_supported_size.height(), min_supported_size.height());
ASSERT_LE(max_supported_size.width(), kLargestSupportedSize.width());
ASSERT_LE(max_supported_size.height(), kLargestSupportedSize.height());
// The actual image min/max coded size depends on the subsampling format. For
// example, for 4:2:0, the coded dimensions must be multiples of 16. So, if
// the minimum surface size is, e.g., 18x18, the minimum image coded size is
// 32x32. Get those actual min/max coded sizes now.
const int min_width = GetMinSupportedDimension(min_supported_size.width());
const int min_height = GetMinSupportedDimension(min_supported_size.height());
const int max_width = GetMaxSupportedDimension(max_supported_size.width());
const int max_height = GetMaxSupportedDimension(max_supported_size.height());
ASSERT_GT(max_width, 0);
ASSERT_GT(max_height, 0);
const std::vector<gfx::Size> test_sizes = {{min_width, min_height},
{min_width, max_height},
{max_width, min_height},
{max_width, max_height}};
for (const auto& test_size : test_sizes) {
const std::vector<unsigned char> jpeg_data = GenerateJpegImage(test_size);
auto jpeg_data_span = base::as_bytes(base::make_span(jpeg_data));
ASSERT_FALSE(jpeg_data.empty());
std::unique_ptr<ScopedVAImage> scoped_image = Decode(jpeg_data_span);
ASSERT_TRUE(scoped_image)
<< "Decode unexpectedly failed for size = " << test_size.ToString();
ASSERT_TRUE(Decoder()->GetScopedVASurface());
EXPECT_TRUE(Decoder()->GetScopedVASurface()->IsValid());
// Decode the image using libyuv. Using |temp_*| for resource management.
std::vector<uint8_t> temp_y;
std::vector<uint8_t> temp_u;
std::vector<uint8_t> temp_v;
DecodedImagePtr sw_decoded_jpeg =
GetSwDecode(jpeg_data_span, &temp_y, &temp_u, &temp_v);
ASSERT_TRUE(sw_decoded_jpeg);
EXPECT_TRUE(vaapi_test_utils::CompareImages(
*sw_decoded_jpeg,
vaapi_test_utils::ScopedVAImageToDecodedImage(scoped_image.get()),
kMinSsim))
<< "The SSIM check unexpectedly failed for size = "
<< test_size.ToString();
}
}
class VaapiJpegDecoderWithDmaBufsTest : public VaapiJpegDecoderTest {
public:
VaapiJpegDecoderWithDmaBufsTest() = default;
~VaapiJpegDecoderWithDmaBufsTest() override = default;
};
// TODO(andrescj): test other JPEG formats besides YUV 4:2:0.
TEST_P(VaapiJpegDecoderWithDmaBufsTest, DecodeSucceeds) {
ASSERT_NE(VAImplementation::kInvalid, VaapiWrapper::GetImplementationType());
if (VaapiWrapper::GetImplementationType() == VAImplementation::kMesaGallium) {
// TODO(crbug.com/974438): until we support surfaces with multiple buffer
// objects, the AMD driver fails this test.
GTEST_SKIP();
}
base::FilePath input_file = FindTestDataFilePath(GetParam().filename);
std::string jpeg_data;
ASSERT_TRUE(base::ReadFileToString(input_file, &jpeg_data))
<< "failed to read input data from " << input_file.value();
const auto encoded_image = base::as_bytes(base::make_span(jpeg_data));
// Decode into a VAAPI-allocated surface.
const VaapiImageDecodeStatus decode_status = Decoder()->Decode(encoded_image);
EXPECT_EQ(VaapiImageDecodeStatus::kSuccess, decode_status);
ASSERT_TRUE(Decoder()->GetScopedVASurface());
const gfx::Size va_surface_visible_size =
Decoder()->GetScopedVASurface()->size();
// The size stored in the ScopedVASurface should be the visible size of the
// JPEG.
JpegParseResult parse_result;
ASSERT_TRUE(ParseJpegPicture(encoded_image.data(), encoded_image.size(),
&parse_result));
EXPECT_EQ(gfx::Size(parse_result.frame_header.visible_width,
parse_result.frame_header.visible_height),
va_surface_visible_size);
// Export the surface.
VaapiImageDecodeStatus export_status = VaapiImageDecodeStatus::kInvalidState;
std::unique_ptr<NativePixmapAndSizeInfo> exported_pixmap =
Decoder()->ExportAsNativePixmapDmaBuf(&export_status);
EXPECT_EQ(VaapiImageDecodeStatus::kSuccess, export_status);
ASSERT_TRUE(exported_pixmap);
ASSERT_TRUE(exported_pixmap->pixmap);
EXPECT_FALSE(Decoder()->GetScopedVASurface());
// For JPEG decoding, the size of the surface we request is the coded size of
// the JPEG. Make sure the surface contains that coded area.
EXPECT_TRUE(gfx::Rect(exported_pixmap->va_surface_resolution)
.Contains(gfx::Rect(parse_result.frame_header.coded_width,
parse_result.frame_header.coded_height)));
// Make sure the visible area is contained by the surface.
EXPECT_EQ(va_surface_visible_size, exported_pixmap->pixmap->GetBufferSize());
EXPECT_FALSE(exported_pixmap->va_surface_resolution.IsEmpty());
EXPECT_FALSE(exported_pixmap->pixmap->GetBufferSize().IsEmpty());
ASSERT_TRUE(
gfx::Rect(exported_pixmap->va_surface_resolution)
.Contains(gfx::Rect(exported_pixmap->pixmap->GetBufferSize())));
// TODO(andrescj): we could get a better lower bound based on the dimensions
// and the format.
ASSERT_GT(exported_pixmap->byte_size, 0u);
// After exporting the surface, we should not be able to obtain a VAImage with
// the decoded data.
VAImageFormat i420_format{};
i420_format.fourcc = VA_FOURCC_I420;
EXPECT_TRUE(VaapiWrapper::IsImageFormatSupported(i420_format));
VaapiImageDecodeStatus image_status = VaapiImageDecodeStatus::kSuccess;
EXPECT_FALSE(static_cast<VaapiJpegDecoder*>(Decoder())->GetImage(
i420_format.fourcc, &image_status));
EXPECT_EQ(VaapiImageDecodeStatus::kInvalidState, image_status);
// Workaround: in order to import and map the pixmap using minigbm when the
// format is gfx::BufferFormat::YVU_420, we need to reorder the planes so that
// the offsets are in increasing order as assumed in https://bit.ly/2NLubNN.
// Otherwise, we get a validation error. In essence, we're making minigbm
// think that it is mapping a YVU_420, but it's actually mapping a YUV_420.
//
// TODO(andrescj): revisit this once crrev.com/c/1573718 lands.
gfx::NativePixmapHandle handle = exported_pixmap->pixmap->ExportHandle();
ASSERT_EQ(gfx::NumberOfPlanesForLinearBufferFormat(
exported_pixmap->pixmap->GetBufferFormat()),
handle.planes.size());
if (exported_pixmap->pixmap->GetBufferFormat() == gfx::BufferFormat::YVU_420)
std::swap(handle.planes[1], handle.planes[2]);
LocalGpuMemoryBufferManager gpu_memory_buffer_manager;
std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer =
gpu_memory_buffer_manager.ImportDmaBuf(
handle, exported_pixmap->pixmap->GetBufferSize(),
exported_pixmap->pixmap->GetBufferFormat());
ASSERT_TRUE(gpu_memory_buffer);
ASSERT_TRUE(gpu_memory_buffer->Map());
vaapi_test_utils::DecodedImage decoded_image{};
const gfx::BufferFormat format = gpu_memory_buffer->GetFormat();
if (format == gfx::BufferFormat::YVU_420) {
decoded_image.fourcc = VA_FOURCC_I420;
decoded_image.number_of_planes = 3u;
} else if (format == gfx::BufferFormat::YUV_420_BIPLANAR) {
decoded_image.fourcc = VA_FOURCC_NV12;
decoded_image.number_of_planes = 2u;
} else {
ASSERT_TRUE(false) << "Unsupported format "
<< gfx::BufferFormatToString(format);
}
decoded_image.size = gpu_memory_buffer->GetSize();
for (size_t plane = 0u;
plane < base::strict_cast<size_t>(decoded_image.number_of_planes);
plane++) {
decoded_image.planes[plane].data =
static_cast<uint8_t*>(gpu_memory_buffer->memory(plane));
decoded_image.planes[plane].stride = gpu_memory_buffer->stride(plane);
}
// Decode the image using libyuv. Using |temp_*| for resource management.
std::vector<uint8_t> temp_y;
std::vector<uint8_t> temp_u;
std::vector<uint8_t> temp_v;
DecodedImagePtr sw_decoded_jpeg =
GetSwDecode(encoded_image, &temp_y, &temp_u, &temp_v);
ASSERT_TRUE(sw_decoded_jpeg);
EXPECT_TRUE(vaapi_test_utils::CompareImages(*sw_decoded_jpeg, decoded_image,
kMinSsim));
gpu_memory_buffer->Unmap();
}
// Make sure that JPEGs whose size is below the supported size range are
// rejected.
//
// TODO(andrescj): for now, this assumes 4:2:0. Handle other formats.
TEST_F(VaapiJpegDecoderTest, DecodeFailsForBelowMinSize) {
gfx::Size min_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMinResolution(VAProfileJPEGBaseline,
&min_supported_size));
gfx::Size max_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMaxResolution(VAProfileJPEGBaseline,
&max_supported_size));
// Ensure the maximum supported size is reasonable.
ASSERT_GE(max_supported_size.width(), min_supported_size.width());
ASSERT_GE(max_supported_size.height(), min_supported_size.height());
ASSERT_LE(max_supported_size.width(), kLargestSupportedSize.width());
ASSERT_LE(max_supported_size.height(), kLargestSupportedSize.height());
// Get good (supported) minimum dimensions.
const int good_width = GetMinSupportedDimension(min_supported_size.width());
ASSERT_LE(good_width, max_supported_size.width());
const int good_height = GetMinSupportedDimension(min_supported_size.height());
ASSERT_LE(good_height, max_supported_size.height());
// Get bad (unsupported) dimensions.
int bad_width;
const bool got_bad_width =
GetMinUnsupportedDimension(min_supported_size.width(), &bad_width);
int bad_height;
const bool got_bad_height =
GetMinUnsupportedDimension(min_supported_size.height(), &bad_height);
// Now build and test the good/bad combinations that we expect will fail.
std::vector<gfx::Size> test_sizes;
if (got_bad_width)
test_sizes.push_back({bad_width, good_height});
if (got_bad_height)
test_sizes.push_back({good_width, bad_height});
if (got_bad_width && got_bad_height)
test_sizes.push_back({bad_width, bad_height});
for (const auto& test_size : test_sizes) {
const std::vector<unsigned char> jpeg_data = GenerateJpegImage(test_size);
ASSERT_FALSE(jpeg_data.empty());
VaapiImageDecodeStatus status = VaapiImageDecodeStatus::kSuccess;
ASSERT_FALSE(Decode(base::as_bytes(base::make_span(jpeg_data)), &status))
<< "Decode unexpectedly succeeded for size = " << test_size.ToString();
EXPECT_EQ(VaapiImageDecodeStatus::kUnsupportedImage, status);
EXPECT_FALSE(Decoder()->GetScopedVASurface());
}
}
// Make sure that JPEGs whose size is above the supported size range are
// rejected.
//
// TODO(andrescj): for now, this assumes 4:2:0. Handle other formats.
TEST_F(VaapiJpegDecoderTest, DecodeFailsForAboveMaxSize) {
gfx::Size min_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMinResolution(VAProfileJPEGBaseline,
&min_supported_size));
gfx::Size max_supported_size;
ASSERT_TRUE(VaapiWrapper::GetDecodeMaxResolution(VAProfileJPEGBaseline,
&max_supported_size));
// Ensure the maximum supported size is reasonable.
ASSERT_GE(max_supported_size.width(), min_supported_size.width());
ASSERT_GE(max_supported_size.height(), min_supported_size.height());
ASSERT_LE(max_supported_size.width(), kLargestSupportedSize.width());
ASSERT_LE(max_supported_size.height(), kLargestSupportedSize.height());
// Get good (supported) maximum dimensions.
const int good_width = GetMaxSupportedDimension(max_supported_size.width());
ASSERT_GE(good_width, min_supported_size.width());
ASSERT_GT(good_width, 0);
const int good_height = GetMaxSupportedDimension(max_supported_size.height());
ASSERT_GE(good_height, min_supported_size.height());
ASSERT_GT(good_height, 0);
// Get bad (unsupported) dimensions.
const int bad_width =
RoundUpToMultiple(max_supported_size.width() + 1, k420MCUSize);
const int bad_height =
RoundUpToMultiple(max_supported_size.height() + 1, k420MCUSize);
// Now build and test the good/bad combinations that we expect will fail.
const std::vector<gfx::Size> test_sizes = {{bad_width, good_height},
{good_width, bad_height},
{bad_width, bad_height}};
for (const auto& test_size : test_sizes) {
const std::vector<unsigned char> jpeg_data = GenerateJpegImage(test_size);
ASSERT_FALSE(jpeg_data.empty());
VaapiImageDecodeStatus status = VaapiImageDecodeStatus::kSuccess;
ASSERT_FALSE(Decode(base::as_bytes(base::make_span(jpeg_data)), &status))
<< "Decode unexpectedly succeeded for size = " << test_size.ToString();
EXPECT_EQ(VaapiImageDecodeStatus::kUnsupportedImage, status);
EXPECT_FALSE(Decoder()->GetScopedVASurface());
}
}
TEST_F(VaapiJpegDecoderTest, DecodeFails) {
// A grayscale image (4:0:0) should be rejected.
base::FilePath input_file = FindTestDataFilePath(kUnsupportedFilename);
std::string jpeg_data;
ASSERT_TRUE(base::ReadFileToString(input_file, &jpeg_data))
<< "failed to read input data from " << input_file.value();
VaapiImageDecodeStatus status = VaapiImageDecodeStatus::kSuccess;
ASSERT_FALSE(Decode(base::as_bytes(base::make_span(jpeg_data)), &status));
EXPECT_EQ(VaapiImageDecodeStatus::kUnsupportedSubsampling, status);
EXPECT_FALSE(Decoder()->GetScopedVASurface());
}
INSTANTIATE_TEST_SUITE_P(All,
VaapiJpegDecoderTest,
testing::ValuesIn(kVAImageTestCases),
vaapi_test_utils::TestParamToString);
INSTANTIATE_TEST_SUITE_P(All,
VaapiJpegDecoderWithDmaBufsTest,
testing::ValuesIn(kDmaBufTestCases),
vaapi_test_utils::TestParamToString);
} // namespace media