| // 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 |