diff --git a/tools/chatgui/src/live_video_encoder.cpp b/tools/chatgui/src/live_video_encoder.cpp index 899c96b3..80c20740 100644 --- a/tools/chatgui/src/live_video_encoder.cpp +++ b/tools/chatgui/src/live_video_encoder.cpp @@ -53,6 +53,10 @@ QList encoders(int codec) { default: return {}; } } +bool matchesBackend(const QByteArray& name, LiveVideoEncoder::Backend backend) { + bool hardware = name.endsWith("_nvenc") || name.endsWith("_qsv") || name.endsWith("_amf") || name.endsWith("_vaapi"); + return backend == LiveVideoEncoder::Backend::Automatic || hardware == (backend == LiveVideoEncoder::Backend::HardwareOnly); +} AVPixelFormat softwareFormat(const AVCodec* codec) { const AVPixelFormat* formats = nullptr; #if LIBAVCODEC_VERSION_MAJOR >= 61 @@ -106,7 +110,7 @@ bool verifiedProfile(const LiveVideoConfig& config, const QList 4096 || config.height > 4096 @@ -120,6 +124,7 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config) { } #ifdef HAVE_FFMPEG for (const auto& name : encoders(config.codec)) { + if (!matchesBackend(name, backend)) continue; if (!config.backend.isEmpty() && name != config.backend.toLatin1()) continue; if (constrained && name != "libx264" && name != "libx265") continue; const AVCodec* encoder = avcodec_find_encoder_by_name(name.constData()); @@ -212,11 +217,13 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config) { QImage probe(config.width, config.height, QImage::Format_RGB32); probe.fill(Qt::black); QList output; if (!encode(probe, 0, true, output) || output.isEmpty() || !output.front().key) { + if (m->error.isEmpty()) m->error = "Encoder did not produce an immediate key frame"; DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder probe lacks immediate KEY name=%s error=%s", name.constData(), qPrintable(m->error)); break; } output.clear(); if (!encode(probe, 1000000 / config.fps, true, output) || output.isEmpty() || !output.front().key) { + if (m->error.isEmpty()) m->error = "Encoder cannot produce an independent key frame on request"; DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder cannot force an independent KEY name=%s error=%s", name.constData(), qPrintable(m->error)); break; } @@ -236,7 +243,8 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config) { #else m->error = "FFmpeg is unavailable"; #endif - DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encoder open failed: %s", qPrintable(m->error)); return false; + DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encoder open failed codec=%d backend=%d: %s", + config.codec, int(backend), qPrintable(m->error)); return false; } bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList& output) { @@ -309,17 +317,18 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList #endif } -QList LiveVideoEncoder::available() { +QList LiveVideoEncoder::available(Backend backend) { QList result; - for (int codec = 1; codec <= 5; codec++) { #ifdef HAVE_FFMPEG - bool installed = false; - for (const auto& name : encoders(codec)) if (avcodec_find_encoder_by_name(name.constData())) { installed = true; break; } - if (!installed) { DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop codec has no installed encoder codec=%d", codec); continue; } -#endif - LiveVideoEncoder encoder; LiveVideoConfig config; config.width = 320; config.height = 180; config.codec = codec; - if (encoder.open(config)) result.push_back(codec); + for (int codec = 1; codec <= 5; codec++) { + for (const auto& name : encoders(codec)) { + if (!matchesBackend(name, backend) || !avcodec_find_encoder_by_name(name.constData())) continue; + result.push_back(codec); break; + } } +#else + Q_UNUSED(backend); +#endif return result; } diff --git a/tools/chatgui/src/live_video_encoder.h b/tools/chatgui/src/live_video_encoder.h index b1adb86b..5123870a 100644 --- a/tools/chatgui/src/live_video_encoder.h +++ b/tools/chatgui/src/live_video_encoder.h @@ -1,6 +1,6 @@ /* FFmpeg live encoder: один worker владеет контекстом, конвертером и decoder configuration. * open/encode/close последовательны; каждый KEY содержит uAV2 envelope. Пакеты имеют capture PTS. - * Encoder открывается по фактическим возможностям; GPU имеет приоритет, затем software того же кодека. */ + * Backend ограничивает перебор: HardwareOnly никогда не открывает software encoder. */ #pragma once #include #include @@ -20,15 +20,16 @@ struct LiveVideoConfig { struct LiveEncodedFrame { QByteArray data; uint64_t pts = 0; bool key = false; }; class LiveVideoEncoder { public: + enum class Backend { Automatic, HardwareOnly, SoftwareOnly }; LiveVideoEncoder(); ~LiveVideoEncoder(); - bool open(const LiveVideoConfig& config); + bool open(const LiveVideoConfig& config, Backend backend = Backend::Automatic); bool setBitrate(int bitrate); // true — изменение на следующем кадре; false — нужна смена видеоконтекста void close(); bool encode(const QImage& image, uint64_t pts, bool key, QList& output); QString error() const; QString name() const; - static QList available(); // реальная проба открытия и первого KEY; вызывать вне GUI + static QList available(Backend backend = Backend::Automatic); // Установленные кодеки, без открытия и кодирования. static QString codecName(int codec); private: struct Impl;