|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
#include "live_video_encoder.h" |
|
|
|
|
#include "debug_config.h" |
|
|
|
|
#include <QElapsedTimer> |
|
|
|
|
#include <QPainter> |
|
|
|
|
#include <algorithm> |
|
|
|
|
#include <cstring> |
|
|
|
|
@ -18,6 +19,9 @@ struct LiveVideoEncoder::Impl {
|
|
|
|
|
QString error, name; |
|
|
|
|
LiveVideoConfig config; |
|
|
|
|
int64_t submittedFrames = 0; |
|
|
|
|
QElapsedTimer reportTimer; |
|
|
|
|
unsigned inputs = 0, outputs = 0, keys = 0; |
|
|
|
|
qint64 prepareMax = 0, submitMax = 0, drainMax = 0, keyMax = 0, deltaMax = 0; |
|
|
|
|
#ifdef HAVE_FFMPEG |
|
|
|
|
AVCodecContext* codec = nullptr; |
|
|
|
|
AVBSFContext* filter = nullptr; |
|
|
|
|
@ -297,6 +301,9 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
QElapsedTimer timer; timer.start(); |
|
|
|
|
if (!m->reportTimer.isValid()) m->reportTimer.start(); |
|
|
|
|
const auto outputBase = output.size(); |
|
|
|
|
QImage canvas(m->config.width, m->config.height, QImage::Format_RGB32); canvas.fill(Qt::black); |
|
|
|
|
QSize fitted = image.size().scaled(canvas.size(), Qt::KeepAspectRatio); |
|
|
|
|
{ QPainter painter(&canvas); painter.drawImage(QRect(QPoint((canvas.width() - fitted.width()) / 2, |
|
|
|
|
@ -325,7 +332,9 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
|
|
|
|
|
if (result >= 0) result = av_hwframe_transfer_data(hardware, m->frame, 0); |
|
|
|
|
if (result >= 0) result = av_frame_copy_props(hardware, m->frame); |
|
|
|
|
} |
|
|
|
|
const qint64 preparedUs = timer.nsecsElapsed() / 1000; |
|
|
|
|
if (result >= 0) result = avcodec_send_frame(m->codec, hardware ? hardware : m->frame); |
|
|
|
|
const qint64 submittedUs = timer.nsecsElapsed() / 1000; |
|
|
|
|
if (result >= 0) m->submittedFrames++; |
|
|
|
|
av_frame_free(&hardware); |
|
|
|
|
AVPacket* packet = av_packet_alloc(); |
|
|
|
|
@ -355,6 +364,27 @@ bool LiveVideoEncoder::encode(const QImage& image, uint64_t pts, bool key, QList
|
|
|
|
|
} |
|
|
|
|
av_packet_free(&packet); |
|
|
|
|
if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) { |
|
|
|
|
// Сводка по этапам отличает конвертацию, синхронный encoder и получение/упаковку пакета.
|
|
|
|
|
const qint64 totalUs = timer.nsecsElapsed() / 1000; |
|
|
|
|
++m->inputs; m->outputs += output.size() - outputBase; |
|
|
|
|
bool producedKey = false; |
|
|
|
|
for (auto i = outputBase; i < output.size(); ++i) if (output[i].key) { ++m->keys; producedKey = true; } |
|
|
|
|
m->prepareMax = std::max(m->prepareMax, preparedUs); |
|
|
|
|
m->submitMax = std::max(m->submitMax, submittedUs - preparedUs); |
|
|
|
|
m->drainMax = std::max(m->drainMax, totalUs - submittedUs); |
|
|
|
|
auto& frameMax = producedKey ? m->keyMax : m->deltaMax; |
|
|
|
|
frameMax = std::max(frameMax, totalUs); |
|
|
|
|
if (m->reportTimer.elapsed() >= 1000) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, |
|
|
|
|
"video encoder flow name=%s size=%dx%d bitrate=%d interval_us=%lld inputs=%u outputs=%u keys=%u " |
|
|
|
|
"prepare_max_us=%lld submit_max_us=%lld drain_max_us=%lld key_max_us=%lld delta_max_us=%lld pts=%llu", |
|
|
|
|
qPrintable(m->name), m->config.width, m->config.height, m->config.bitrate, |
|
|
|
|
(long long)(m->reportTimer.nsecsElapsed() / 1000), m->inputs, m->outputs, m->keys, |
|
|
|
|
(long long)m->prepareMax, (long long)m->submitMax, (long long)m->drainMax, |
|
|
|
|
(long long)m->keyMax, (long long)m->deltaMax, (unsigned long long)pts); |
|
|
|
|
m->reportTimer.restart(); m->inputs = m->outputs = m->keys = 0; |
|
|
|
|
m->prepareMax = m->submitMax = m->drainMax = m->keyMax = m->deltaMax = 0; |
|
|
|
|
} |
|
|
|
|
if (key && output.isEmpty()) DEBUG_DEBUG(DEBUG_CATEGORY_VIDEO, "desktop requested KEY buffered name=%s PTS=%llu", |
|
|
|
|
qPrintable(m->name), (unsigned long long)pts); |
|
|
|
|
return true; |
|
|
|
|
|