|
|
|
|
@ -79,6 +79,8 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
for (const auto& name : encoders(config.codec)) { |
|
|
|
|
const AVCodec* encoder = avcodec_find_encoder_by_name(name.constData()); |
|
|
|
|
if (!encoder) continue; |
|
|
|
|
// Проба не оставляет references/PTS в live-контексте: после двух KEY encoder открывается заново.
|
|
|
|
|
for (int pass = 0; pass < 2; pass++) { |
|
|
|
|
close(); m->config = config; m->name = QString::fromLatin1(name); |
|
|
|
|
m->codec = avcodec_alloc_context3(encoder); |
|
|
|
|
if (!m->codec) { m->error = "Video encoder allocation failed"; break; } |
|
|
|
|
@ -108,6 +110,7 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
AVDictionary* options = nullptr; |
|
|
|
|
if (name == "libx264" || name == "libx265") { |
|
|
|
|
av_dict_set(&options, "preset", "ultrafast", 0); av_dict_set(&options, "tune", "zerolatency", 0); |
|
|
|
|
av_dict_set(&options, "forced-idr", "1", 0); |
|
|
|
|
if (name == "libx264") av_dict_set(&options, "x264-params", "scenecut=0:open-gop=0:repeat-headers=0", 0); |
|
|
|
|
else av_dict_set(&options, "x265-params", "bframes=0:rc-lookahead=0:open-gop=0:pools=1:frame-threads=1:log-level=error", 0); |
|
|
|
|
} else if (name.endsWith("_nvenc")) { |
|
|
|
|
@ -118,9 +121,12 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
av_dict_set(&options, "deadline", "realtime", 0); av_dict_set(&options, "cpu-used", "8", 0); |
|
|
|
|
av_dict_set(&options, "lag-in-frames", "0", 0); av_dict_set(&options, "row-mt", "1", 0); |
|
|
|
|
} else if (name == "libsvtav1") { |
|
|
|
|
av_dict_set(&options, "preset", "12", 0); av_dict_set(&options, "svtav1-params", "pred-struct=1:lookahead=0:lp=2", 0); |
|
|
|
|
c->rc_max_rate = 0; |
|
|
|
|
av_dict_set(&options, "preset", "11", 0); av_dict_set(&options, "svtav1-params", "pred-struct=1:lookahead=0:lp=2", 0); |
|
|
|
|
} else if (name.endsWith("_qsv")) { |
|
|
|
|
av_dict_set(&options, "async_depth", "1", 0); av_dict_set(&options, "look_ahead", "0", 0); |
|
|
|
|
} else if (name.endsWith("_vaapi")) { |
|
|
|
|
av_dict_set(&options, "async_depth", "1", 0); |
|
|
|
|
} else if (name == "libvvenc") av_dict_set(&options, "preset", "fast", 0); |
|
|
|
|
if (result >= 0) result = avcodec_open2(c, encoder, &options); |
|
|
|
|
av_dict_free(&options); |
|
|
|
|
@ -144,17 +150,26 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
if (result < 0) { |
|
|
|
|
m->error = ffError(result); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop encoder unavailable name=%s size=%dx%d error=%s", |
|
|
|
|
name.constData(), config.width, config.height, qPrintable(m->error)); continue; |
|
|
|
|
name.constData(), config.width, config.height, qPrintable(m->error)); break; |
|
|
|
|
} |
|
|
|
|
if (!pass) { |
|
|
|
|
QImage probe(config.width, config.height, QImage::Format_RGB32); probe.fill(Qt::black); |
|
|
|
|
QList<StreamEncodedFrame> 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)); |
|
|
|
|
continue; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
output.clear(); |
|
|
|
|
if (!encode(probe, 1000000 / config.fps, true, output) || output.isEmpty() || !output.front().key) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_VIDEO, "desktop encoder cannot force an independent KEY name=%s error=%s", name.constData(), qPrintable(m->error)); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop encoder ready name=%s codec=%d size=%dx%d fps=%d bitrate=%d probe_KEY=%d", |
|
|
|
|
name.constData(), config.codec, config.width, config.height, config.fps, config.bitrate, output.front().data.size()); |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop encoder ready name=%s codec=%d size=%dx%d fps=%d bitrate=%d two_KEYs=verified", |
|
|
|
|
name.constData(), config.codec, config.width, config.height, config.fps, config.bitrate); |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (m->error.isEmpty()) m->error = "No working encoder for " + codecName(config.codec); |
|
|
|
|
#else |
|
|
|
|
@ -183,6 +198,7 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi
|
|
|
|
|
result = sws_scale(m->scale, input, stride, 0, canvas.height(), m->frame->data, m->frame->linesize); |
|
|
|
|
} |
|
|
|
|
m->frame->pts = static_cast<int64_t>(pts); m->frame->pict_type = key ? AV_PICTURE_TYPE_I : AV_PICTURE_TYPE_NONE; |
|
|
|
|
if (key) m->frame->flags |= AV_FRAME_FLAG_KEY; else m->frame->flags &= ~AV_FRAME_FLAG_KEY; |
|
|
|
|
AVFrame* hardware = nullptr; |
|
|
|
|
if (result >= 0 && m->codec->hw_frames_ctx) { |
|
|
|
|
hardware = av_frame_alloc(); |
|
|
|
|
|