1 // Copyright 2019 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 // This has to be included first.
6 // See http://code.google.com/p/googletest/issues/detail?id=371
7 #include "testing/gtest/include/gtest/gtest.h"
8 
9 #include <unistd.h>
10 #include <map>
11 #include <vector>
12 
13 #include <va/va.h>
14 #include <va/va_str.h>
15 
16 #include "base/files/file.h"
17 #include "base/files/scoped_file.h"
18 #include "base/logging.h"
19 #include "base/optional.h"
20 #include "base/process/launch.h"
21 #include "base/stl_util.h"
22 #include "base/strings/string_split.h"
23 #include "base/test/launcher/unit_test_launcher.h"
24 #include "base/test/test_suite.h"
25 #include "build/chromeos_buildflags.h"
26 #include "gpu/config/gpu_driver_bug_workarounds.h"
27 #include "media/gpu/vaapi/vaapi_wrapper.h"
28 #include "media/media_buildflags.h"
29 
30 namespace media {
31 namespace {
32 
ConvertToVAProfile(VideoCodecProfile profile)33 base::Optional<VAProfile> ConvertToVAProfile(VideoCodecProfile profile) {
34   // A map between VideoCodecProfile and VAProfile.
35   const std::map<VideoCodecProfile, VAProfile> kProfileMap = {
36     // VAProfileH264Baseline is deprecated in <va/va.h> from libva 2.0.0.
37     {H264PROFILE_BASELINE, VAProfileH264ConstrainedBaseline},
38     {H264PROFILE_MAIN, VAProfileH264Main},
39     {H264PROFILE_HIGH, VAProfileH264High},
40     {VP8PROFILE_ANY, VAProfileVP8Version0_3},
41     {VP9PROFILE_PROFILE0, VAProfileVP9Profile0},
42     {VP9PROFILE_PROFILE2, VAProfileVP9Profile2},
43 #if BUILDFLAG(IS_ASH)
44     // TODO(hiroh): Remove if-macro once libva for linux-chrome is upreved to
45     // 2.9.0 or newer.
46     // https://source.chromium.org/chromium/chromium/src/+/master:build/linux/sysroot_scripts/generated_package_lists/sid.amd64
47     {AV1PROFILE_PROFILE_MAIN, VAProfileAV1Profile0},
48 #endif
49 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
50     {HEVCPROFILE_MAIN, VAProfileHEVCMain},
51 #endif
52   };
53   auto it = kProfileMap.find(profile);
54   return it != kProfileMap.end() ? base::make_optional<VAProfile>(it->second)
55                                  : base::nullopt;
56 }
57 
58 // Converts the given string to VAProfile
StringToVAProfile(const std::string & va_profile)59 base::Optional<VAProfile> StringToVAProfile(const std::string& va_profile) {
60   const std::map<std::string, VAProfile> kStringToVAProfile = {
61     {"VAProfileNone", VAProfileNone},
62     {"VAProfileH264ConstrainedBaseline", VAProfileH264ConstrainedBaseline},
63     // Even though it's deprecated, we leave VAProfileH264Baseline's
64     // translation here to assert we never encounter it.
65     {"VAProfileH264Baseline", VAProfileH264Baseline},
66     {"VAProfileH264Main", VAProfileH264Main},
67     {"VAProfileH264High", VAProfileH264High},
68     {"VAProfileJPEGBaseline", VAProfileJPEGBaseline},
69     {"VAProfileVP8Version0_3", VAProfileVP8Version0_3},
70     {"VAProfileVP9Profile0", VAProfileVP9Profile0},
71     {"VAProfileVP9Profile2", VAProfileVP9Profile2},
72 #if BUILDFLAG(IS_ASH)
73     // TODO(hiroh): Remove if-macro once libva for linux-chrome is upreved to
74     // 2.9.0 or newer.
75     // https://source.chromium.org/chromium/chromium/src/+/master:build/linux/sysroot_scripts/generated_package_lists/sid.amd64
76     {"VAProfileAV1Profile0", VAProfileAV1Profile0},
77 #endif
78 #if BUILDFLAG(ENABLE_PLATFORM_HEVC)
79     {"VAProfileHEVCMain", VAProfileHEVCMain},
80 #endif
81   };
82 
83   auto it = kStringToVAProfile.find(va_profile);
84   return it != kStringToVAProfile.end()
85              ? base::make_optional<VAProfile>(it->second)
86              : base::nullopt;
87 }
88 
89 // Converts the given string to VAProfile
StringToVAEntrypoint(const std::string & va_entrypoint)90 base::Optional<VAEntrypoint> StringToVAEntrypoint(
91     const std::string& va_entrypoint) {
92   const std::map<std::string, VAEntrypoint> kStringToVAEntrypoint = {
93       {"VAEntrypointVLD", VAEntrypointVLD},
94       {"VAEntrypointEncSlice", VAEntrypointEncSlice},
95       {"VAEntrypointEncPicture", VAEntrypointEncPicture},
96       {"VAEntrypointEncSliceLP", VAEntrypointEncSliceLP},
97       {"VAEntrypointVideoProc", VAEntrypointVideoProc}};
98 
99   auto it = kStringToVAEntrypoint.find(va_entrypoint);
100   return it != kStringToVAEntrypoint.end()
101              ? base::make_optional<VAEntrypoint>(it->second)
102              : base::nullopt;
103 }
104 }  // namespace
105 
106 class VaapiTest : public testing::Test {
107  public:
108   VaapiTest() = default;
109   ~VaapiTest() override = default;
110 };
111 
ParseVainfo(const std::string & output)112 std::map<VAProfile, std::vector<VAEntrypoint>> ParseVainfo(
113     const std::string& output) {
114   const std::vector<std::string> lines =
115       base::SplitString(output, "\n", base::WhitespaceHandling::TRIM_WHITESPACE,
116                         base::SplitResult::SPLIT_WANT_ALL);
117   std::map<VAProfile, std::vector<VAEntrypoint>> info;
118   for (const std::string& line : lines) {
119     if (!base::StartsWith(line, "VAProfile",
120                           base::CompareCase::INSENSITIVE_ASCII)) {
121       continue;
122     }
123     std::vector<std::string> res =
124         base::SplitString(line, ":", base::WhitespaceHandling::TRIM_WHITESPACE,
125                           base::SplitResult::SPLIT_WANT_ALL);
126     if (res.size() != 2) {
127       LOG(ERROR) << "Unexpected line: " << line;
128       continue;
129     }
130 
131     auto va_profile = StringToVAProfile(res[0]);
132     if (!va_profile)
133       continue;
134     auto va_entrypoint = StringToVAEntrypoint(res[1]);
135     if (!va_entrypoint)
136       continue;
137     info[*va_profile].push_back(*va_entrypoint);
138     DVLOG(3) << line;
139   }
140   return info;
141 }
142 
RetrieveVAInfoOutput()143 std::map<VAProfile, std::vector<VAEntrypoint>> RetrieveVAInfoOutput() {
144   int fds[2];
145   PCHECK(pipe(fds) == 0);
146   base::File read_pipe(fds[0]);
147   base::ScopedFD write_pipe_fd(fds[1]);
148 
149   base::LaunchOptions options;
150   options.fds_to_remap.emplace_back(write_pipe_fd.get(), STDOUT_FILENO);
151   std::vector<std::string> argv = {"vainfo"};
152   EXPECT_TRUE(LaunchProcess(argv, options).IsValid());
153   write_pipe_fd.reset();
154 
155   char buf[4096] = {};
156   int n = read_pipe.ReadAtCurrentPos(buf, sizeof(buf));
157   PCHECK(n >= 0);
158   EXPECT_LT(n, 4096);
159   std::string output(buf, n);
160   DVLOG(4) << output;
161   return ParseVainfo(output);
162 }
163 
TEST_F(VaapiTest,VaapiSandboxInitialization)164 TEST_F(VaapiTest, VaapiSandboxInitialization) {
165   // Here we just test that the PreSandboxInitialization() in SetUp() worked
166   // fine. Said initialization is buried in internal singletons, but we can
167   // verify that at least the implementation type has been filled in.
168   EXPECT_NE(VaapiWrapper::GetImplementationType(), VAImplementation::kInvalid);
169 }
170 
171 // Commit [1] deprecated VAProfileH264Baseline from libva in 2017 (release
172 // 2.0.0). This test verifies that such profile is never seen in the lab.
173 // [1] https://github.com/intel/libva/commit/6f69256f8ccc9a73c0b196ab77ac69ab1f4f33c2
TEST_F(VaapiTest,VerifyNoVAProfileH264Baseline)174 TEST_F(VaapiTest, VerifyNoVAProfileH264Baseline) {
175   const auto va_info = RetrieveVAInfoOutput();
176   EXPECT_FALSE(base::Contains(va_info, VAProfileH264Baseline));
177 }
178 
179 // Verifies that every VAProfile from VaapiWrapper::GetSupportedDecodeProfiles()
180 // is indeed supported by the command line vainfo utility and by
181 // VaapiWrapper::IsDecodeSupported().
TEST_F(VaapiTest,GetSupportedDecodeProfiles)182 TEST_F(VaapiTest, GetSupportedDecodeProfiles) {
183   const auto va_info = RetrieveVAInfoOutput();
184 
185   for (const auto& profile : VaapiWrapper::GetSupportedDecodeProfiles(
186            gpu::GpuDriverBugWorkarounds())) {
187     const auto va_profile = ConvertToVAProfile(profile.profile);
188     ASSERT_TRUE(va_profile.has_value());
189 
190     EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointVLD))
191         << " profile: " << GetProfileName(profile.profile)
192         << ", va profile: " << vaProfileStr(*va_profile);
193     EXPECT_TRUE(VaapiWrapper::IsDecodeSupported(*va_profile))
194         << " profile: " << GetProfileName(profile.profile)
195         << ", va profile: " << vaProfileStr(*va_profile);
196   }
197 }
198 
199 // Verifies that every VAProfile from VaapiWrapper::GetSupportedEncodeProfiles()
200 // is indeed supported by the command line vainfo utility.
TEST_F(VaapiTest,GetSupportedEncodeProfiles)201 TEST_F(VaapiTest, GetSupportedEncodeProfiles) {
202   const auto va_info = RetrieveVAInfoOutput();
203 
204   for (const auto& profile : VaapiWrapper::GetSupportedEncodeProfiles()) {
205     const auto va_profile = ConvertToVAProfile(profile.profile);
206     ASSERT_TRUE(va_profile.has_value());
207 
208     EXPECT_TRUE(base::Contains(va_info.at(*va_profile), VAEntrypointEncSlice) ||
209                 base::Contains(va_info.at(*va_profile), VAEntrypointEncSliceLP))
210         << " profile: " << GetProfileName(profile.profile)
211         << ", va profile: " << vaProfileStr(*va_profile);
212   }
213 }
214 
215 // Verifies that if JPEG decoding and encoding are supported by VaapiWrapper,
216 // they are also supported by by the command line vainfo utility.
TEST_F(VaapiTest,VaapiProfilesJPEG)217 TEST_F(VaapiTest, VaapiProfilesJPEG) {
218   const auto va_info = RetrieveVAInfoOutput();
219 
220   EXPECT_EQ(VaapiWrapper::IsDecodeSupported(VAProfileJPEGBaseline),
221             base::Contains(va_info.at(VAProfileJPEGBaseline), VAEntrypointVLD));
222   EXPECT_EQ(VaapiWrapper::IsJpegEncodeSupported(),
223             base::Contains(va_info.at(VAProfileJPEGBaseline),
224                            VAEntrypointEncPicture));
225 }
226 
227 // Verifies that the default VAEntrypoint as per VaapiWrapper is indeed among
228 // the supported ones.
TEST_F(VaapiTest,DefaultEntrypointIsSupported)229 TEST_F(VaapiTest, DefaultEntrypointIsSupported) {
230   for (size_t i = 0; i < VaapiWrapper::kCodecModeMax; ++i) {
231     const auto wrapper_mode = static_cast<VaapiWrapper::CodecMode>(i);
232     std::map<VAProfile, std::vector<VAEntrypoint>> configurations =
233         VaapiWrapper::GetSupportedConfigurationsForCodecModeForTesting(
234             wrapper_mode);
235     for (const auto& profile_and_entrypoints : configurations) {
236       const VAEntrypoint default_entrypoint =
237           VaapiWrapper::GetDefaultVaEntryPoint(wrapper_mode,
238                                                profile_and_entrypoints.first);
239       const auto& supported_entrypoints = profile_and_entrypoints.second;
240       EXPECT_TRUE(base::Contains(supported_entrypoints, default_entrypoint))
241           << "Default VAEntrypoint " << vaEntrypointStr(default_entrypoint)
242           << " (VaapiWrapper mode = " << wrapper_mode
243           << ") is not supported for "
244           << vaProfileStr(profile_and_entrypoints.first);
245     }
246   }
247 }
248 }  // namespace media
249 
main(int argc,char ** argv)250 int main(int argc, char** argv) {
251   base::TestSuite test_suite(argc, argv);
252 
253   // PreSandboxInitialization() loads and opens the driver, queries its
254   // capabilities and fills in the VASupportedProfiles.
255   media::VaapiWrapper::PreSandboxInitialization();
256 
257   return base::LaunchUnitTests(
258       argc, argv,
259       base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite)));
260 }
261