1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <va/va.h>
6 
7 #include "base/files/file_util.h"
8 #include "media/base/video_types.h"
9 #include "media/gpu/vaapi/test/macros.h"
10 #include "media/gpu/vaapi/test/shared_va_surface.h"
11 #include "third_party/libyuv/include/libyuv.h"
12 #include "ui/gfx/codec/png_codec.h"
13 
14 namespace media {
15 namespace vaapi_test {
16 
17 namespace {
18 
19 // Derives the VAImage metadata and image data from |surface_id| in |display|,
20 // returning true on success.
DeriveImage(VADisplay display,VASurfaceID surface_id,VAImage * image,uint8_t ** image_data)21 bool DeriveImage(VADisplay display,
22                  VASurfaceID surface_id,
23                  VAImage* image,
24                  uint8_t** image_data) {
25   VAStatus res = vaDeriveImage(display, surface_id, image);
26   VLOG_IF(2, (res != VA_STATUS_SUCCESS))
27       << "vaDeriveImage failed, VA error: " << vaErrorStr(res);
28 
29   if (image->format.fourcc != VA_FOURCC_NV12) {
30     VLOG(2) << "Test decoder binary does not support derived surface format "
31             << "with fourcc " << media::FourccToString(image->format.fourcc);
32     res = vaDestroyImage(display, image->image_id);
33     VA_LOG_ASSERT(res, "vaDestroyImage");
34     return false;
35   }
36 
37   res = vaMapBuffer(display, image->buf, reinterpret_cast<void**>(image_data));
38   VA_LOG_ASSERT(res, "vaMapBuffer");
39   return true;
40 }
41 
42 // Maps the image data from |surface_id| in |display| with given |size| by
43 // attempting to derive into |image| and |image_data|, or creating an NV12
44 // VAImage to use with vaGetImage as fallback and setting |image| and
45 // |image_data| accordingly.
GetSurfaceImage(VADisplay display,VASurfaceID surface_id,const gfx::Size size,VAImage * image,uint8_t ** image_data)46 void GetSurfaceImage(VADisplay display,
47                      VASurfaceID surface_id,
48                      const gfx::Size size,
49                      VAImage* image,
50                      uint8_t** image_data) {
51   // First attempt to derive the image from the surface.
52   if (DeriveImage(display, surface_id, image, image_data))
53     return;
54 
55   // Fall back to getting the image as NV12.
56   VAImageFormat format{
57       .fourcc = VA_FOURCC_NV12,
58       .byte_order = VA_LSB_FIRST,
59       .bits_per_pixel = 12,
60   };
61   VAStatus res =
62       vaCreateImage(display, &format, size.width(), size.height(), image);
63   VA_LOG_ASSERT(res, "vaCreateImage");
64 
65   res = vaGetImage(display, surface_id, 0, 0, size.width(), size.height(),
66                    image->image_id);
67   VA_LOG_ASSERT(res, "vaGetImage");
68 
69   res = vaMapBuffer(display, image->buf, reinterpret_cast<void**>(image_data));
70   VA_LOG_ASSERT(res, "vaMapBuffer");
71 }
72 
73 }  // namespace
74 
SharedVASurface(const VaapiDevice & va,VASurfaceID id,const gfx::Size & size,unsigned int format)75 SharedVASurface::SharedVASurface(const VaapiDevice& va,
76                                  VASurfaceID id,
77                                  const gfx::Size& size,
78                                  unsigned int format)
79     : va_(va), id_(id), size_(size), internal_va_format_(format) {}
80 
81 // static
Create(const VaapiDevice & va,const gfx::Size & size,VASurfaceAttrib attrib)82 scoped_refptr<SharedVASurface> SharedVASurface::Create(const VaapiDevice& va,
83                                                        const gfx::Size& size,
84                                                        VASurfaceAttrib attrib) {
85   VASurfaceID surface_id;
86   VAStatus res =
87       vaCreateSurfaces(va.display(), va.internal_va_format(),
88                        base::checked_cast<unsigned int>(size.width()),
89                        base::checked_cast<unsigned int>(size.height()),
90                        &surface_id, 1u, &attrib, 1u);
91   VA_LOG_ASSERT(res, "vaCreateSurfaces");
92   VLOG(1) << "created surface: " << surface_id;
93   return base::WrapRefCounted(
94       new SharedVASurface(va, surface_id, size, va.internal_va_format()));
95 }
96 
~SharedVASurface()97 SharedVASurface::~SharedVASurface() {
98   VAStatus res =
99       vaDestroySurfaces(va_.display(), const_cast<VASurfaceID*>(&id_), 1u);
100   VA_LOG_ASSERT(res, "vaDestroySurfaces");
101   VLOG(1) << "destroyed surface " << id_;
102 }
103 
SaveAsPNG(const std::string & path)104 void SharedVASurface::SaveAsPNG(const std::string& path) {
105   VAImage image;
106   uint8_t* image_data;
107 
108   GetSurfaceImage(va_.display(), id_, size_, &image, &image_data);
109 
110   // Convert the NV12 image data to ARGB and write to |path|.
111   const size_t argb_stride = image.width * 4;
112   auto argb_data = std::make_unique<uint8_t[]>(argb_stride * image.height);
113   const int convert_res = libyuv::NV12ToARGB(
114       (uint8_t*)(image_data + image.offsets[0]), image.pitches[0],
115       (uint8_t*)(image_data + image.offsets[1]), image.pitches[1],
116       argb_data.get(), argb_stride, image.width, image.height);
117   DCHECK(convert_res == 0);
118 
119   std::vector<unsigned char> image_buffer;
120   const bool result = gfx::PNGCodec::Encode(
121       argb_data.get(), gfx::PNGCodec::FORMAT_BGRA, size_, argb_stride,
122       true /* discard_transparency */, std::vector<gfx::PNGCodec::Comment>(),
123       &image_buffer);
124   DCHECK(result);
125 
126   LOG_ASSERT(base::WriteFile(base::FilePath(path), image_buffer));
127 
128   // Clean up VA handles.
129   VAStatus res = vaUnmapBuffer(va_.display(), image.buf);
130   VA_LOG_ASSERT(res, "vaUnmapBuffer");
131 
132   res = vaDestroyImage(va_.display(), image.image_id);
133   VA_LOG_ASSERT(res, "vaDestroyImage");
134 }
135 
136 }  // namespace vaapi_test
137 }  // namespace media
138