Добавить GPU-конвейер звонка с исходными PTS и подтверждением смены по IDR
This commit is contained in:
1 parent
3ed9acb384
commit
6715acdef8
3 files changed
+133
-67
No files matched your search
@@ -17,7 +17,7 @@ bool CallVideoEncoder::open(const call_video_format& format, uint32_t bitrate, c
|
||||
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>();
|
||||
if (!candidate->open(config)) {
|
||||
if (!candidate->open(config, backend.isEmpty() ? LiveVideoEncoder::Backend::HardwareOnly : LiveVideoEncoder::Backend::Automatic)) {
|
||||
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_format = format;
|
||||
@@ -29,9 +29,10 @@ void CallVideoEncoder::close() {
|
||||
if (m_encoder) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video encoder close generation=%llu pending_request=%llu",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)m_request);
|
||||
m_prepared = {}; // Завершение фоновой подготовки ждём только при остановке/смене режима.
|
||||
m_encoder.reset(); m_config = {}; m_config.bitrate = 0; m_error.clear();
|
||||
m_replacement.reset(); m_encoder.reset(); m_config = {}; m_config.bitrate = 0; m_error.clear();
|
||||
m_format = {}; m_request = m_lastRequest = m_lastPts = m_lastKey = 0;
|
||||
m_pendingBitrate = 0; m_haveFrame = false;
|
||||
m_lastOutputPts = m_switchPts = 0; m_backgroundMs = 0; m_switchInputs = 0;
|
||||
m_pendingBitrate = 0; m_haveFrame = m_haveInput = m_keyPending = false;
|
||||
}
|
||||
|
||||
// Ядро сериализует команды; повторные/чужие requests не заменяют уже ожидающую команду.
|
||||
@@ -64,71 +65,83 @@ bool CallVideoEncoder::requestBitrate(uint64_t generation, uint64_t request, uin
|
||||
}
|
||||
|
||||
// Capture PTS продолжает идти при backpressure, поэтому запрет отправки не задерживает применение.
|
||||
bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey, LiveEncodedFrame& output,
|
||||
bool CallVideoEncoder::encode(const QImage& image, uint64_t pts, bool forceKey, QList<LiveEncodedFrame>& output,
|
||||
CallVideoRateCompletion& completion) {
|
||||
completion = {};
|
||||
if (!m_encoder || image.isNull() || pts > INT64_MAX || (m_haveFrame && pts <= m_lastPts)) {
|
||||
completion = {}; output.clear();
|
||||
if (!m_encoder || image.isNull() || pts > INT64_MAX || (m_haveInput && pts <= m_lastPts)) {
|
||||
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",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)pts, (unsigned long long)m_lastPts,
|
||||
m_encoder != nullptr, image.isNull()); return false;
|
||||
}
|
||||
bool key = forceKey || !m_haveFrame || pts - m_lastKey >= 1000000;
|
||||
bool key = !m_keyPending && (forceKey || !m_haveInput || (pts >= m_lastKey && pts - m_lastKey >= 1000000));
|
||||
QList<LiveEncodedFrame> frames;
|
||||
bool prepared = !m_prepared.valid() || m_prepared.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
|
||||
if (key && m_request && !prepared)
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call video preparation pending request=%llu; keep current encoder", (unsigned long long)m_request);
|
||||
if (key && m_request && prepared) {
|
||||
completion = {m_format.generation, m_request, 0};
|
||||
if (m_pendingBitrate == (uint32_t)m_config.bitrate) completion.bitrate = m_pendingBitrate;
|
||||
if (key && m_request && prepared && !m_replacement) {
|
||||
if (m_pendingBitrate == (uint32_t)m_config.bitrate) {
|
||||
completion = {m_format.generation, m_request, m_pendingBitrate}; m_request = 0; m_pendingBitrate = 0;
|
||||
}
|
||||
else {
|
||||
LiveVideoConfig next = m_config; next.bitrate = m_pendingBitrate;
|
||||
auto replacement = m_prepared.get(); auto& candidate = replacement.encoder;
|
||||
QElapsedTimer timer; timer.start();
|
||||
bool ready = candidate && candidate->encode(image, pts, true, frames)
|
||||
&& frames.size() == 1 && frames.front().key && frames.front().pts == pts;
|
||||
const qint64 encodedUs = timer.nsecsElapsed() / 1000;
|
||||
if (ready) ready = !call_video_check_key(&m_format,
|
||||
reinterpret_cast<const uint8_t*>(frames.front().data.constData()), frames.front().data.size());
|
||||
const qint64 verifiedUs = timer.nsecsElapsed() / 1000;
|
||||
if (ready) {
|
||||
m_encoder = std::move(candidate); m_config = next; completion.bitrate = m_pendingBitrate;
|
||||
const qint64 committedUs = timer.nsecsElapsed() / 1000;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL,
|
||||
"call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu background_ms=%lld "
|
||||
"commit_ms=%lld encode_us=%lld verify_us=%lld retire_us=%lld bytes=%lld",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate,
|
||||
(unsigned long long)pts, (long long)replacement.elapsed, (long long)(committedUs / 1000),
|
||||
(long long)encodedUs, (long long)(verifiedUs - encodedUs), (long long)(committedUs - verifiedUs),
|
||||
(long long)frames.front().data.size());
|
||||
} else {
|
||||
m_error = candidate ? candidate->error() : replacement.error;
|
||||
if (m_error.isEmpty()) m_error = "Replacement encoder did not produce a compatible immediate IDR";
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video replacement failed generation=%llu request=%llu target=%u: %s",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)m_request, m_pendingBitrate, qPrintable(m_error));
|
||||
frames.clear();
|
||||
auto preparedEncoder = m_prepared.get(); m_backgroundMs = preparedEncoder.elapsed;
|
||||
m_replacement = std::move(preparedEncoder.encoder); m_switchPts = pts; m_switchInputs = 0;
|
||||
if (!m_replacement) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call GPU replacement open failed request=%llu: %s",
|
||||
(unsigned long long)m_request, qPrintable(preparedEncoder.error));
|
||||
completion = {m_format.generation, m_request, 0}; m_request = 0; m_pendingBitrate = 0;
|
||||
}
|
||||
}
|
||||
m_request = 0; m_pendingBitrate = 0;
|
||||
}
|
||||
if (frames.isEmpty() && !m_encoder->encode(image, pts, key, frames)) {
|
||||
QElapsedTimer timer; timer.start();
|
||||
bool encoded = false;
|
||||
if (m_replacement) {
|
||||
++m_switchInputs;
|
||||
encoded = m_replacement->encode(image, pts, m_switchInputs == 1, frames);
|
||||
if (encoded && !frames.isEmpty()) {
|
||||
const auto& first = frames.front();
|
||||
encoded = first.key && first.pts == m_switchPts && !call_video_check_key(&m_format,
|
||||
reinterpret_cast<const uint8_t*>(first.data.constData()), first.data.size());
|
||||
if (encoded) {
|
||||
const qint64 encodeUs = timer.nsecsElapsed() / 1000;
|
||||
m_encoder = std::move(m_replacement); m_config.bitrate = m_pendingBitrate;
|
||||
completion = {m_format.generation, m_request, m_pendingBitrate};
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CALL,
|
||||
"call video bitrate committed generation=%llu request=%llu bitrate=%u pts=%llu background_ms=%lld "
|
||||
"encode_us=%lld retire_us=%lld pipeline_frames=%u pipeline_pts_us=%llu bytes=%lld",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)m_request, completion.bitrate,
|
||||
(unsigned long long)first.pts, (long long)m_backgroundMs, (long long)encodeUs,
|
||||
(long long)(timer.nsecsElapsed() / 1000 - encodeUs), m_switchInputs,
|
||||
(unsigned long long)(pts - first.pts), (long long)first.data.size());
|
||||
m_request = 0; m_pendingBitrate = 0;
|
||||
}
|
||||
}
|
||||
if (!encoded) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call GPU replacement failed request=%llu inputs=%u frames=%lld: %s",
|
||||
(unsigned long long)m_request, m_switchInputs, (long long)frames.size(), qPrintable(m_replacement->error()));
|
||||
completion = {m_format.generation, m_request, 0}; m_request = 0; m_pendingBitrate = 0;
|
||||
m_replacement.reset(); frames.clear(); key = true; m_keyPending = false;
|
||||
}
|
||||
}
|
||||
if (!encoded) encoded = m_encoder->encode(image, pts, key, frames);
|
||||
m_haveInput = true; m_lastPts = pts; if (key) m_keyPending = true;
|
||||
if (!encoded) {
|
||||
m_error = m_encoder->error();
|
||||
if (completion.request) completion.bitrate = 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call video encoding failed generation=%llu: %s",
|
||||
(unsigned long long)m_format.generation, qPrintable(m_error)); return false;
|
||||
}
|
||||
if (frames.size() != 1 || frames.front().pts != pts || (key && !frames.front().key)) {
|
||||
m_error = "Call encoder delayed output or failed to produce IDR";
|
||||
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",
|
||||
(unsigned long long)m_format.generation, (unsigned long long)pts, (long long)frames.size(), key); return false;
|
||||
for (const auto& frame : frames) {
|
||||
if ((m_haveFrame && frame.pts <= m_lastOutputPts) || frame.pts > pts || (!m_haveFrame && !frame.key)
|
||||
|| (frame.key && call_video_check_key(&m_format,
|
||||
reinterpret_cast<const uint8_t*>(frame.data.constData()), frame.data.size()))) {
|
||||
m_error = "GPU produced an incompatible video frame";
|
||||
if (completion.request) completion.bitrate = 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call GPU output rejected pts=%llu last=%llu capture=%llu key=%d",
|
||||
(unsigned long long)frame.pts, (unsigned long long)m_lastOutputPts, (unsigned long long)pts, frame.key); return false;
|
||||
}
|
||||
m_haveFrame = true; m_lastOutputPts = frame.pts;
|
||||
if (frame.key) { m_lastKey = frame.pts; m_keyPending = false; }
|
||||
}
|
||||
if (frames.front().key && call_video_check_key(&m_format,
|
||||
reinterpret_cast<const uint8_t*>(frames.front().data.constData()), frames.front().data.size())) {
|
||||
m_error = "Encoder produced an incompatible IDR";
|
||||
if (completion.request) completion.bitrate = 0;
|
||||
return false;
|
||||
}
|
||||
m_haveFrame = true; m_lastPts = pts; if (frames.front().key) m_lastKey = pts;
|
||||
output = std::move(frames.front()); return true;
|
||||
output = std::move(frames); return true;
|
||||
}
|
||||
Reference in new issue
Block a user