Browse Source

Выдавать готовые кадры GPU без дополнительного ожидания в TX

stream-multiview
evgeny 6 days ago
parent
commit
ae8eeab490
  1. 19
      tools/chatgui/src/call_video_engine.cpp
  2. 8
      tools/chatgui/tests/test_call_video_clock.cpp
  3. 29
      tools/chatgui/tests/test_call_video_encoder.cpp

19
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<LiveEncodedFrame> 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<const uint8_t*>(output.data.constData());
packet.flags = output.key ? CALL_VIDEO_KEY : 0;
{ std::lock_guard<std::mutex> 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

8
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<LiveEncodedFrame> 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<int64_t> published;
QObject::connect(&engine, &CallVideoEngine::imageChanged, [&](const QImage& frame) {

29
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<LiveEncodedFrame> 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<LiveEncodedFrame> 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<LiveEncodedFrame> 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);

Loading…
Cancel
Save