|
|
|
|
@ -47,7 +47,8 @@ QList<QByteArray> encoders(int codec) {
|
|
|
|
|
case STREAM_MEDIA_H264: return {"h264_nvenc", "h264_qsv", "h264_amf", "h264_vaapi", "libx264", "libopenh264"}; |
|
|
|
|
case STREAM_MEDIA_H265: return {"hevc_nvenc", "hevc_qsv", "hevc_amf", "hevc_vaapi", "libx265"}; |
|
|
|
|
case STREAM_MEDIA_VP9: return {"vp9_qsv", "vp9_vaapi", "libvpx-vp9"}; |
|
|
|
|
case STREAM_MEDIA_AV1: return {"av1_nvenc", "av1_qsv", "av1_amf", "av1_vaapi", "libsvtav1", "libaom-av1"}; |
|
|
|
|
// FFmpeg/SVT не принимает произвольный KEY при GOP>1; libaom поддерживает нужный live-контракт.
|
|
|
|
|
case STREAM_MEDIA_AV1: return {"av1_nvenc", "av1_qsv", "av1_amf", "av1_vaapi", "libaom-av1"}; |
|
|
|
|
case STREAM_MEDIA_H266: return {"libvvenc"}; |
|
|
|
|
default: return {}; |
|
|
|
|
} |
|
|
|
|
@ -120,9 +121,6 @@ bool StreamVideoEncoder::open(const StreamVideoConfig& config) {
|
|
|
|
|
} else if (name == "libvpx-vp9" || name == "libaom-av1") { |
|
|
|
|
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") { |
|
|
|
|
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")) { |
|
|
|
|
@ -194,7 +192,8 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi
|
|
|
|
|
if (result >= 0) { |
|
|
|
|
const int* coefficients = sws_getCoefficients(SWS_CS_ITU709); |
|
|
|
|
sws_setColorspaceDetails(m->scale, coefficients, 1, coefficients, 0, 0, 1 << 16, 1 << 16); |
|
|
|
|
const uint8_t* input[] = {canvas.constBits()}; int stride[] = {static_cast<int>(canvas.bytesPerLine())}; |
|
|
|
|
const uint8_t* input[4] = {canvas.constBits(), nullptr, nullptr, nullptr}; |
|
|
|
|
int stride[4] = {static_cast<int>(canvas.bytesPerLine()), 0, 0, 0}; |
|
|
|
|
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; |
|
|
|
|
@ -250,8 +249,25 @@ bool StreamVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QLi
|
|
|
|
|
QList<int> StreamVideoEncoder::available() { |
|
|
|
|
QList<int> 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 |
|
|
|
|
StreamVideoEncoder encoder; StreamVideoConfig 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) { |
|
|
|
|
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 при подаче следующего кадра.
|
|
|
|
|
if (m->codec && m->name == "libx264") { |
|
|
|
|
m->codec->bit_rate = m->codec->rc_max_rate = bitrate; m->codec->rc_buffer_size = bitrate / 4; m->config.bitrate = bitrate; |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_VIDEO, "desktop encoder bitrate updated in place name=libx264 bitrate=%d", bitrate); return true; |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop bitrate needs encoder replacement name=%s", qPrintable(m->name)); return false; |
|
|
|
|
} |
|
|
|
|
|