Open desktop call encoders from the shared negotiated video format
This commit is contained in:
1 parent
534f319d86
commit
3f61549769
2 files changed
+30
-26
No files matched your search
@@ -4,38 +4,40 @@
|
|||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
// Профиль и envelope задаются явно: автоматический выбор level при смене bitrate запрещён.
|
// Профиль и envelope задаются явно: автоматический выбор level при смене bitrate запрещён.
|
||||||
bool CallVideoEncoder::open(const LiveVideoConfig& config, uint64_t generation, uint32_t maximumBitrate) {
|
bool CallVideoEncoder::open(const call_video_format& format, uint32_t bitrate, const QString& backend) {
|
||||||
if (!generation || config.profile == LiveVideoProfile::Automatic || config.references != 1
|
if (!call_video_format_valid(&format) || bitrate < CALL_VIDEO_RATE_MIN_BPS || bitrate > format.maximum_bps) {
|
||||||
|| maximumBitrate < CALL_VIDEO_RATE_MIN_BPS || maximumBitrate > CALL_VIDEO_RATE_MAX_BPS
|
|
||||||
|| config.bitrate < CALL_VIDEO_RATE_MIN_BPS || config.bitrate > maximumBitrate) {
|
|
||||||
m_error = "Invalid call video encoder configuration";
|
m_error = "Invalid call video encoder configuration";
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video open invalid generation=%llu bitrate=%d profile=%d refs=%d",
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video open invalid generation=%llu codec=%u level=%u bitrate=%u cap=%u vbv=%u",
|
||||||
(unsigned long long)generation, config.bitrate, (int)config.profile, config.references); return false;
|
(unsigned long long)format.generation, format.codec, format.level, bitrate, format.maximum_bps, format.vbv_bits); return false;
|
||||||
}
|
}
|
||||||
|
LiveVideoConfig config;
|
||||||
|
config.width = format.width; config.height = format.height; config.fps = format.fps; config.rotation = format.rotation;
|
||||||
|
config.codec = format.codec; config.level = format.level; config.vbvBits = format.vbv_bits;
|
||||||
|
config.bitrate = bitrate; config.references = 1; config.backend = backend;
|
||||||
|
config.profile = format.codec == CALL_VIDEO_H264 ? LiveVideoProfile::H264ConstrainedBaseline : LiveVideoProfile::H265Main;
|
||||||
auto candidate = std::make_unique<LiveVideoEncoder>();
|
auto candidate = std::make_unique<LiveVideoEncoder>();
|
||||||
if (!candidate->open(config)) {
|
if (!candidate->open(config)) {
|
||||||
m_error = candidate->error(); DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video open failed: %s", qPrintable(m_error)); return false;
|
m_error = candidate->error(); DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video open failed: %s", qPrintable(m_error)); return false;
|
||||||
}
|
}
|
||||||
close(); m_encoder = std::move(candidate); m_config = config; m_config.backend = m_encoder->name(); m_generation = generation;
|
close(); m_encoder = std::move(candidate); m_config = config; m_config.backend = m_encoder->name(); m_format = format;
|
||||||
m_maximumBitrate = maximumBitrate;
|
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video encoder opened generation=%llu codec=%d encoder=%s bitrate=%d level=%d",
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call video encoder opened generation=%llu codec=%d encoder=%s bitrate=%d level=%d",
|
||||||
(unsigned long long)generation, config.codec, qPrintable(m_config.backend), config.bitrate, config.level); return true;
|
(unsigned long long)format.generation, config.codec, qPrintable(m_config.backend), config.bitrate, config.level); return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CallVideoEncoder::close() {
|
void CallVideoEncoder::close() {
|
||||||
if (m_encoder) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video encoder close generation=%llu pending_request=%llu",
|
if (m_encoder) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video encoder close generation=%llu pending_request=%llu",
|
||||||
(unsigned long long)m_generation, (unsigned long long)m_request);
|
(unsigned long long)m_format.generation, (unsigned long long)m_request);
|
||||||
m_encoder.reset(); m_config = {}; m_config.bitrate = 0; m_error.clear();
|
m_encoder.reset(); m_config = {}; m_config.bitrate = 0; m_error.clear();
|
||||||
m_generation = m_request = m_lastRequest = m_lastPts = m_lastKey = 0;
|
m_format = {}; m_request = m_lastRequest = m_lastPts = m_lastKey = 0;
|
||||||
m_pendingBitrate = m_maximumBitrate = 0; m_haveFrame = false;
|
m_pendingBitrate = 0; m_haveFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ядро сериализует команды; повторные/чужие requests не заменяют уже ожидающую команду.
|
// Ядро сериализует команды; повторные/чужие requests не заменяют уже ожидающую команду.
|
||||||
bool CallVideoEncoder::requestBitrate(uint64_t generation, uint64_t request, uint32_t bitrate) {
|
bool CallVideoEncoder::requestBitrate(uint64_t generation, uint64_t request, uint32_t bitrate) {
|
||||||
if (!m_encoder || generation != m_generation || !request || request <= m_lastRequest || m_request
|
if (!m_encoder || generation != m_format.generation || !request || request <= m_lastRequest || m_request
|
||||||
|| bitrate < CALL_VIDEO_RATE_MIN_BPS || bitrate > m_maximumBitrate) {
|
|| bitrate < CALL_VIDEO_RATE_MIN_BPS || bitrate > m_format.maximum_bps) {
|
||||||
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call video bitrate rejected generation=%llu/%llu request=%llu last=%llu pending=%llu bitrate=%u",
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call video bitrate rejected generation=%llu/%llu request=%llu last=%llu pending=%llu bitrate=%u",
|
||||||
(unsigned long long)generation, (unsigned long long)m_generation, (unsigned long long)request,
|
(unsigned long long)generation, (unsigned long long)m_format.generation, (unsigned long long)request,
|
||||||
(unsigned long long)m_lastRequest, (unsigned long long)m_request, bitrate); return false;
|
(unsigned long long)m_lastRequest, (unsigned long long)m_request, bitrate); return false;
|
||||||
}
|
}
|
||||||
m_request = m_lastRequest = request; m_pendingBitrate = bitrate;
|
m_request = m_lastRequest = request; m_pendingBitrate = bitrate;
|
||||||
@@ -50,13 +52,13 @@ bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey,
|
|||||||
if (!m_encoder || image.isNull() || pts > INT64_MAX || (m_haveFrame && pts <= m_lastPts)) {
|
if (!m_encoder || image.isNull() || pts > INT64_MAX || (m_haveFrame && pts <= m_lastPts)) {
|
||||||
m_error = "Invalid call video frame or non-monotonic PTS";
|
m_error = "Invalid call video frame or non-monotonic PTS";
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video encode invalid generation=%llu pts=%llu last=%llu active=%d image_null=%d",
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video encode invalid generation=%llu pts=%llu last=%llu active=%d image_null=%d",
|
||||||
(unsigned long long)m_generation, (unsigned long long)pts, (unsigned long long)m_lastPts,
|
(unsigned long long)m_format.generation, (unsigned long long)pts, (unsigned long long)m_lastPts,
|
||||||
m_encoder != nullptr, image.isNull()); return false;
|
m_encoder != nullptr, image.isNull()); return false;
|
||||||
}
|
}
|
||||||
bool key = forceKey || !m_haveFrame || pts - m_lastKey >= 1000000;
|
bool key = forceKey || !m_haveFrame || pts - m_lastKey >= 1000000;
|
||||||
QList<LiveEncodedFrame> frames;
|
QList<LiveEncodedFrame> frames;
|
||||||
if (key && m_request) {
|
if (key && m_request) {
|
||||||
completion = {m_generation, m_request, 0};
|
completion = {m_format.generation, m_request, 0};
|
||||||
if (m_pendingBitrate == (uint32_t)m_config.bitrate) completion.bitrate = m_pendingBitrate;
|
if (m_pendingBitrate == (uint32_t)m_config.bitrate) completion.bitrate = m_pendingBitrate;
|
||||||
else {
|
else {
|
||||||
LiveVideoConfig next = m_config; next.bitrate = m_pendingBitrate;
|
LiveVideoConfig next = m_config; next.bitrate = m_pendingBitrate;
|
||||||
@@ -67,12 +69,12 @@ bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey,
|
|||||||
m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate;
|
m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate;
|
||||||
DEBUG_INFO(DEBUG_CATEGORY_CALL,
|
DEBUG_INFO(DEBUG_CATEGORY_CALL,
|
||||||
"call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu prepare_ms=%lld bytes=%lld",
|
"call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu prepare_ms=%lld bytes=%lld",
|
||||||
(unsigned long long)m_generation, (unsigned long long)m_request, completion.bitrate,
|
(unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate,
|
||||||
(unsigned long long)pts, (long long)timer.elapsed(), (long long)frames.front().data.size());
|
(unsigned long long)pts, (long long)timer.elapsed(), (long long)frames.front().data.size());
|
||||||
} else {
|
} else {
|
||||||
m_error = candidate->error(); if (m_error.isEmpty()) m_error = "Replacement encoder did not produce an immediate IDR";
|
m_error = candidate->error(); if (m_error.isEmpty()) m_error = "Replacement encoder did not produce an immediate IDR";
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video replacement failed generation=%llu request=%llu target=%u: %s",
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video replacement failed generation=%llu request=%llu target=%u: %s",
|
||||||
(unsigned long long)m_generation, (unsigned long long)m_request, m_pendingBitrate, qPrintable(m_error));
|
(unsigned long long)m_format.generation, (unsigned long long)m_request, m_pendingBitrate, qPrintable(m_error));
|
||||||
frames.clear();
|
frames.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,13 +84,13 @@ bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey,
|
|||||||
m_error = m_encoder->error();
|
m_error = m_encoder->error();
|
||||||
if (completion.request) completion.bitrate = 0;
|
if (completion.request) completion.bitrate = 0;
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video encoding failed generation=%llu: %s",
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video encoding failed generation=%llu: %s",
|
||||||
(unsigned long long)m_generation, qPrintable(m_error)); return false;
|
(unsigned long long)m_format.generation, qPrintable(m_error)); return false;
|
||||||
}
|
}
|
||||||
if (frames.size() != 1 || frames.front().pts != pts || (key && !frames.front().key)) {
|
if (frames.size() != 1 || frames.front().pts != pts || (key && !frames.front().key)) {
|
||||||
m_error = "Call encoder delayed output or failed to produce IDR";
|
m_error = "Call encoder delayed output or failed to produce IDR";
|
||||||
if (completion.request) completion.bitrate = 0;
|
if (completion.request) completion.bitrate = 0;
|
||||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video output contract failed generation=%llu pts=%llu frames=%lld key_requested=%d",
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video output contract failed generation=%llu pts=%llu frames=%lld key_requested=%d",
|
||||||
(unsigned long long)m_generation, (unsigned long long)pts, (long long)frames.size(), key); return false;
|
(unsigned long long)m_format.generation, (unsigned long long)pts, (long long)frames.size(), key); return false;
|
||||||
}
|
}
|
||||||
m_haveFrame = true; m_lastPts = pts; if (frames.front().key) m_lastKey = pts;
|
m_haveFrame = true; m_lastPts = pts; if (frames.front().key) m_lastKey = pts;
|
||||||
output = std::move(frames.front()); return true;
|
output = std::move(frames.front()); return true;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
* Смена bitrate ожидает ближайший IDR (период <= 1с по capture PTS). Новый encoder
|
* Смена bitrate ожидает ближайший IDR (период <= 1с по capture PTS). Новый encoder
|
||||||
* заменяет старый только после полного первого IDR. Completion передаётся в uasync
|
* заменяет старый только после полного первого IDR. Completion передаётся в uasync
|
||||||
* независимо от admission видео. PTS, generation и frame_id принадлежат владельцу.
|
* независимо от admission видео. PTS, generation и frame_id принадлежат владельцу.
|
||||||
* open получает уже согласованный profile/level/VBV и предел bitrate данного режима;
|
* open получает согласованный call_video_format; соответствие level/размера/FPS
|
||||||
* сетевой CAPS и соответствие размера/FPS этому envelope проверяет владелец. */
|
* проверяет общий модуль, фактический bitstream — LiveVideoEncoder. CAPS проверяет владелец. */
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "live_video_encoder.h"
|
#include "live_video_encoder.h"
|
||||||
|
#include "../../../src/call/call_video.h"
|
||||||
|
|
||||||
struct CallVideoRateCompletion {
|
struct CallVideoRateCompletion {
|
||||||
uint64_t generation = 0, request = 0;
|
uint64_t generation = 0, request = 0;
|
||||||
@@ -14,7 +15,7 @@ struct CallVideoRateCompletion {
|
|||||||
|
|
||||||
class CallVideoEncoder {
|
class CallVideoEncoder {
|
||||||
public:
|
public:
|
||||||
bool open(const LiveVideoConfig& config, uint64_t generation, uint32_t maximumBitrate);
|
bool open(const call_video_format& format, uint32_t bitrate, const QString& backend = {});
|
||||||
void close();
|
void close();
|
||||||
bool requestBitrate(uint64_t generation, uint64_t request, uint32_t bitrate);
|
bool requestBitrate(uint64_t generation, uint64_t request, uint32_t bitrate);
|
||||||
// Один вход -> один выход. На false output не меняется; completion нужно обработать и на ошибке.
|
// Один вход -> один выход. На false output не меняется; completion нужно обработать и на ошибке.
|
||||||
@@ -24,8 +25,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::unique_ptr<LiveVideoEncoder> m_encoder;
|
std::unique_ptr<LiveVideoEncoder> m_encoder;
|
||||||
LiveVideoConfig m_config;
|
LiveVideoConfig m_config;
|
||||||
|
call_video_format m_format{};
|
||||||
QString m_error;
|
QString m_error;
|
||||||
uint64_t m_generation = 0, m_request = 0, m_lastRequest = 0, m_lastPts = 0, m_lastKey = 0;
|
uint64_t m_request = 0, m_lastRequest = 0, m_lastPts = 0, m_lastKey = 0;
|
||||||
uint32_t m_pendingBitrate = 0, m_maximumBitrate = 0;
|
uint32_t m_pendingBitrate = 0;
|
||||||
bool m_haveFrame = false;
|
bool m_haveFrame = false;
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user