diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a9764e..af1cdde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -218,6 +218,7 @@ endif() set(HAVE_PAM ${ENABLE_PAM}) option(DEBUG_FFMPEG "Debug ffmpeg" OFF) +option(ENABLE_LIBYUV "Enable libyuv conversion support" ON) # Check for SSE2 check_cxx_compiler_flag(-msse2 COMPILER_SUPPORTS_SSE2) diff --git a/common/rfb/CMakeLists.txt b/common/rfb/CMakeLists.txt index a00c836..82f4a0c 100644 --- a/common/rfb/CMakeLists.txt +++ b/common/rfb/CMakeLists.txt @@ -152,6 +152,14 @@ else () set(RFB_LIBRARIES ${RFB_LIBRARIES} ${TBB_LIBRARIES}) endif () +if (ENABLE_LIBYUV) + find_library(LIBYUV_LIBRARY NAMES yuv) + #pkg_check_modules(LIBYUV libyuv) + if (LIBYUV_FOUND) + set(RFB_LIBRARIES ${RFB_LIBRARIES} ${LIBYUV_LIBRARY}) + endif () +endif () + add_library(rfb STATIC ${RFB_SOURCES}) target_include_directories(rfb PRIVATE @@ -163,6 +171,7 @@ target_include_directories(rfb PRIVATE ${FFMPEG_INCLUDE_DIRS} ${TBB_INCLUDE_DIRS} ${CPUID_INCLUDE_DIRS} + ${LIBYUV_INCLUDE_DIR} ) set(RFB_LIBRARIES ${RFB_LIBRARIES} tinyxml2_objs ${TBB_LIBRARIES} ${CPUID_LIBRARIES} ${FMT_LIBRARIES} ${VIDEO_ACCELERATION_LIBRARIES}) diff --git a/common/rfb/encoders/EncoderProbe.cpp b/common/rfb/encoders/EncoderProbe.cpp index db5aa9c..f9dc328 100644 --- a/common/rfb/encoders/EncoderProbe.cpp +++ b/common/rfb/encoders/EncoderProbe.cpp @@ -1,5 +1,5 @@ /* Copyright (C) 2025 Kasm. All Rights Reserved. -* + * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -17,11 +17,12 @@ */ #include "EncoderProbe.h" #include +#include #include #include #include #include "KasmVideoConstants.h" -#include +#include "KasmVideoEncoders.h" extern "C" { #include @@ -33,24 +34,6 @@ extern "C" { namespace rfb::video_encoders { static LogWriter vlog("EncoderProbe"); - struct EncoderCandidate { - KasmVideoEncoders::Encoder encoder; - AVCodecID codec_id; - AVHWDeviceType hw_type; - }; - - static std::array candidates = { - { - //{KasmVideoEncoders::Encoder::h264_nvenc, AV_CODEC_ID_H264, AV_HWDEVICE_TYPE_VAAPI} - //{KasmVideoEncoders::Encoder::av1_vaapi, AV_CODEC_ID_AV1, AV_HWDEVICE_TYPE_VAAPI}, - //{KasmVideoEncoders::Encoder::hevc_vaapi, AV_CODEC_ID_HEVC, AV_HWDEVICE_TYPE_VAAPI}, // h265 - EncoderCandidate{KasmVideoEncoders::Encoder::h264_ffmpeg_vaapi, AV_CODEC_ID_H264, AV_HWDEVICE_TYPE_VAAPI}, - // EncoderCandidate{KasmVideoEncoders::Encoder::h264_software, AV_CODEC_ID_H264, AV_HWDEVICE_TYPE_NONE} - //{KasmVideoEncoders::Encoder::av1_software, AV_CODEC_ID_AV1, AV_HWDEVICE_TYPE_NONE}, - //{KasmVideoEncoders::Encoder::h265_software, AV_CODEC_ID_HEVC, AV_HWDEVICE_TYPE_NONE}, - } - }; - EncoderProbe::EncoderProbe(FFmpeg &ffmpeg_, const std::vector &parsed_encoders, const char *dri_node) : ffmpeg(ffmpeg_) { if (!ffmpeg.is_available()) { @@ -69,7 +52,18 @@ namespace rfb::video_encoders { const auto encoders = SupportedVideoEncoders::map_encoders(parsed_encoders); debug_encoders("CLI-specified video codecs", encoders); - available_encoders = probe(dri_node); + static std::array candidates = { + { + EncoderCandidate{KasmVideoEncoders::Encoder::h264_ffmpeg_vaapi, AV_CODEC_ID_H264, AV_HWDEVICE_TYPE_VAAPI}, + EncoderCandidate{KasmVideoEncoders::Encoder::h265_ffmpeg_vaapi, AV_CODEC_ID_HEVC, AV_HWDEVICE_TYPE_VAAPI}, + // EncoderCandidate{KasmVideoEncoders::Encoder::av1_ffmpeg_vaapi, AV_CODEC_ID_AV1, AV_HWDEVICE_TYPE_VAAPI}, + EncoderCandidate{KasmVideoEncoders::Encoder::h264_software, AV_CODEC_ID_H264, AV_HWDEVICE_TYPE_NONE}, + EncoderCandidate{KasmVideoEncoders::Encoder::h265_software, AV_CODEC_ID_HEVC, AV_HWDEVICE_TYPE_NONE}, + // EncoderCandidate{KasmVideoEncoders::Encoder::av1_software, AV_CODEC_ID_AV1, AV_HWDEVICE_TYPE_NONE}, + } + }; + + available_encoders = probe(dri_node, candidates); debug_encoders("Available encoders", available_encoders); available_encoders = SupportedVideoEncoders::filter_available_encoders(encoders, available_encoders); @@ -83,7 +77,7 @@ namespace rfb::video_encoders { best_encoder = available_encoders.front(); } - KasmVideoEncoders::Encoders EncoderProbe::probe(const char *dri_node) { + KasmVideoEncoders::Encoders EncoderProbe::probe(const char *dri_node, std::span candidates) { KasmVideoEncoders::Encoders result{}; for (const auto &encoder_candidate: candidates) { const AVCodec *codec = ffmpeg.avcodec_find_encoder_by_name(KasmVideoEncoders::to_string(encoder_candidate.encoder)); diff --git a/common/rfb/encoders/EncoderProbe.h b/common/rfb/encoders/EncoderProbe.h index 1309af3..77624cf 100644 --- a/common/rfb/encoders/EncoderProbe.h +++ b/common/rfb/encoders/EncoderProbe.h @@ -18,7 +18,7 @@ #pragma once #include -#include "KasmVideoConstants.h" +#include #include "SupportedVideoEncoders.h" #include "rfb/ffmpeg.h" @@ -29,8 +29,14 @@ namespace rfb::video_encoders { std::string drm_device_path; FFmpeg &ffmpeg; + struct EncoderCandidate { + KasmVideoEncoders::Encoder encoder; + AVCodecID codec_id; + AVHWDeviceType hw_type; + }; + explicit EncoderProbe(FFmpeg &ffmpeg, const std::vector &parsed_encoders, const char *dri_node); - KasmVideoEncoders::Encoders probe(const char *dri_node); + KasmVideoEncoders::Encoders probe(const char *dri_node, std::span candidates); public: EncoderProbe(const EncoderProbe &) = delete; diff --git a/common/rfb/encoders/FFMPEGVAAPIEncoder.cxx b/common/rfb/encoders/FFMPEGVAAPIEncoder.cxx index d02ae5b..d8a4d33 100644 --- a/common/rfb/encoders/FFMPEGVAAPIEncoder.cxx +++ b/common/rfb/encoders/FFMPEGVAAPIEncoder.cxx @@ -23,13 +23,13 @@ #include "EncoderProbe.h" #include "rfb/LogWriter.h" -extern "C" { -#include -} - +#include "EncoderConfiguration.h" #include "KasmVideoConstants.h" #include "rfb/encodings.h" #include +#if defined LIBYUV_CONVERSION +#include +#endif static rfb::LogWriter vlog("FFMPEGVAAPIEncoder"); @@ -91,6 +91,7 @@ namespace rfb { ctx->delay = 0; ctx->flags |= AV_CODEC_FLAG_LOW_DELAY; +//TODO if (ffmpeg.av_opt_set(ctx->priv_data, "async_depth", "1", 0) < 0) { vlog.info("Cannot set async_depth"); } @@ -163,15 +164,14 @@ namespace rfb { vlog.error("Failed to open codec (%s). Error code: %d", ffmpeg.get_error_description(err).c_str(), err); return false; } - - auto *sws_ctx = ffmpeg.sws_getContext( - width, height, AV_PIX_FMT_RGB32, params.width, params.height, AV_PIX_FMT_NV12, SWS_BILINEAR, nullptr, nullptr, nullptr); - if (!sws_ctx) { - vlog.error("Could not initialize the conversion context"); - return false; - } - - sws_guard.reset(sws_ctx); +#if defined(FFMPEG_FILTER) + const char* filters = "format=nv12,hwupload"; // Or "scale_vaapi=format=nv12" for explicit VAAPI scaler + ffmpeg.avfilter_graph_parse_ptr(filter_graph, filters, &inputs, &outputs, nullptr); + ffmpeg.av_buffersrc_add_frame_flags(buffersrc_ctx, rgb_frame, AV_BUFFERSRC_FLAG_PUSH); + AVFrame* hw_frame = av_frame_alloc(); + ffmpeg.av_buffersink_get_frame_flags(buffersink_ctx, hw_frame, AV_BUFFERSINK_FLAG_NO_REQUEST); + // ffmpeg.avcodec_send_frame(enc_ctx, hw_frame); +#endif return true; } @@ -217,14 +217,21 @@ namespace rfb { frame->pict_type = AV_PICTURE_TYPE_NONE; } - const uint8_t *src_data[1] = {buffer}; - const int src_line_size[1] = {stride * bpp}; // RGB has bpp bytes per pixel + const int src_stride_bytes = stride * bpp; + + vlog.debug("render(): width=%d, height=%d, dst_width=%d, dst_height=%d, stride=%d pixels, stride_bytes=%d, bpp=%d", + width, height, dst_width, dst_height, stride, src_stride_bytes, bpp); int err{}; - if (err = ffmpeg.sws_scale(sws_guard.get(), src_data, src_line_size, 0, height, frame->data, frame->linesize); err < 0) { - vlog.error("Error (%s) while scaling image. Error code: %d", ffmpeg.get_error_description(err).c_str(), err); + +#if defined(LIBYUV_CONVERSION) + if (err = libyuv::ARGBToNV12(buffer, src_stride_bytes, frame->data[0], frame->linesize[0], + frame->data[1], frame->linesize[1], dst_width, dst_height); err != 0) { + vlog.error("libyuv::ARGBToNV12 failed with code: %d", err); return false; } +#endif + frame->pts = pts++; diff --git a/common/rfb/encoders/FFMPEGVAAPIEncoder.h b/common/rfb/encoders/FFMPEGVAAPIEncoder.h index ebc77d2..a1bdc67 100644 --- a/common/rfb/encoders/FFMPEGVAAPIEncoder.h +++ b/common/rfb/encoders/FFMPEGVAAPIEncoder.h @@ -32,7 +32,6 @@ class FFMPEGVAAPIEncoder final : public VideoEncoder { FFmpeg::FrameGuard hw_frame_guard; FFmpeg::PacketGuard pkt_guard; FFmpeg::ContextGuard ctx_guard; - FFmpeg::SwsContextGuard sws_guard; FFmpeg::BufferGuard hw_device_ctx_guard; FFmpeg::BufferGuard hw_frames_ref_guard; diff --git a/common/rfb/encoders/KasmVideoConstants.h b/common/rfb/encoders/KasmVideoConstants.h index 4ec316e..fb612c7 100644 --- a/common/rfb/encoders/KasmVideoConstants.h +++ b/common/rfb/encoders/KasmVideoConstants.h @@ -21,11 +21,15 @@ #include namespace rfb { - static constexpr unsigned int SupportedEncoderCount = 3; + // NOTE: SupportedEncoderCount represents the number of actively supported codec families (H264, H265, AV1) + static constexpr unsigned int SupportedEncoderCount = 5; + // Compression control static constexpr unsigned int kasmVideoH264 = 0x01 << 4; // H.264 encoding static constexpr unsigned int kasmVideoH265 = 0x02 << 4; // H.265 encoding static constexpr unsigned int kasmVideoAV1 = 0x03 << 4; // AV1 encoding + static constexpr unsigned int kasmVideoVP8 = 0x04 << 4; // VP8 encoding (not yet supported) + static constexpr unsigned int kasmVideoVP9 = 0x05 << 4; // VP9 encoding (not yet supported) static constexpr unsigned int kasmVideoSkip = 0x00 << 4; // Skip frame static constexpr auto drm_device_paths = std::to_array({ diff --git a/common/rfb/ffmpeg.h b/common/rfb/ffmpeg.h index ddc3ada..dd6b7e1 100644 --- a/common/rfb/ffmpeg.h +++ b/common/rfb/ffmpeg.h @@ -26,6 +26,7 @@ extern "C" { #include #include #include +#include } #define STR_HELPER(x) #x @@ -138,6 +139,10 @@ class FFmpeg final { using avcodec_close_func = int (*)(AVCodecContext *avctx); using av_codec_is_encoder_func = int (*)(const AVCodec *codec); + // libavfilter + using avfilter_graph_parse_ptr_func = int (*)(AVFilterGraph *graph, const char *filters, AVFilterInOut **inputs, + AVFilterInOut **outputs, void *log_ctx); + struct DlHandler { void operator()(void *handle) const { dlclose(handle); @@ -196,10 +201,14 @@ class FFmpeg final { avcodec_close_func avcodec_close_f{}; av_codec_is_encoder_func av_codec_is_encoder_f{}; + // libavfilter + avfilter_graph_parse_ptr_func avfilter_graph_parse_ptr_f{}; + DlHandlerGuard libavformat{}; DlHandlerGuard libavutil{}; DlHandlerGuard libswscale{}; DlHandlerGuard libavcodec{}; + DlHandlerGuard libavfilter{}; FFmpeg(); ~FFmpeg() = default;