diff --git a/tools/chatgui/src/call_video_engine.cpp b/tools/chatgui/src/call_video_engine.cpp index 51ed72e3..9e10ee05 100644 --- a/tools/chatgui/src/call_video_engine.cpp +++ b/tools/chatgui/src/call_video_engine.cpp @@ -146,6 +146,8 @@ void CallVideoEngine::transmit() { CallVideoEncoder encoder; call_video_format format{}; uint64_t appliedRevision = 0, keySerial = 0, request = 0, frameId = 0; + uint64_t userRevision = 0, rejectedGeneration = 0; + unsigned excluded = 0; int64_t lastPts = -1, lastKey = -1; bool forceKey = true; while (m->running) { @@ -161,11 +163,28 @@ void CallVideoEngine::transmit() { raw = m->raw; #endif } + if (revision != userRevision) { userRevision = revision; excluded = 0; } + if (format.generation && state.decoded_generation == format.generation && !state.decoded_accepted + && rejectedGeneration != format.generation) { + rejectedGeneration = format.generation; excluded |= 1u << format.codec; + if (!codec) { appliedRevision = 0; lastKey = -1; m->report(tr("Receiver rejected video; selecting fallback")); } + else { + m->report(tr("Receiver cannot decode selected codec")); + std::lock_guard guard(m->lock); m->enabled = false; continue; + } + } + if (format.generation && state.have_peer && !call_video_format_supported(&caps, &state.peer, &format)) appliedRevision = 0; if (!enabled || !state.have_peer || pts <= lastPts || (lastPts >= 0 && pts - lastPts < 33333)) { idle(); continue; } if (revision != appliedRevision) { if (lastKey >= 0 && pts - lastKey < 1000000) { idle(); continue; } call_video_format next{format.generation + 1, (uint16_t)width, (uint16_t)height, 30, 0, (uint8_t)codec, 0, 4000000, 0}; - if (call_video_choose(&caps, &state.peer, &next) <= 0 || !encoder.open(next, 1000000)) { + bool opened = false; + for (int candidate = 2; candidate >= 1; --candidate) { + if ((codec && codec != candidate) || (excluded & (1u << candidate))) continue; + next.codec = candidate; next.level = next.profile = 0; + if (call_video_choose(&caps, &state.peer, &next) > 0 && encoder.open(next, 1000000)) { opened = true; break; } + } + if (!opened) { m->report(tr("Selected video mode is unavailable to the peer")); std::lock_guard guard(m->lock); if (m->revision == revision) m->enabled = false; continue; @@ -209,6 +228,7 @@ void CallVideoEngine::receive() { std::deque pictures; CallVideoDecoder decoder; uint64_t sourceEpoch = 0; int64_t lastShown = -1, report = 0; unsigned shown = 0; + uint64_t reportedGeneration = 0, rejectedGeneration = 0; while (m->running) { int64_t now = nowUs(); call_clock_snapshot clock{}; call_audio_get_clock(m->id, now, &clock); @@ -228,9 +248,16 @@ void CallVideoEngine::receive() { if (pictures.size() < 16) { call_video_frame packet{}; if (call_video_port_pull(m->id, &packet) == 1) { + if (packet.format.generation == rejectedGeneration) { u_free((void*)packet.data); continue; } QImage image; - if (decoder.decode(packet, image)) pictures.push_back({packet.pts_us, image}); - else call_video_port_request_key(m->id); + if (decoder.decode(packet, image)) { + pictures.push_back({packet.pts_us, image}); + if (reportedGeneration != packet.format.generation) { + reportedGeneration = packet.format.generation; call_video_port_decoded(m->id, reportedGeneration, 1); + } + } else { + rejectedGeneration = packet.format.generation; call_video_port_decoded(m->id, rejectedGeneration, 0); + } u_free((void*)packet.data); continue; } } diff --git a/tools/chatgui/tests/test_call_video_encoder.cpp b/tools/chatgui/tests/test_call_video_encoder.cpp index e4a0c7ed..de7e1784 100644 --- a/tools/chatgui/tests/test_call_video_encoder.cpp +++ b/tools/chatgui/tests/test_call_video_encoder.cpp @@ -45,6 +45,11 @@ private slots: void switchesAtScheduledIdr_data() { QTest::addColumn("codec"); QTest::newRow("H264") << 1; QTest::newRow("H265") << 2; } + void independentDecoders() { + auto caps = CallVideoDecoder::capabilities(); + QCOMPARE(caps.decode[0].profiles, uint8_t(3)); QCOMPARE(caps.decode[1].profiles, uint8_t(1)); + QCOMPARE(caps.encode[0].width, uint16_t(0)); QCOMPARE(caps.encode[1].width, uint16_t(0)); + } void switchesAtScheduledIdr() { QFETCH(int, codec); auto config = configuration(codec);