Browse Source

Separate hardware and software live encoder selection

master
evgeny 3 days ago
parent
commit
86da11b2e1
  1. 29
      tools/chatgui/src/live_video_encoder.cpp
  2. 7
      tools/chatgui/src/live_video_encoder.h

29
tools/chatgui/src/live_video_encoder.cpp

@ -53,6 +53,10 @@ QList<QByteArray> encoders(int codec) {
default: return {}; 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) { AVPixelFormat softwareFormat(const AVCodec* codec) {
const AVPixelFormat* formats = nullptr; const AVPixelFormat* formats = nullptr;
#if LIBAVCODEC_VERSION_MAJOR >= 61 #if LIBAVCODEC_VERSION_MAJOR >= 61
@ -106,7 +110,7 @@ bool verifiedProfile(const LiveVideoConfig& config, const QList<LiveEncodedFrame
} }
#endif #endif
bool LiveVideoEncoder::open(const LiveVideoConfig& config) { bool LiveVideoEncoder::open(const LiveVideoConfig& config, Backend backend) {
close(); close();
bool constrained = config.profile != LiveVideoProfile::Automatic; bool constrained = config.profile != LiveVideoProfile::Automatic;
if (config.width < 16 || config.height < 16 || config.width > 4096 || config.height > 4096 if (config.width < 16 || config.height < 16 || config.width > 4096 || config.height > 4096
@ -120,6 +124,7 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config) {
} }
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
for (const auto& name : encoders(config.codec)) { for (const auto& name : encoders(config.codec)) {
if (!matchesBackend(name, backend)) continue;
if (!config.backend.isEmpty() && name != config.backend.toLatin1()) continue; if (!config.backend.isEmpty() && name != config.backend.toLatin1()) continue;
if (constrained && name != "libx264" && name != "libx265") continue; if (constrained && name != "libx264" && name != "libx265") continue;
const AVCodec* encoder = avcodec_find_encoder_by_name(name.constData()); 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); QImage probe(config.width, config.height, QImage::Format_RGB32); probe.fill(Qt::black);
QList<LiveEncodedFrame> output; QList<LiveEncodedFrame> output;
if (!encode(probe, 0, true, output) || output.isEmpty() || !output.front().key) { 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)); DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder probe lacks immediate KEY name=%s error=%s", name.constData(), qPrintable(m->error));
break; break;
} }
output.clear(); output.clear();
if (!encode(probe, 1000000 / config.fps, true, output) || output.isEmpty() || !output.front().key) { 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)); DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder cannot force an independent KEY name=%s error=%s", name.constData(), qPrintable(m->error));
break; break;
} }
@ -236,7 +243,8 @@ bool LiveVideoEncoder::open(const LiveVideoConfig& config) {
#else #else
m->error = "FFmpeg is unavailable"; m->error = "FFmpeg is unavailable";
#endif #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<LiveEncodedFrame>& output) { bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList<LiveEncodedFrame>& output) {
@ -309,17 +317,18 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
#endif #endif
} }
QList<int> LiveVideoEncoder::available() { QList<int> LiveVideoEncoder::available(Backend backend) {
QList<int> result; QList<int> result;
for (int codec = 1; codec <= 5; codec++) {
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
bool installed = false; for (int codec = 1; codec <= 5; codec++) {
for (const auto& name : encoders(codec)) if (avcodec_find_encoder_by_name(name.constData())) { installed = true; break; } for (const auto& name : encoders(codec)) {
if (!installed) { DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop codec has no installed encoder codec=%d", codec); continue; } if (!matchesBackend(name, backend) || !avcodec_find_encoder_by_name(name.constData())) continue;
#endif result.push_back(codec); break;
LiveVideoEncoder encoder; LiveVideoConfig config; config.width = 320; config.height = 180; config.codec = codec; }
if (encoder.open(config)) result.push_back(codec);
} }
#else
Q_UNUSED(backend);
#endif
return result; return result;
} }

7
tools/chatgui/src/live_video_encoder.h

@ -1,6 +1,6 @@
/* FFmpeg live encoder: один worker владеет контекстом, конвертером и decoder configuration. /* FFmpeg live encoder: один worker владеет контекстом, конвертером и decoder configuration.
* open/encode/close последовательны; каждый KEY содержит uAV2 envelope. Пакеты имеют capture PTS. * open/encode/close последовательны; каждый KEY содержит uAV2 envelope. Пакеты имеют capture PTS.
* Encoder открывается по фактическим возможностям; GPU имеет приоритет, затем software того же кодека. */ * Backend ограничивает перебор: HardwareOnly никогда не открывает software encoder. */
#pragma once #pragma once
#include <QByteArray> #include <QByteArray>
#include <QImage> #include <QImage>
@ -20,15 +20,16 @@ struct LiveVideoConfig {
struct LiveEncodedFrame { QByteArray data; uint64_t pts = 0; bool key = false; }; struct LiveEncodedFrame { QByteArray data; uint64_t pts = 0; bool key = false; };
class LiveVideoEncoder { class LiveVideoEncoder {
public: public:
enum class Backend { Automatic, HardwareOnly, SoftwareOnly };
LiveVideoEncoder(); LiveVideoEncoder();
~LiveVideoEncoder(); ~LiveVideoEncoder();
bool open(const LiveVideoConfig& config); bool open(const LiveVideoConfig& config, Backend backend = Backend::Automatic);
bool setBitrate(int bitrate); // true — изменение на следующем кадре; false — нужна смена видеоконтекста bool setBitrate(int bitrate); // true — изменение на следующем кадре; false — нужна смена видеоконтекста
void close(); void close();
bool encode(const QImage& image, uint64_t pts, bool key, QList<LiveEncodedFrame>& output); bool encode(const QImage& image, uint64_t pts, bool key, QList<LiveEncodedFrame>& output);
QString error() const; QString error() const;
QString name() const; QString name() const;
static QList<int> available(); // реальная проба открытия и первого KEY; вызывать вне GUI static QList<int> available(Backend backend = Backend::Automatic); // Установленные кодеки, без открытия и кодирования.
static QString codecName(int codec); static QString codecName(int codec);
private: private:
struct Impl; struct Impl;

Loading…
Cancel
Save