From ae8eeab49069f1352801e45521a2d215903073aa Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 18 Oct 2026 16:51:55 +0200 Subject: [PATCH] =?UTF-8?q?=D0=92=D1=8B=D0=B4=D0=B0=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BA?= =?UTF-8?q?=D0=B0=D0=B4=D1=80=D1=8B=20GPU=20=D0=B1=D0=B5=D0=B7=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B2=20TX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/chatgui/src/call_video_engine.cpp | 19 +++++++----- tools/chatgui/tests/test_call_video_clock.cpp | 8 ++--- .../chatgui/tests/test_call_video_encoder.cpp | 29 ++++++++++--------- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index bf77889c..0375f3b4 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -238,10 +238,10 @@ void CallVideoEngine::transmit() { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX skips old capture age_us=%lld", (long long)(nowUs() - pts)); lastPts = pts; ++expired; continue; } - LiveEncodedFrame output; CallVideoRateCompletion completion; + QList outputs; CallVideoRateCompletion completion; int64_t encodeStarted = nowUs(); inputAgeMax = std::max(inputAgeMax, encodeStarted - pts); - bool encoded = encoder.encode(image, pts, forceKey, output, completion); lastPts = pts; + bool encoded = encoder.encode(image, pts, forceKey, outputs, completion); lastPts = pts; encodeMax = std::max(encodeMax, nowUs() - encodeStarted); if (completion.request) { call_video_port_applied(m->id, completion.generation, completion.request, completion.bitrate); @@ -249,26 +249,29 @@ void CallVideoEngine::transmit() { .arg(format.width).arg(format.height).arg(encoder.bitrate() / 1000)); } if (!encoded) { forceKey = true; m->report(encoder.error()); continue; } + // GPU может вернуть несколько уже готовых кадров: отправляем все, не добавляя очередь в TX worker. + for (const auto& output : outputs) { ++encodedCount; if (output.key) ++keyCount; call_video_frame packet{}; - packet.call_id = m->id; packet.frame_id = ++frameId; packet.pts_us = pts; packet.format = format; + packet.call_id = m->id; packet.frame_id = ++frameId; packet.pts_us = output.pts; packet.format = format; packet.size = packet.length = output.data.size(); packet.data = reinterpret_cast(output.data.constData()); packet.flags = output.key ? CALL_VIDEO_KEY : 0; { std::lock_guard guard(m->lock); - if (!m->running || !m->enabled || m->revision != revision || nowUs() - pts > CallCameraMaxFrameAgeUs) { + if (!m->running || !m->enabled || m->revision != revision || nowUs() - output.pts > CallCameraMaxFrameAgeUs) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "video TX discards obsolete encoded frame revision=%llu/%llu enabled=%d age_us=%lld", - (unsigned long long)revision, (unsigned long long)m->revision, m->enabled, (long long)(nowUs() - pts)); + (unsigned long long)revision, (unsigned long long)m->revision, m->enabled, (long long)(nowUs() - output.pts)); ++expired; forceKey = true; continue; } forceKey = call_video_port_send(&packet) != 1; } // Одинаковое окно исходного PTS на всех этапах: примерно один кадр в секунду, без трассы каждого кадра. - if (pts % 1000000 < 34000) + if (output.pts % 1000000 < 34000) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "video timing stage=encode call=%llu frame=%llu pts=%lld mono_us=%lld encode_us=%lld accepted=%d", - (unsigned long long)m->id, (unsigned long long)frameId, (long long)pts, (long long)nowUs(), + (unsigned long long)m->id, (unsigned long long)frameId, (long long)output.pts, (long long)nowUs(), (long long)(nowUs() - encodeStarted), !forceKey); - if (forceKey) ++sendBusy; + if (forceKey) { ++sendBusy; break; } + } #else idle(); #endif diff --git a/tools/chatgui/tests/test_call_video_clock.cpp b/tools/chatgui/tests/test_call_video_clock.cpp index c6d92162..e9608f6f 100644 --- a/tools/chatgui/tests/test_call_video_clock.cpp +++ b/tools/chatgui/tests/test_call_video_clock.cpp @@ -44,12 +44,12 @@ extern "C" int __wrap_call_video_port_pull(uint64_t id, call_video_frame* out) { int main(int argc, char** argv) { QCoreApplication app(argc, argv); debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); debug_set_category_level(DEBUG_CATEGORY_CALL, DEBUG_LEVEL_DEBUG); - CallVideoEncoder encoder; if (!encoder.open(format, 1000000)) return 2; + CallVideoEncoder encoder; if (!encoder.open(format, 1000000, "libx264")) return 2; QImage image(640, 360, QImage::Format_RGB32); image.fill(Qt::blue); for (int i = 0; i < 90; ++i) { - LiveEncodedFrame frame; CallVideoRateCompletion completion; - if (!encoder.encode(image, 1000000 + i * 33333ll, i == 0, frame, completion)) return 3; - frames.push_back(frame); + QList output; CallVideoRateCompletion completion; + if (!encoder.encode(image, 1000000 + i * 33333ll, i == 0, output, completion) || output.size() != 1) return 3; + frames.push_back(output.front()); } CallVideoEngine engine; std::vector published; QObject::connect(&engine, &CallVideoEngine::imageChanged, [&](const QImage& frame) { diff --git a/tools/chatgui/tests/test_call_video_encoder.cpp b/tools/chatgui/tests/test_call_video_encoder.cpp index f324fdbc..da176ce5 100644 --- a/tools/chatgui/tests/test_call_video_encoder.cpp +++ b/tools/chatgui/tests/test_call_video_encoder.cpp @@ -107,17 +107,17 @@ private slots: if (!std::setlocale(LC_NUMERIC, locale.constData())) QSKIP("Requested numeric locale is not installed"); if (!avcodec_find_encoder_by_name(codec == 1 ? "libx264" : "libx265")) QSKIP("Software encoder unavailable"); auto config = configuration(codec); config.width = 1280; config.height = 720; - CallVideoEncoder encoder; QVERIFY2(encoder.open(config, 1000000), qPrintable(encoder.error())); + CallVideoEncoder encoder; QVERIFY2(encoder.open(config, 1000000, codec == 1 ? "libx264" : "libx265"), qPrintable(encoder.error())); Decoder decoder; QVERIFY(decoder.open(codec)); QImage image(config.width, config.height, QImage::Format_RGB32); image.fill(Qt::blue); uint64_t pts = 1000000, request = 0; for (uint32_t bitrate : {1000000u, 300000u, 4000000u}) { QVERIFY(encoder.requestBitrate(config.generation, ++request, bitrate)); - LiveEncodedFrame output; CallVideoRateCompletion completion; + QList outputs; CallVideoRateCompletion completion; QElapsedTimer deadline; deadline.start(); do { - QVERIFY2(encoder.encode(image, pts, true, output, completion), qPrintable(encoder.error())); - QVERIFY(decoder.decode(output)); pts += 33333; + QVERIFY2(encoder.encode(image, pts, true, outputs, completion), qPrintable(encoder.error())); + QCOMPARE(outputs.size(), 1); QVERIFY(decoder.decode(outputs.front())); pts += 33333; if (!completion.request) QTest::qWait(33); } while (!completion.request && deadline.elapsed() < 3000); QCOMPARE(completion.request, request); QCOMPARE(completion.bitrate, bitrate); @@ -158,7 +158,7 @@ private slots: = {1280, 720, 30, 1280 * 720 * 30, 4000000, config.profile, config.level}; config.level = 0; QCOMPARE(call_video_choose(&sender, &receiver, &config), codec); QVERIFY(call_video_format_supported(&sender, &receiver, &config)); - CallVideoEncoder encoder; QVERIFY2(encoder.open(config, 4000000), qPrintable(encoder.error())); + CallVideoEncoder encoder; QVERIFY2(encoder.open(config, 4000000, codec == 1 ? "libx264" : "libx265"), qPrintable(encoder.error())); Decoder decoder; QVERIFY(decoder.open(codec)); CallVideoDecoder productionDecoder; QImage image(config.width, config.height, QImage::Format_RGB32); @@ -179,9 +179,10 @@ private slots: QVERIFY(!encoder.requestBitrate(17, phase + 100, target)); // Нельзя затереть pending. } uint64_t pts = 5000000 + (phase * 120 + frame) * 1000000ull / 30; - LiveEncodedFrame output; CallVideoRateCompletion completion; + QList outputs; CallVideoRateCompletion completion; QElapsedTimer encoding; encoding.start(); - QVERIFY2(encoder.encode(image, pts, false, output, completion), qPrintable(encoder.error())); + QVERIFY2(encoder.encode(image, pts, false, outputs, completion), qPrintable(encoder.error())); + QCOMPARE(outputs.size(), 1); const auto& output = outputs.front(); longestEncode = qMax(longestEncode, encoding.elapsed()); QCOMPARE(output.pts, pts); QCOMPARE(output.key, frame % 30 == 0); if (phase && frame == 30) { @@ -213,24 +214,26 @@ private slots: void keepsOldEncoderAndRejectsInvalidCommands() { auto config = configuration(1); config.width = 160; config.height = 90; config.generation = 9; config.maximum_bps = 700000; if (!avcodec_find_encoder_by_name("libx264")) QSKIP("libx264 unavailable"); - CallVideoEncoder encoder; QVERIFY(encoder.open(config, 500000)); + CallVideoEncoder encoder; QVERIFY(encoder.open(config, 500000, "libx264")); QImage image(160, 90, QImage::Format_RGB32); image.fill(Qt::blue); - LiveEncodedFrame output; CallVideoRateCompletion completion; + QList output; CallVideoRateCompletion completion; QVERIFY(encoder.encode(image, 100000, false, output, completion)); QVERIFY(!encoder.requestBitrate(9, 1, 700001)); QVERIFY(!encoder.requestBitrate(9, 1, 299999)); auto bad = config; bad.generation = 10; QVERIFY(!encoder.open(bad, 500000, "unavailable")); bad.width = 1920; bad.height = 1080; QVERIFY(!encoder.open(bad, 500000)); // Level 3.1 не допускает этот размер. bad = config; bad.profile = 4; QVERIFY(!encoder.open(bad, 500000)); QVERIFY(encoder.requestBitrate(9, 1, 500000)); // Неизменный target подтверждается на KEY. - QVERIFY(encoder.encode(image, 133333, false, output, completion)); QVERIFY(!completion.request && !output.key); + QVERIFY(encoder.encode(image, 133333, false, output, completion)); QCOMPARE(output.size(), 1); + QVERIFY(!completion.request && !output.front().key); QVERIFY(encoder.encode(image, 166666, true, output, completion)); - QCOMPARE(completion.request, uint64_t(1)); QCOMPARE(completion.bitrate, 500000u); QVERIFY(output.key); - auto saved = output; QVERIFY(!encoder.encode(image, 166666, false, output, completion)); QCOMPARE(output.data, saved.data); + QCOMPARE(completion.request, uint64_t(1)); QCOMPARE(completion.bitrate, 500000u); QVERIFY(output.front().key); + QVERIFY(!encoder.encode(image, 166666, false, output, completion)); QVERIFY(output.isEmpty()); // Пропуск capture-кадров не откладывает IDR на количество пропущенных кадров. QVERIFY(encoder.requestBitrate(9, 2, 300000)); uint64_t pts = 1500000; QElapsedTimer deadline; deadline.start(); do { - QVERIFY(encoder.encode(image, pts, false, output, completion)); QVERIFY(output.key); pts += 1000000; + QVERIFY(encoder.encode(image, pts, false, output, completion)); QCOMPARE(output.size(), 1); + QVERIFY(output.front().key); pts += 1000000; if (!completion.request) QTest::qWait(33); } while (!completion.request && deadline.elapsed() < 3000); QCOMPARE(completion.request, uint64_t(2)); QCOMPARE(completion.bitrate, 300000u); QVERIFY(output.key);