master ohmu/fastfetch-openbsd / src / detection / codec / codec_linux.c
ohmu  ·  2026-10-15
  1#include "codec.h"
  2
  3#if FF_HAVE_VADRM || FF_HAVE_VAX11
  4    #define FF_HAVE_VA 1
  5#endif
  6
  7#if FF_HAVE_VA || FF_HAVE_VDPAU
  8
  9    #include "common/debug.h"
 10    #include "common/library.h"
 11    #include "common/mallocHelper.h"
 12    #include "common/strutil.h"
 13    #include "common/io.h"
 14
 15    #if FF_HAVE_VA
 16
 17        #include <fcntl.h>
 18        #include <string.h>
 19        #include <va/va.h>
 20
 21typedef struct FFVAData {
 22    FF_LIBRARY_SYMBOL(vaInitialize)
 23    FF_LIBRARY_SYMBOL(vaTerminate)
 24    FF_LIBRARY_SYMBOL(vaMaxNumProfiles)
 25    FF_LIBRARY_SYMBOL(vaMaxNumEntrypoints)
 26    FF_LIBRARY_SYMBOL(vaQueryConfigProfiles)
 27    FF_LIBRARY_SYMBOL(vaQueryConfigEntrypoints)
 28    FF_LIBRARY_SYMBOL(vaQueryVendorString)
 29    FF_LIBRARY_SYMBOL(vaGetConfigAttributes)
 30} FFVAData;
 31
 32static FFCodecType vaProfileToType(VAProfile profile) {
 33    switch (profile) {
 34        case 11: // VAProfileH263Baseline
 35            return FF_CODEC_TYPE_H263;
 36
 37        case 12: // VAProfileJPEGBaseline
 38            return FF_CODEC_TYPE_MJPEG;
 39
 40        case 0: // VAProfileMPEG2Simple
 41        case 1: // VAProfileMPEG2Main
 42            return FF_CODEC_TYPE_MPEG2;
 43
 44        case 2: // VAProfileMPEG4Simple
 45        case 3: // VAProfileMPEG4AdvancedSimple
 46        case 4: // VAProfileMPEG4Main
 47            return FF_CODEC_TYPE_DIVX_XVID;
 48
 49        case 5:  // VAProfileH264Baseline
 50        case 6:  // VAProfileH264Main
 51        case 7:  // VAProfileH264High
 52        case 13: // VAProfileH264ConstrainedBaseline
 53        case 15: // VAProfileH264MultiviewHigh
 54        case 16: // VAProfileH264StereoHigh
 55        case 36: // VAProfileH264High10
 56        case 40: // VAProfileH264High422
 57            return FF_CODEC_TYPE_H264;
 58
 59        case 8:  // VAProfileVC1Simple
 60        case 9:  // VAProfileVC1Main
 61        case 10: // VAProfileVC1Advanced
 62            return FF_CODEC_TYPE_VC1;
 63
 64        case 14: // VAProfileVP8Version0_3
 65            return FF_CODEC_TYPE_VP8;
 66
 67        case 17: // VAProfileHEVCMain
 68        case 18: // VAProfileHEVCMain10
 69        case 23: // VAProfileHEVCMain12
 70        case 24: // VAProfileHEVCMain422_10
 71        case 25: // VAProfileHEVCMain422_12
 72        case 26: // VAProfileHEVCMain444
 73        case 27: // VAProfileHEVCMain444_10
 74        case 28: // VAProfileHEVCMain444_12
 75        case 29: // VAProfileHEVCSccMain
 76        case 30: // VAProfileHEVCSccMain10
 77        case 31: // VAProfileHEVCSccMain444
 78        case 34: // VAProfileHEVCSccMain444_10
 79            return FF_CODEC_TYPE_HEVC;
 80
 81        case 19: // VAProfileVP9Profile0
 82        case 20: // VAProfileVP9Profile1
 83        case 21: // VAProfileVP9Profile2
 84        case 22: // VAProfileVP9Profile3
 85            return FF_CODEC_TYPE_VP9;
 86
 87        case 32: // VAProfileAV1Profile0
 88        case 33: // VAProfileAV1Profile1
 89        case 39: // VAProfileAV1Profile2
 90            return FF_CODEC_TYPE_AV1;
 91
 92        case 37: // VAProfileVVCMain10
 93        case 38: // VAProfileVVCMultilayerMain10
 94            return FF_CODEC_TYPE_VVC;
 95
 96        default:
 97            return FF_CODEC_TYPE_UNKNOWN;
 98    }
 99}
