KPipewire

vaapiutils.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "vaapiutils_p.h"
8#include <logging_vaapi.h>
9
10#include <QDir>
11
12extern "C" {
13#include <drm_fourcc.h>
14#include <fcntl.h>
15#include <unistd.h>
16#include <va/va_drm.h>
17#include <xf86drm.h>
18}
19
20VaapiUtils::VaapiUtils(VaapiUtils::Private)
21{
22 int max_devices = drmGetDevices2(0, nullptr, 0);
23 if (max_devices <= 0) {
24 qCWarning(PIPEWIREVAAPI_LOGGING) << "drmGetDevices2() has not found any devices (errno=" << -max_devices << ")";
25 return;
26 }
27
28 std::vector<drmDevicePtr> devices(max_devices);
29 int ret = drmGetDevices2(0, devices.data(), max_devices);
30 if (ret < 0) {
31 qCWarning(PIPEWIREVAAPI_LOGGING) << "drmGetDevices2() returned an error " << ret;
32 return;
33 }
34
35 for (const drmDevicePtr &device : devices) {
36 if (device->available_nodes & (1 << DRM_NODE_RENDER)) {
37 QByteArray fullPath = device->nodes[DRM_NODE_RENDER];
38 if (supportsH264(fullPath)) {
39 m_devicePath = fullPath;
40 break;
41 }
42 }
43 }
44
45 drmFreeDevices(devices.data(), ret);
46
47 if (m_devicePath.isEmpty()) {
48 qCWarning(PIPEWIREVAAPI_LOGGING) << "DRM device not found";
49 }
50}
51
52VaapiUtils::~VaapiUtils()
53{
54}
55
56bool VaapiUtils::supportsProfile(VAProfile profile)
57{
58 if (m_devicePath.isEmpty()) {
59 return false;
60 }
61 bool ret = false;
62
63 int drmFd = -1;
64
65 VADisplay vaDpy = openDevice(&drmFd, m_devicePath);
66 if (!vaDpy) {
67 return false;
68 }
69
70 ret = supportsProfile(profile, vaDpy, m_devicePath);
71
72 closeDevice(&drmFd, vaDpy);
73
74 return ret;
75}
76
77bool VaapiUtils::supportsH264(const QByteArray &path) const
78{
79 if (path.isEmpty()) {
80 return false;
81 }
82 bool ret = false;
83
84 int drmFd = -1;
85
86 VADisplay vaDpy = openDevice(&drmFd, path);
87 if (!vaDpy) {
88 return false;
89 }
90
91 const char *driver = vaQueryVendorString(vaDpy);
92 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI:" << driver << "in use for device" << path;
93
94 ret = supportsProfile(VAProfileH264ConstrainedBaseline, vaDpy, path) || supportsProfile(VAProfileH264Main, vaDpy, path)
95 || supportsProfile(VAProfileH264High, vaDpy, path);
96
97 querySizeConstraints(vaDpy);
98
99 closeDevice(&drmFd, vaDpy);
100
101 return ret;
102}
103
104QByteArray VaapiUtils::devicePath()
105{
106 return m_devicePath;
107}
108
109QSize VaapiUtils::minimumSize() const
110{
111 return m_minSize;
112}
113
114QSize VaapiUtils::maximumSize() const
115{
116 return m_maxSize;
117}
118
119bool VaapiUtils::supportsModifier(uint32_t /*format*/, uint64_t modifier)
120{
121 // For now, we have no way of querying VAAPI for what modifiers are
122 // actually supported for encoding. So assume we can only support linear
123 // buffers for now, even though some cards may actually also support
124 // other formats.
125 //
126 // As of 8/4/24, we know that on AMD chips using the RadeonSI driver, frames
127 // using "Delta Color Compression" modifier will be rejected by the driver.
128 // Also, Intel chips using the Interl iHD Media driver will accept any
129 // modifier but internally force using LINEAR so any modifier other than
130 // LINEAR should be rejected.
131 //
132 // See https://github.com/intel/libva/pull/589 for discussion surrounding
133 // API in LibVA for querying supported modifiers.
134 return modifier == DRM_FORMAT_MOD_LINEAR;
135}
136
137std::shared_ptr<VaapiUtils> VaapiUtils::instance()
138{
139 static std::shared_ptr<VaapiUtils> instance = std::make_shared<VaapiUtils>(VaapiUtils::Private{});
140 return instance;
141}
142
143VADisplay VaapiUtils::openDevice(int *fd, const QByteArray &path)
144{
145 VADisplay vaDpy;
146
147 if (path.isEmpty()) {
148 return NULL;
149 }
150
151 *fd = open(path.data(), O_RDWR);
152 if (*fd < 0) {
153 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: Failed to open device" << path;
154 return NULL;
155 }
156
157 vaDpy = vaGetDisplayDRM(*fd);
158 if (!vaDpy) {
159 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: Failed to initialize DRM display";
160 return NULL;
161 }
162
163 if (vaDisplayIsValid(vaDpy) == 0) {
164 qCWarning(PIPEWIREVAAPI_LOGGING) << "Invalid VA display";
165 vaTerminate(vaDpy);
166 return NULL;
167 }
168
169 int major, minor;
170 VAStatus va_status = vaInitialize(vaDpy, &major, &minor);
171
172 if (va_status != VA_STATUS_SUCCESS) {
173 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: Failed to initialize display";
174 return NULL;
175 }
176
177 qCInfo(PIPEWIREVAAPI_LOGGING) << "VAAPI: API version" << major << "." << minor;
178 qCInfo(PIPEWIREVAAPI_LOGGING) << "VAAPI: Display initialized";
179
180 return vaDpy;
181}
182
183void VaapiUtils::closeDevice(int *fd, VADisplay dpy)
184{
185 vaTerminate(dpy);
186 if (*fd < 0) {
187 return;
188 }
189
190 close(*fd);
191 *fd = -1;
192}
193
194bool VaapiUtils::supportsProfile(VAProfile profile, VADisplay dpy, const QByteArray &path)
195{
196 uint32_t ret = rateControlForProfile(profile, VAEntrypointEncSlice, dpy, path);
197
198 if (ret & VA_RC_CBR || ret & VA_RC_CQP || ret & VA_RC_VBR) {
199 return true;
200 } else {
201 ret = rateControlForProfile(profile, VAEntrypointEncSliceLP, dpy, path);
202
203 if (ret & VA_RC_CBR || ret & VA_RC_CQP || ret & VA_RC_VBR) {
204 return true;
205 }
206 }
207
208 return false;
209}
210
211uint32_t VaapiUtils::rateControlForProfile(VAProfile profile, VAEntrypoint entrypoint, VADisplay dpy, const QByteArray &path)
212{
213 VAStatus va_status;
214 VAConfigAttrib attrib[1];
215 attrib->type = VAConfigAttribRateControl;
216
217 va_status = vaGetConfigAttributes(dpy, profile, entrypoint, attrib, 1);
218
219 switch (va_status) {
220 case VA_STATUS_SUCCESS:
221 return attrib->value;
222 case VA_STATUS_ERROR_UNSUPPORTED_PROFILE:
223 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: profile" << profile << "is not supported by the device" << path;
224 return 0;
225 case VA_STATUS_ERROR_UNSUPPORTED_ENTRYPOINT:
226 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: entrypoint" << entrypoint << "of profile" << profile << "is not supported by the device" << path;
227 return 0;
228 default:
229 qCWarning(PIPEWIREVAAPI_LOGGING) << "VAAPI: Fail to get RC attribute from the" << profile << entrypoint << "of the device" << path;
230 return 0;
231 }
232}
233
234void VaapiUtils::querySizeConstraints(VADisplay dpy) const
235{
236 VAConfigID config;
237 if (auto status = vaCreateConfig(dpy, VAProfileH264ConstrainedBaseline, VAEntrypointEncSlice, nullptr, 0, &config); status != VA_STATUS_SUCCESS) {
238 return;
239 }
240
241 VASurfaceAttrib attrib[8];
242 uint32_t attribCount = 8;
243
244 auto status = vaQuerySurfaceAttributes(dpy, config, attrib, &attribCount);
245 if (status == VA_STATUS_SUCCESS) {
246 for (uint32_t i = 0; i < attribCount; ++i) {
247 switch (attrib[i].type) {
248 case VASurfaceAttribMinWidth:
249 m_minSize.setWidth(attrib[i].value.value.i);
250 break;
251 case VASurfaceAttribMinHeight:
252 m_minSize.setHeight(attrib[i].value.value.i);
253 break;
254 case VASurfaceAttribMaxWidth:
255 m_maxSize.setWidth(attrib[i].value.value.i);
256 break;
257 case VASurfaceAttribMaxHeight:
258 m_maxSize.setHeight(attrib[i].value.value.i);
259 break;
260 default:
261 break;
262 }
263 }
264 }
265
266 vaDestroyConfig(dpy, config);
267}
Q_SCRIPTABLE CaptureState status()
QString path(const QString &relativePath)
const QList< QKeySequence > & close()
const QList< QKeySequence > & open()
QChar * data()
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:15:17 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.