|
|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
#include "stream_video_encoder.h" |
|
|
|
|
#include "live_video_encoder.h" |
|
|
|
|
#include "debug_config.h" |
|
|
|
|
#include <QPainter> |
|
|
|
|
#include <algorithm> |
|
|
|
|
@ -14,9 +14,9 @@ extern "C" {
|
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct StreamVideoEncoder::Impl { |
|
|
|
|
struct LiveVideoEncoder::Impl { |
|
|
|
|
QString error, name; |
|
|
|
|
StreamVideoConfig config; |
|
|
|
|
LiveVideoConfig config; |
|
|
|
|
#ifdef HAVE_FFMPEG |
|
|
|
|
AVCodecContext* codec = nullptr; |
|
|
|
|
AVBSFContext* filter = nullptr; |
|
|
|
|
@ -29,12 +29,12 @@ struct StreamVideoEncoder::Impl {
|
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
}; |
|
|
|
|
StreamVideoEncoder::StreamVideoEncoder() : m(std::make_unique<Impl>()) {} |
|
|
|
|
StreamVideoEncoder::~StreamVideoEncoder() = default; |
|
|
|
|
void StreamVideoEncoder::close() { m = std::make_unique<Impl>(); } |
|
|
|
|
QString StreamVideoEncoder::error() const { return m->error; } |
|
|
|
|
QString StreamVideoEncoder::name() const { return m->name; } |
|
|
|
|
QString StreamVideoEncoder::codecName(int codec) { |
|
|
|
|
LiveVideoEncoder::LiveVideoEncoder() : m(std::make_unique<Impl>()) {} |
|
|
|
|
LiveVideoEncoder::~LiveVideoEncoder() = default; |
|
|
|
|
void LiveVideoEncoder::close() { m = std::make_unique<Impl>(); } |
|
|
|
|
QString LiveVideoEncoder::error() const { return m->error; } |
|
|
|
|
QString LiveVideoEncoder::name() const { return m->name; } |
|
|
|
|
QString LiveVideoEncoder::codecName(int codec) { |
|
|
|
|
static const char* names[] = {"", "H.264", "H.265", "VP9", "AV1", "H.266"}; |
|
|
|
|
return codec >= 1 && codec <= 5 ? QString::fromLatin1(names[codec]) : QString(); |
|
|
|
|
} |
|
|
|
|
@ -69,7 +69,7 @@ AVPixelFormat softwareFormat(const AVCodec* codec) {
|
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
bool StreamVideoEncoder::open(const StreamVideoConfig& config) { |
|
|
|
|
bool LiveVideoEncoder::open(const LiveVideoConfig& config) { |
|
|
|
|
close(); |
|
|
|
|
if (config.width < 16 || config.height < 16 || config.width > 4096 || config.height > 4096 |
|
|
|
|
|| config.width % 2 || config.height % 2 || config.fps < 1 || config.fps > 60 |
|
|
|
|
@ -152,7 +152,7 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
} |
|
|
|
|
if (!pass) { |
|
|
|
|
QImage probe(config.width, config.height, QImage::Format_RGB32); probe.fill(Qt::black); |
|
|
|
|
QList<StreamEncodedFrame> output; |
|
|
|
|
QList<LiveEncodedFrame> output; |
|
|
|
|
if (!encode(probe, 0, true, output) || output.isEmpty() || !output.front().key) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder probe lacks immediate KEY name=%s error=%s", name.constData(), qPrintable(m->error)); |
|
|
|
|
break; |
|
|
|
|
@ -176,7 +176,7 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encoder open failed: %s", qPrintable(m->error)); return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList<StreamEncodedFrame>& output) { |
|
|
|
|
bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList<LiveEncodedFrame>& output) { |
|
|
|
|
#ifdef HAVE_FFMPEG |
|
|
|
|
if (!m->codec || !m->frame || image.isNull() || pts > INT64_MAX) { |
|
|
|
|
m->error = "Invalid video frame"; DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop encode invalid frame/state"); return false; |
|
|
|
|
@ -217,7 +217,7 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi
|
|
|
|
|
if (result >= 0) result = av_bsf_receive_packet(m->filter, packet); |
|
|
|
|
if (result < 0) break; |
|
|
|
|
} |
|
|
|
|
StreamEncodedFrame encoded; encoded.key = (packet->flags & AV_PKT_FLAG_KEY) != 0; |
|
|
|
|
LiveEncodedFrame encoded; encoded.key = (packet->flags & AV_PKT_FLAG_KEY) != 0; |
|
|
|
|
encoded.pts = packet->pts >= 0 ? static_cast<uint64_t>(packet->pts) : pts; |
|
|
|
|
if (key) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop requested KEY name=%s input_PTS=%llu output_PTS=%llu key=%d flags=%x bytes=%d", |
|
|
|
|
qPrintable(m->name), (unsigned long long)pts, (unsigned long long)encoded.pts, encoded.key, packet->flags, packet->size); |
|
|
|
|
@ -246,7 +246,7 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi
|
|
|
|
|
#endif |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QList<int> StreamVideoEncoder::available() { |
|
|
|
|
QList<int> LiveVideoEncoder::available() { |
|
|
|
|
QList<int> result; |
|
|
|
|
for (int codec = 1; codec <= 5; codec++) { |
|
|
|
|
#ifdef HAVE_FFMPEG |
|
|
|
|
@ -254,13 +254,13 @@ QList<int> StreamVideoEncoder::available() {
|
|
|
|
|
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 |
|
|
|
|
StreamVideoEncoder encoder; StreamVideoConfig config; config.width = 320; config.height = 180; config.codec = codec; |
|
|
|
|
LiveVideoEncoder encoder; LiveVideoConfig config; config.width = 320; config.height = 180; config.codec = codec; |
|
|
|
|
if (encoder.open(config)) result.push_back(codec); |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool StreamVideoEncoder::setBitrate(int bitrate) { |
|
|
|
|
bool LiveVideoEncoder::setBitrate(int bitrate) { |
|
|
|
|
if (bitrate < 64000 || bitrate > 16000000) { DEBUG_ERROR(DEBUG_CATEGORY_VIDEO, "desktop live bitrate invalid value=%d", bitrate); return false; } |
|
|
|
|
#ifdef HAVE_FFMPEG |
|
|
|
|
// libx264 перечитывает эти три значения через reconfig_encoder при подаче следующего кадра.
|