100
101static FFCodecShowType vaGetEntrypointType(VAEntrypoint entrypoint) {
102    switch (entrypoint) {
103        case VAEntrypointVLD:
104        case VAEntrypointIDCT:
105        case VAEntrypointMoComp:
106            return FF_CODEC_SHOW_TYPE_DECODER;
107        case VAEntrypointEncSlice:
108        case VAEntrypointEncSliceLP:
109        case VAEntrypointFEI:
110            return FF_CODEC_SHOW_TYPE_ENCODER;
111        default:
112            return FF_CODEC_SHOW_TYPE_NONE;
113    }
114}
115
116static bool vaProfileHasOutput(
117    FFVAData* vaData,
118    VADisplay display,
119    int maxEntrypoints,
120    VAEntrypoint* entrypoints,
121    VAProfile profile,
122    FFCodecShowType entrypointType) {
123    int numEntrypoints = maxEntrypoints;
124    if (vaData->ffvaQueryConfigEntrypoints(display, profile, entrypoints, &numEntrypoints) != VA_STATUS_SUCCESS) {
125        return false;
126    }
127
128    for (int i = 0; i < numEntrypoints; ++i) {
129        if (vaGetEntrypointType(entrypoints[i]) != entrypointType) {
130            continue;
131        }
132
133        VAConfigAttrib attrib = {
134            .type = VAConfigAttribRTFormat,
135            .value = 0,
136        };
137
138        if (vaData->ffvaGetConfigAttributes(display, profile, entrypoints[i], &attrib, 1) == VA_STATUS_SUCCESS &&
139            attrib.value != VA_ATTRIB_NOT_SUPPORTED &&
140            attrib.value != 0) {
141            return true;
142        }
143    }
144
145    return false;
146}
147
148static bool vaDetectDisplay(VADisplay display, FFVAData* vaData, FFCodecOptions* options, FFlist* result, const char* api) {
149    int major = 0, minor = 0;
150    if (vaData->ffvaInitialize(display, &major, &minor) != VA_STATUS_SUCCESS) {
151        vaData->ffvaTerminate(display);
152        return false;
153    }
154
155    int maxProfiles = vaData->ffvaMaxNumProfiles(display);
156    int maxEntrypoints = vaData->ffvaMaxNumEntrypoints(display);
157    if (maxProfiles <= 0 || maxEntrypoints <= 0) {
158        vaData->ffvaTerminate(display);
159        return false;
160    }
161
162    FF_AUTO_FREE VAProfile* profiles = (VAProfile*) malloc(sizeof(VAProfile) * (size_t) maxProfiles);
163    FF_AUTO_FREE VAEntrypoint* entrypoints = (VAEntrypoint*) malloc(sizeof(VAEntrypoint) * (size_t) maxEntrypoints);
164
165    int numProfiles = maxProfiles;
166    if (vaData->ffvaQueryConfigProfiles(display, profiles, &numProfiles) != VA_STATUS_SUCCESS) {
167        vaData->ffvaTerminate(display);
168        return false;
169    }
170
171    FFCodecType decoderTypes = FF_CODEC_TYPE_NONE;
172    FFCodecType encoderTypes = FF_CODEC_TYPE_NONE;
173    for (int j = 0; j < numProfiles; ++j) {
174        FFCodecType type = vaProfileToType(profiles[j]);
175
176        bool hasDecoder = (options->showType & FF_CODEC_SHOW_TYPE_DECODER) &&
177            !(decoderTypes & type) &&
178            vaProfileHasOutput(
179                vaData,
180                display,
181                maxEntrypoints,
182                entrypoints,
183                profiles[j],
184                FF_CODEC_SHOW_TYPE_DECODER);
185
186        bool hasEncoder = (options->showType & FF_CODEC_SHOW_TYPE_ENCODER) &&
187            !(encoderTypes & type) &&
188            vaProfileHasOutput(
189                vaData,
190                display,
191                maxEntrypoints,
192                entrypoints,
193                profiles[j],
194                FF_CODEC_SHOW_TYPE_ENCODER);
195
196        if (!hasDecoder && !hasEncoder) {
197            continue;
198        }
199
200        if (hasDecoder) {
201            decoderTypes |= type;
202        }
203        if (hasEncoder) {
204            encoderTypes |= type;
205        }
206    }
207
208    if (decoderTypes != FF_CODEC_TYPE_NONE || encoderTypes != FF_CODEC_TYPE_NONE) {
209        FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result);
210        ffStrbufInitS(&item->gpu, vaData->ffvaQueryVendorString(display));
211        item->decoders = decoderTypes;
212        item->encoders = encoderTypes;
213        item->platformApi = api;
214    }
215
216    vaData->ffvaTerminate(display);
217
218    return true;
219}
220
221        #if FF_HAVE_VADRM
222            #include <va/va_drm.h>
223
224static const char* detectCodecByVaDrm(FFVAData* vaData, FFCodecOptions* options, FFlist* result) {
225    FF_LIBRARY_LOAD_MESSAGE(libvaDrm, "libva-drm" FF_LIBRARY_EXTENSION, 2)
226    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvaDrm, vaGetDisplayDRM)
227
228    const char* error = "No DRM device could initialize VA-API";
229
230    FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/dev/dri/");
231    if (dirp == nullptr) {
232        FF_DEBUG("opendir(/dev/dri/) failed: %s", strerror(errno));
233        return "opendir(/dev/dri/) failed";
234    }
235    int drifd = dirfd(dirp);
236
237    struct dirent* entry;
238    while ((entry = readdir(dirp)) != nullptr) {
239        if (entry->d_name[0] == '.' || !ffStrStartsWith(entry->d_name, "renderD")) {
240            continue;
241        }
242
243        FF_AUTO_CLOSE_FD int fd = openat(drifd, entry->d_name, O_RDWR | O_CLOEXEC);
244        if (fd < 0) {
245            continue;
246        }
247
248        VADisplay display = ffvaGetDisplayDRM(fd);
249        if (!display) {
250            continue;
251        }
252
253        if (vaDetectDisplay(display, vaData, options, result, "VA-API (DRM)")) {
254            error = nullptr;
255        }
256    }
257
258    return error;
259}
260        #endif
261        #if FF_HAVE_VAX11
262            #include <va/va_x11.h>
263
264static const char* detectCodecByVaX11(FFVAData* vaData, FFCodecOptions* options, FFlist* result) {
265    FF_LIBRARY_LOAD_MESSAGE(libX11, "libX11" FF_LIBRARY_EXTENSION, 6)
266    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XOpenDisplay)
267    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XCloseDisplay)
268
269    FF_LIBRARY_LOAD_MESSAGE(libvaX11, "libva-x11" FF_LIBRARY_EXTENSION, 2)
270    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvaX11, vaGetDisplay)
271
272    Display* x11Display = ffXOpenDisplay(nullptr);
273    if (!x11Display) {
274        FF_DEBUG("XOpenDisplay() failed");
275        return "XOpenDisplay() failed";
276    }
277
278    VADisplay display = ffvaGetDisplay(x11Display);
279    if (!display) {
280        FF_DEBUG("vaGetDisplay() failed");
281        ffXCloseDisplay(x11Display);
282        return "vaGetDisplay() failed";
283    }
284
285    if (!vaDetectDisplay(display, vaData, options, result, "VA-API (X11)")) {
286        ffXCloseDisplay(x11Display);
287        return "VA-API could not detect any supported codec via X11";
288    }
289    ffXCloseDisplay(x11Display);
290
291    return nullptr;
292}
293        #endif
294
295static const char* detectCodecByVa(FFCodecOptions* options, FFlist* result) {
296    FFVAData vaData = {};
297
298    FF_LIBRARY_LOAD_MESSAGE(libva, "libva" FF_LIBRARY_EXTENSION, 2)
299    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaInitialize)
300    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaTerminate)
301    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaMaxNumProfiles)
302    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaMaxNumEntrypoints)
303    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaQueryConfigProfiles)
304    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaQueryConfigEntrypoints)
305    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaQueryVendorString)
306    FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libva, vaData, vaGetConfigAttributes)
307
308        #if FF_HAVE_VADRM
309    if (detectCodecByVaDrm(&vaData, options, result) == nullptr) {
310        return nullptr;
311    }
312        #endif
313
314        #if FF_HAVE_VAX11
315    if (detectCodecByVaX11(&vaData, options, result) == nullptr) {
316        return nullptr;
317    }
318        #endif
319
320    return "VA-API could not detect any supported codec via DRM or X11";
321}
322    #endif
323
324    #if FF_HAVE_VDPAU
325        #include <vdpau/vdpau_x11.h>
326
327static const struct FFCodecVdpauCodec {
328    VdpDecoderProfile profile;
329    FFCodecType type;
330} FF_CODEC_VDPAU_CODECS[] = {
331    { 0, FF_CODEC_TYPE_MPEG1 },      // VDP_DECODER_PROFILE_MPEG1
332    { 1, FF_CODEC_TYPE_MPEG2 },      // VDP_DECODER_PROFILE_MPEG2_SIMPLE
333    { 2, FF_CODEC_TYPE_MPEG2 },      // VDP_DECODER_PROFILE_MPEG2_MAIN
334
335    { 6, FF_CODEC_TYPE_H264 },       // VDP_DECODER_PROFILE_H264_BASELINE
336    { 7, FF_CODEC_TYPE_H264 },       // VDP_DECODER_PROFILE_H264_MAIN
337    { 8, FF_CODEC_TYPE_H264 },       // VDP_DECODER_PROFILE_H264_HIGH
338    { 9, FF_CODEC_TYPE_VC1 },        // VDP_DECODER_PROFILE_VC1_SIMPLE
339    { 10, FF_CODEC_TYPE_VC1 },       // VDP_DECODER_PROFILE_VC1_MAIN
340    { 11, FF_CODEC_TYPE_VC1 },       // VDP_DECODER_PROFILE_VC1_ADVANCED
341    { 12, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_SP
342    { 13, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_MPEG4_PART2_ASP
343    { 14, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_QMOBILE
344    { 15, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_MOBILE
345    { 16, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HOME_THEATER
346    { 17, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX4_HD_1080P
347    { 18, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_QMOBILE
348    { 19, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_MOBILE
349    { 20, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HOME_THEATER
350    { 21, FF_CODEC_TYPE_DIVX_XVID }, // VDP_DECODER_PROFILE_DIVX5_HD_1080P
351    { 22, FF_CODEC_TYPE_H264 },      // VDP_DECODER_PROFILE_H264_CONSTRAINED_BASELINE
352    { 23, FF_CODEC_TYPE_H264 },      // VDP_DECODER_PROFILE_H264_EXTENDED
353    { 24, FF_CODEC_TYPE_H264 },      // VDP_DECODER_PROFILE_H264_PROGRESSIVE_HIGH
354    { 25, FF_CODEC_TYPE_H264 },      // VDP_DECODER_PROFILE_H264_CONSTRAINED_HIGH
355    { 26, FF_CODEC_TYPE_H264 },      // VDP_DECODER_PROFILE_H264_HIGH_444_PREDICTIVE
356    { 27, FF_CODEC_TYPE_VP9 },       // VDP_DECODER_PROFILE_VP9_PROFILE_0
357    { 28, FF_CODEC_TYPE_VP9 },       // VDP_DECODER_PROFILE_VP9_PROFILE_1
358    { 29, FF_CODEC_TYPE_VP9 },       // VDP_DECODER_PROFILE_VP9_PROFILE_2
359    { 30, FF_CODEC_TYPE_VP9 },       // VDP_DECODER_PROFILE_VP9_PROFILE_3
360
361    { 100, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN
362    { 101, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_10
363    { 102, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_STILL
364    { 103, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_12
365    { 104, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_444
366    { 105, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_444_10
367    { 106, FF_CODEC_TYPE_HEVC },     // VDP_DECODER_PROFILE_HEVC_MAIN_444_12
368    { 107, FF_CODEC_TYPE_AV1 },      // VDP_DECODER_PROFILE_AV1_MAIN
369    { 108, FF_CODEC_TYPE_AV1 },      // VDP_DECODER_PROFILE_AV1_HIGH
370    { 109, FF_CODEC_TYPE_AV1 },      // VDP_DECODER_PROFILE_AV1_PROFESSIONAL
371};
372
373static const char* detectCodecByVdpau(FFCodecOptions* options, FFlist* result) {
374    if (!(options->showType & FF_CODEC_SHOW_TYPE_DECODER)) {
375        return "VDPAU only supports decoding";
376    }
377
378    FF_LIBRARY_LOAD_MESSAGE(libX11, "libX11" FF_LIBRARY_EXTENSION, 6)
379    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XOpenDisplay)
380    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XCloseDisplay)
381    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libX11, XDefaultScreen)
382
383    FF_LIBRARY_LOAD_MESSAGE(libvdpau, "libvdpau" FF_LIBRARY_EXTENSION, 1)
384    FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libvdpau, vdp_device_create_x11)
385
386    Display* x11Display = ffXOpenDisplay(nullptr);
387    if (!x11Display) {
388        FF_DEBUG("XOpenDisplay() failed");
389        return "XOpenDisplay() failed";
390    }
391
392    VdpDevice device = VDP_INVALID_HANDLE;
393    VdpGetProcAddress* ffvdp_get_proc_address = nullptr;
394    VdpStatus ffvdp_device_create_x11_status = ffvdp_device_create_x11(x11Display, ffXDefaultScreen(x11Display), &device, &ffvdp_get_proc_address);
395    if (ffvdp_device_create_x11_status != VDP_STATUS_OK ||
396        device == VDP_INVALID_HANDLE ||
397        ffvdp_get_proc_address == nullptr) {
398        FF_DEBUG("vdp_device_create_x11() failed: status=%d, device=%x, getProcAddress=%p",
399            ffvdp_device_create_x11_status, (unsigned) device, (void*) ffvdp_get_proc_address);
400        ffXCloseDisplay(x11Display);
401        return "vdp_device_create_x11() failed";
402    }
403
404    VdpDeviceDestroy* ffvdp_device_destroy = nullptr;
405    VdpDecoderQueryCapabilities* ffvdp_decoder_query_capabilities = nullptr;
406    if (ffvdp_get_proc_address(device, VDP_FUNC_ID_DEVICE_DESTROY, (void**) &ffvdp_device_destroy) != VDP_STATUS_OK ||
407        ffvdp_get_proc_address(device, VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, (void**) &ffvdp_decoder_query_capabilities) != VDP_STATUS_OK ||
408        ffvdp_device_destroy == nullptr ||
409        ffvdp_decoder_query_capabilities == nullptr) {
410        FF_DEBUG("vdp_device_create_x11() did not provide VDP_FUNC_ID_DEVICE_DESTROY / VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES");
411        if (ffvdp_device_destroy) {
412            ffvdp_device_destroy(device);
413        }
414        ffXCloseDisplay(x11Display);
415        return "ffvdp_get_proc_address() failed";
416    }
417
418    FFCodecType decoderTypes = FF_CODEC_TYPE_NONE;
419
420    for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_VDPAU_CODECS); ++i) {
421        const struct FFCodecVdpauCodec* codec = &FF_CODEC_VDPAU_CODECS[i];
422        if (decoderTypes & codec->type) {
423            continue;
424        }
425        VdpBool isSupported = VDP_FALSE;
426        uint32_t maxLevel = 0, maxMacroblocks = 0, maxWidth = 0, maxHeight = 0;
427        if (ffvdp_decoder_query_capabilities(device, codec->profile, &isSupported, &maxLevel, &maxMacroblocks, &maxWidth, &maxHeight) != VDP_STATUS_OK ||
428            !isSupported) {
429            continue;
430        }
431
432        decoderTypes |= codec->type;
433    }
434
435    ffvdp_device_destroy(device);
436    ffXCloseDisplay(x11Display);
437
438    if (decoderTypes == FF_CODEC_TYPE_NONE) {
439        return nullptr;
440    }
441
442    FFCodecResult* item = FF_LIST_ADD(FFCodecResult, *result);
443    ffStrbufInit(&item->gpu);
444    const char* driver = getenv("VDPAU_DRIVER");
445    if (driver && *driver) {
446        ffStrbufSetS(&item->gpu, driver);
447    } else {
448        ffStrbufSetStatic(&item->gpu, "Default");
449    }
450    item->decoders = decoderTypes;
451    item->encoders = FF_CODEC_TYPE_NONE;
452    item->platformApi = "VDPAU";
453
454    return nullptr;
455}
456    #endif
457
458const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) {
459    FF_SUPPRESS_IO();
460
461    #if FF_HAVE_VA
462    if (detectCodecByVa(options, result) == nullptr) {
463        return nullptr;
464    }
465    #endif
466    #if FF_HAVE_VDPAU
467    if (detectCodecByVdpau(options, result) == nullptr) {
468        return nullptr;
469    }
470    #endif
471
472    FF_DEBUG("Neither libva nor libvdpau could initialize a display");
473    return "Both libva and libvdpau fail to initialize";
474}
475
476#else
477
478const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list of FFCodecResult */) {
479    FF_UNUSED(options, result);
480    return "Fastfetch was built without VA-API / VDPAU headers";
481}
482
483#endif