File indexing completed on 2024-05-05 05:30:19

0001 /*
0002     SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 
0007 #include "vaapiutils_p.h"
0008 #include <logging_record.h>
0009 
0010 #include <QDir>
0011 
0012 extern "C" {
0013 #include <fcntl.h>
0014 #include <unistd.h>
0015 #include <va/va_drm.h>
0016 #include <xf86drm.h>
0017 }
0018 
0019 VaapiUtils::VaapiUtils()
0020 {
0021     int max_devices = drmGetDevices2(0, nullptr, 0);
0022     if (max_devices <= 0) {
0023         qCWarning(PIPEWIRERECORD_LOGGING) << "drmGetDevices2() has not found any devices (errno=" << -max_devices << ")";
0024         return;
0025     }
0026 
0027     std::vector<drmDevicePtr> devices(max_devices);
0028     int ret = drmGetDevices2(0, devices.data(), max_devices);
0029     if (ret < 0) {
0030         qCWarning(PIPEWIRERECORD_LOGGING) << "drmGetDevices2() returned an error " << ret;
0031         return;
0032     }
0033 
0034     for (const drmDevicePtr &device : devices) {
0035         if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
0036             QByteArray fullPath = device->nodes[DRM_NODE_RENDER];
0037             if (supportsH264(fullPath)) {
0038                 m_devicePath = fullPath;
0039                 break;
0040             }
0041             break;
0042         }
0043     }
0044 
0045     drmFreeDevices(devices.data(), ret);
0046 
0047     if (m_devicePath.isEmpty()) {
0048         qCWarning(PIPEWIRERECORD_LOGGING) << "DRM device not found";
0049     }
0050 }
0051 
0052 VaapiUtils::~VaapiUtils()
0053 {
0054 }
0055 
0056 bool VaapiUtils::supportsProfile(VAProfile profile)
0057 {
0058     if (m_devicePath.isEmpty()) {
0059         return false;
0060     }
0061     bool ret = false;
0062 
0063     int drmFd = -1;
0064 
0065     VADisplay vaDpy = openDevice(&drmFd, m_devicePath);
0066     if (!vaDpy) {
0067         return false;
0068     }
0069 
0070     ret = supportsProfile(profile, vaDpy, m_devicePath);
0071 
0072     closeDevice(&drmFd, vaDpy);
0073 
0074     return ret;
0075 }
0076 
0077 bool VaapiUtils::supportsH264(const QByteArray &path) const
0078 {
0079     if (path.isEmpty()) {
0080         return false;
0081     }
0082     bool ret = false;
0083 
0084     int drmFd = -1;
0085 
0086     VADisplay vaDpy = openDevice(&drmFd, path);
0087     if (!vaDpy) {
0088         return false;
0089     }
0090 
0091     ret = supportsProfile(VAProfileH264ConstrainedBaseline, vaDpy, path) || supportsProfile(VAProfileH264Main, vaDpy, path)
0092         || supportsProfile(VAProfileH264High, vaDpy, path);
0093 
0094     querySizeConstraints(vaDpy);
0095 
0096     /**
0097      * If FFPEG fails to import a buffer with modifiers, it silently goes into
0098      * of importing as linear, which looks to us like it works, but obviously results in
0099      * a messed up image. At the time of writing the Intel iHD driver does not
0100      *
0101      * Manually blacklist drivers which are known to fail import
0102      *
0103      * 10/7/23 - FFmpeg 2.6 with intel-media-driver 23.2.3-1
0104      */
0105     const bool blackListed = QByteArray(vaQueryVendorString(vaDpy)).startsWith("Intel iHD driver");
0106 
0107     const bool disabledByEnvVar = qEnvironmentVariableIntValue("KPIPEWIRE_NO_MODIFIERS_FOR_ENCODING") > 0;
0108 
0109     if (blackListed || disabledByEnvVar) {
0110         m_supportsHardwareModifiers = false;
0111     }
0112 
0113     closeDevice(&drmFd, vaDpy);
0114 
0115     return ret;
0116 }
0117 
0118 QByteArray VaapiUtils::devicePath()
0119 {
0120     return m_devicePath;
0121 }
0122 
0123 QSize VaapiUtils::minimumSize() const
0124 {
0125     return m_minSize;
0126 }
0127 
0128 QSize VaapiUtils::maximumSize() const
0129 {
0130     return m_maxSize;
0131 }
0132 
0133 bool VaapiUtils::supportsHardwareModifiers() const
0134 {
0135     return m_supportsHardwareModifiers;
0136 }
0137 
0138 VADisplay VaapiUtils::openDevice(int *fd, const QByteArray &path)
0139 {
0140     VADisplay vaDpy;
0141 
0142     if (path.isEmpty()) {
0143         return NULL;
0144     }
0145 
0146     *fd = open(path.data(), O_RDWR);
0147     if (*fd < 0) {
0148         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: Failed to open device" << path;
0149         return NULL;
0150     }
0151 
0152     vaDpy = vaGetDisplayDRM(*fd);
0153     if (!vaDpy) {
0154         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: Failed to initialize DRM display";
0155         return NULL;
0156     }
0157 
0158     if (vaDisplayIsValid(vaDpy) == 0) {
0159         qCWarning(PIPEWIRERECORD_LOGGING) << "Invalid VA display";
0160         vaTerminate(vaDpy);
0161         return NULL;
0162     }
0163 
0164     int major, minor;
0165     VAStatus va_status = vaInitialize(vaDpy, &major, &minor);
0166 
0167     if (va_status != VA_STATUS_SUCCESS) {
0168         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: Failed to initialize display";
0169         return NULL;
0170     }
0171 
0172     qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: Display initialized";
0173 
0174     qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: API version" << major << "." << minor;
0175 
0176     const char *driver = vaQueryVendorString(vaDpy);
0177 
0178     qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI:" << driver << "in use for device" << path;
0179 
0180     return vaDpy;
0181 }
0182 
0183 void VaapiUtils::closeDevice(int *fd, VADisplay dpy)
0184 {
0185     vaTerminate(dpy);
0186     if (*fd < 0) {
0187         return;
0188     }
0189 
0190     close(*fd);
0191     *fd = -1;
0192 }
0193 
0194 bool VaapiUtils::supportsProfile(VAProfile profile, VADisplay dpy, const QByteArray &path)
0195 {
0196     uint32_t ret = rateControlForProfile(profile, VAEntrypointEncSlice, dpy, path);
0197 
0198     if (ret & VA_RC_CBR || ret & VA_RC_CQP || ret & VA_RC_VBR) {
0199         return true;
0200     } else {
0201         ret = rateControlForProfile(profile, VAEntrypointEncSliceLP, dpy, path);
0202 
0203         if (ret & VA_RC_CBR || ret & VA_RC_CQP || ret & VA_RC_VBR) {
0204             return true;
0205         }
0206     }
0207 
0208     return false;
0209 }
0210 
0211 uint32_t VaapiUtils::rateControlForProfile(VAProfile profile, VAEntrypoint entrypoint, VADisplay dpy, const QByteArray &path)
0212 {
0213     VAStatus va_status;
0214     VAConfigAttrib attrib[1];
0215     attrib->type = VAConfigAttribRateControl;
0216 
0217     va_status = vaGetConfigAttributes(dpy, profile, entrypoint, attrib, 1);
0218 
0219     switch (va_status) {
0220     case VA_STATUS_SUCCESS:
0221         return attrib->value;
0222     case VA_STATUS_ERROR_UNSUPPORTED_PROFILE:
0223         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: profile" << profile << "is not supported by the device" << path;
0224         return 0;
0225     case VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT:
0226         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: entrypoint" << entrypoint << "of profile" << profile << "is not supported by the device" << path;
0227         return 0;
0228     default:
0229         qCWarning(PIPEWIRERECORD_LOGGING) << "VAAPI: Fail to get RC attribute from the" << profile << entrypoint << "of the device" << path;
0230         return 0;
0231     }
0232 }
0233 
0234 void VaapiUtils::querySizeConstraints(VADisplay dpy) const
0235 {
0236     VAConfigID config;
0237     if (auto status = vaCreateConfig(dpy, VAProfileH264ConstrainedBaseline, VAEntrypointEncSlice, nullptr, 0, &config); status != VA_STATUS_SUCCESS) {
0238         return;
0239     }
0240 
0241     VASurfaceAttrib attrib[8];
0242     uint32_t attribCount = 8;
0243 
0244     auto status = vaQuerySurfaceAttributes(dpy, config, attrib, &attribCount);
0245     if (status == VA_STATUS_SUCCESS) {
0246         for (uint32_t i = 0; i < attribCount; ++i) {
0247             switch (attrib[i].type) {
0248             case VASurfaceAttribMinWidth:
0249                 m_minSize.setWidth(attrib[i].value.value.i);
0250                 break;
0251             case VASurfaceAttribMinHeight:
0252                 m_minSize.setHeight(attrib[i].value.value.i);
0253                 break;
0254             case VASurfaceAttribMaxWidth:
0255                 m_maxSize.setWidth(attrib[i].value.value.i);
0256                 break;
0257             case VASurfaceAttribMaxHeight:
0258                 m_maxSize.setHeight(attrib[i].value.value.i);
0259                 break;
0260             default:
0261                 break;
0262             }
0263         }
0264     }
0265 
0266     vaDestroyConfig(dpy, config);
0267 }