| // Copyright 2021 The Chromium OS 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 "media_capabilities/vaapi.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <va/va.h> |
| #include <va/va_drm.h> |
| #include <va/va_str.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include <base/containers/contains.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/logging.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include "media_capabilities/common.h" |
| |
| #define VA_LOG_ASSERT(va_error, function) \ |
| LOG_ASSERT((va_error) == VA_STATUS_SUCCESS) \ |
| << function << " failed, VA error: " << vaErrorStr(va_error); |
| |
| namespace { |
| Profile VAProfileToProfile(VAProfile va_profile) { |
| switch (va_profile) { |
| case VAProfileH264ConstrainedBaseline: |
| // VAProfileH264Baseline is deprecated in <va/va.h> since libva 2.0.0. |
| // https://github.com/intel/libva/commit/6f69256f8ccc9a73c0b196ab77ac69ab1f4f33c2 |
| // No VA-API driver supports Baseline specific coding tools (ASO, FMO and |
| // redundant slices). We regard ConstrainedBaseline as Baseline because |
| // encoding ConstrainedBaseline can be regarded as encoding Baseline |
| // stream, and since it is rare that the three coding tools are used, most |
| // Baseline streams would be ConstrainedBaseline streams. |
| return Profile::kH264Baseline; |
| case VAProfileH264Main: |
| return Profile::kH264Main; |
| case VAProfileH264High: |
| return Profile::kH264High; |
| case VAProfileVP8Version0_3: |
| return Profile::kVP8; |
| case VAProfileVP9Profile0: |
| return Profile::kVP9Profile0; |
| case VAProfileVP9Profile2: |
| return Profile::kVP9Profile2; |
| case VAProfileJPEGBaseline: |
| return Profile::kJPEG; |
| case VAProfileAV1Profile0: |
| return Profile::kAV1Main; |
| default: |
| return Profile::kNone; |
| } |
| } |
| |
| Subsampling VARtFormatToSubsampling(uint32_t va_rt_format) { |
| switch (va_rt_format) { |
| case VA_RT_FORMAT_YUV420: |
| case VA_RT_FORMAT_YUV420_10: |
| return Subsampling::kYUV420; |
| case VA_RT_FORMAT_YUV422: |
| return Subsampling::kYUV422; |
| case VA_RT_FORMAT_YUV444: |
| return Subsampling::kYUV444; |
| default: |
| LOG(FATAL) << "Unexpected va_rt_format: " << va_rt_format; |
| return Subsampling::kNone; |
| } |
| } |
| |
| std::vector<VAEntrypoint> GetVAEntrypoints(Profile profile, bool decode) { |
| if (decode) |
| return {VAEntrypointVLD}; |
| switch (profile) { |
| case Profile::kH264Baseline: |
| case Profile::kH264Main: |
| case Profile::kH264High: |
| case Profile::kVP8: |
| case Profile::kVP9Profile0: |
| case Profile::kVP9Profile2: |
| case Profile::kAV1Main: |
| return {VAEntrypointEncSlice, VAEntrypointEncSliceLP}; |
| case Profile::kJPEG: |
| return {VAEntrypointEncPicture}; |
| case Profile::kNone: |
| default: |
| LOG(FATAL) << "Unexpected profile: " << static_cast<int32_t>(profile); |
| return {}; |
| } |
| } |
| |
| // Returns the RT formats that we care about for a given codec. |
| std::vector<uint32_t> GetVARTFormats(Profile profile, bool decode) { |
| switch (profile) { |
| case Profile::kH264Baseline: |
| case Profile::kH264Main: |
| case Profile::kH264High: |
| case Profile::kVP8: |
| case Profile::kVP9Profile0: |
| return {VA_RT_FORMAT_YUV420}; |
| case Profile::kJPEG: |
| if (decode) |
| return {VA_RT_FORMAT_YUV420, VA_RT_FORMAT_YUV422, VA_RT_FORMAT_YUV444}; |
| else |
| return {VA_RT_FORMAT_YUV420}; |
| case Profile::kVP9Profile2: |
| return {VA_RT_FORMAT_YUV420_10}; |
| case Profile::kAV1Main: |
| return {VA_RT_FORMAT_YUV420, VA_RT_FORMAT_YUV420_10}; |
| case Profile::kNone: |
| default: |
| LOG(FATAL) << "Unexpected profile: " << static_cast<int32_t>(profile); |
| return {VA_RT_FORMAT_YUV420}; |
| } |
| } |
| |
| ColorDepth GetColorDepth(uint32_t va_rt_format) { |
| return va_rt_format == VA_RT_FORMAT_YUV420_10 ? ColorDepth::k10bit |
| : ColorDepth::k8bit; |
| } |
| |
| uint32_t GetSupportedVARTFormat(VADisplay va_display, |
| VAProfile va_profile, |
| VAEntrypoint entrypoint) { |
| VAConfigAttrib attrib{}; |
| attrib.type = VAConfigAttribRTFormat; |
| VAStatus va_res = |
| vaGetConfigAttributes(va_display, va_profile, entrypoint, &attrib, 1); |
| VA_LOG_ASSERT(va_res, "vaGetConfigAttributes"); |
| LOG_IF(FATAL, attrib.value == VA_ATTRIB_NOT_SUPPORTED) |
| << "VAConfigAttribRTFormat is not supported, va_profile" |
| << vaProfileStr(va_profile) |
| << ", va_entrypoint=" << vaEntrypointStr(entrypoint); |
| return attrib.value; |
| } |
| |
| std::vector<VAEntrypoint> GetSupportedVAEntrypoints(VADisplay va_display, |
| VAProfile va_profile) { |
| const int max_entrypoints = vaMaxNumEntrypoints(va_display); |
| LOG_IF(FATAL, max_entrypoints <= 0) |
| << "vaMaxNumEntrypoints() returns an invalid value, " << max_entrypoints; |
| |
| std::vector<VAEntrypoint> supported_entrypoints(max_entrypoints); |
| int num_supported_entrypoints = 0; |
| VAStatus va_res = vaQueryConfigEntrypoints(va_display, va_profile, |
| supported_entrypoints.data(), |
| &num_supported_entrypoints); |
| VA_LOG_ASSERT(va_res, "vaQueryConfigEntryPoints"); |
| LOG_IF(FATAL, num_supported_entrypoints <= 0 || |
| num_supported_entrypoints > max_entrypoints) |
| << "Invalid number of entrypoints: " << num_supported_entrypoints; |
| |
| supported_entrypoints.resize(num_supported_entrypoints); |
| return supported_entrypoints; |
| } |
| |
| std::pair<int, int> GetMaxResolution(VADisplay va_display, |
| VAProfile va_profile, |
| VAEntrypoint va_entrypoint, |
| uint32_t va_rt_format) { |
| VAConfigAttrib attrib{}; |
| attrib.type = VAConfigAttribRTFormat; |
| // To keep a consistency with the chrome implementation, we set either YUV420 |
| // and YUV420_10. TODO(b/188486492): Set to |va_rt_format| once the chrome |
| // implementation is fixed. |
| attrib.value = va_profile == VAProfileVP9Profile2 ? VA_RT_FORMAT_YUV420_10 |
| : VA_RT_FORMAT_YUV420; |
| |
| VAConfigID va_config_id = VA_INVALID_ID; |
| VAStatus va_res = vaCreateConfig(va_display, va_profile, va_entrypoint, |
| &attrib, 1, &va_config_id); |
| VA_LOG_ASSERT(va_res, "vaCreateConfig"); |
| unsigned int num_surface_attribs; |
| va_res = vaQuerySurfaceAttributes(va_display, va_config_id, nullptr, |
| &num_surface_attribs); |
| VA_LOG_ASSERT(va_res, "vaQuerySurfaceAttributes"); |
| LOG_IF(FATAL, num_surface_attribs == 0) |
| << "vaQuerySurfaceAttributes: num_surface_attribs is zero"; |
| |
| std::vector<VASurfaceAttrib> va_surface_attribs(num_surface_attribs); |
| va_res = |
| vaQuerySurfaceAttributes(va_display, va_config_id, |
| va_surface_attribs.data(), &num_surface_attribs); |
| VA_LOG_ASSERT(va_res, "vaQuerySurfaceAttributes"); |
| std::pair<int, int> max_resolution(0, 0); |
| for (const VASurfaceAttrib& attrib : va_surface_attribs) { |
| switch (attrib.type) { |
| case VASurfaceAttribMaxWidth: |
| max_resolution.first = base::strict_cast<int>(attrib.value.value.i); |
| break; |
| case VASurfaceAttribMaxHeight: |
| max_resolution.second = base::strict_cast<int>(attrib.value.value.i); |
| break; |
| default: |
| break; |
| } |
| } |
| return max_resolution; |
| } |
| |
| std::vector<Capability> GetCapabilitiesInVADisplay(VADisplay va_display) { |
| const int max_profiles = vaMaxNumProfiles(va_display); |
| LOG_IF(FATAL, max_profiles <= 0) |
| << "vaMaxNumProfiles() returns an invalid value, " << max_profiles; |
| |
| std::vector<VAProfile> supported_profiles(max_profiles); |
| int num_supported_profiles = 0; |
| VAStatus va_res = vaQueryConfigProfiles(va_display, supported_profiles.data(), |
| &num_supported_profiles); |
| VA_LOG_ASSERT(va_res, "vaQueryConfigProfiles"); |
| LOG_IF(FATAL, |
| num_supported_profiles <= 0 || num_supported_profiles > max_profiles) |
| << "Invalid number of profiles: " << num_supported_profiles; |
| supported_profiles.resize(num_supported_profiles); |
| |
| std::vector<Capability> capabilities; |
| for (const VAProfile va_profile : supported_profiles) { |
| const Profile profile = VAProfileToProfile(va_profile); |
| if (profile == Profile::kNone) // Uninteresting |va_profile|. |
| continue; |
| |
| auto supported_entrypoints = |
| GetSupportedVAEntrypoints(va_display, va_profile); |
| for (bool decode : {true, false}) { |
| for (VAEntrypoint va_entrypoint : GetVAEntrypoints(profile, decode)) { |
| if (!base::Contains(supported_entrypoints, va_entrypoint)) |
| continue; |
| const uint32_t supported_va_rt_format = |
| GetSupportedVARTFormat(va_display, va_profile, va_entrypoint); |
| for (uint32_t va_rt_format : GetVARTFormats(profile, decode)) { |
| if (!(supported_va_rt_format & va_rt_format)) |
| continue; |
| std::pair<int, int> max_resolution = GetMaxResolution( |
| va_display, va_profile, va_entrypoint, va_rt_format); |
| for (const Resolution resolution : |
| GetInterestingResolutionsUpTo(max_resolution)) { |
| capabilities.push_back( |
| Capability(profile, decode, resolution, |
| VARtFormatToSubsampling(va_rt_format), |
| GetColorDepth(va_rt_format))); |
| } |
| } |
| } |
| } |
| } |
| return capabilities; |
| } |
| } // namespace |
| |
| std::vector<Capability> DetectVaapiCapabilities() { |
| const char* kDriRenderNode0Path = "/dev/dri/renderD128"; |
| LOG_IF(FATAL, !base::PathExists(base::FilePath(kDriRenderNode0Path))) |
| << kDriRenderNode0Path << " doesn't exist"; |
| base::ScopedFD fd( |
| HANDLE_EINTR(open(kDriRenderNode0Path, O_RDWR | O_CLOEXEC))); |
| LOG_IF(FATAL, !fd.is_valid()) << "Failed to open " << kDriRenderNode0Path; |
| |
| VADisplay va_display = vaGetDisplayDRM(fd.get()); |
| LOG_IF(FATAL, !vaDisplayIsValid(va_display)) << "VADisplay is invalid"; |
| |
| int major_version = 0; |
| int minor_version = 0; |
| VAStatus va_res = vaInitialize(va_display, &major_version, &minor_version); |
| VA_LOG_ASSERT(va_res, "vaInitialize"); |
| |
| auto capabilities = GetCapabilitiesInVADisplay(va_display); |
| va_res = vaTerminate(va_display); |
| VA_LOG_ASSERT(va_res, "vaTerminate"); |
| return capabilities; |
| } |