Verify generation changes and stale ACK isolation after restart

This commit is contained in:
evgeny committed 2026-10-19 21:18:03 +02:00
1 parent 9913222e03
commit 55e87d20bf
3 files changed
+19 -9

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ struct CallVideoEngine::Impl {
std::mutex lock;
std::condition_variable wake;
CallVideoWake rxWake;
CallVideoWake displayWake, guiWake;
CallVideoWake displayWake, guiWake{false};
QObject* guiNotifier = nullptr;
struct Picture { call_video_playout_frame timing; QImage image; };
CallVideoQueue<Picture, 3> pictures;
+7 -6
View File
@@ -19,24 +19,25 @@
class CallVideoWake {
std::atomic<uint64_t> serial{0};
std::atomic<unsigned> error{0};
bool timerRequired;
#ifdef _WIN32
HANDLE event = nullptr, timer = nullptr;
#else
int pipefd[2] = {-1, -1};
#endif
public:
CallVideoWake() {
explicit CallVideoWake(bool withTimer = true) : timerRequired(withTimer) {
#ifdef _WIN32
event = CreateEventW(nullptr, FALSE, FALSE, nullptr);
constexpr DWORD highResolution = 0x00000002; // CREATE_WAITABLE_TIMER_HIGH_RESOLUTION (Windows 10 1803+).
timer = CreateWaitableTimerExW(nullptr, nullptr, highResolution, TIMER_MODIFY_STATE | SYNCHRONIZE);
if (!timer) {
if (timerRequired) timer = CreateWaitableTimerExW(nullptr, nullptr, highResolution, TIMER_MODIFY_STATE | SYNCHRONIZE);
if (timerRequired && !timer) {
DEBUG_WARN(DEBUG_CATEGORY_CALL, "video high resolution timer unavailable error=%lu; using regular timer", GetLastError());
timer = CreateWaitableTimerW(nullptr, FALSE, nullptr);
}
if (!event || !timer) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video wait handles unavailable event=%p timer=%p error=%lu",
if (!event || (timerRequired && !timer)) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video wait handles unavailable event=%p timer=%p error=%lu",
event, timer, GetLastError());
else DEBUG_INFO(DEBUG_CATEGORY_CALL, "video RX waitable timer ready event=%p timer=%p", event, timer);
else DEBUG_INFO(DEBUG_CATEGORY_CALL, "video wake ready event=%p timer=%p timed=%d", event, timer, timerRequired);
#else
if (pipe(pipefd) || fcntl(pipefd[0], F_SETFL, O_NONBLOCK) < 0 || fcntl(pipefd[1], F_SETFL, O_NONBLOCK) < 0
|| fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) < 0 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) < 0) {
@@ -55,7 +56,7 @@ public:
}
bool valid() const {
#ifdef _WIN32
return event && timer;
return event && (!timerRequired || timer);
#else
return pipefd[0] >= 0 && pipefd[0] < FD_SETSIZE;
#endif
+11 -2
View File
@@ -70,6 +70,7 @@ extern "C" int __wrap_call_video_port_pull(uint64_t id, call_video_frame* out) {
// Кадр уже пришёл: остановка RX worker не должна подменить время доступности данных.
if (disturbed && cursor == 60) QThread::msleep(80);
const auto& f = frames[cursor.load()]; *out = {}; out->call_id = id; out->format = format;
if (cursor >= 45) ++out->format.generation;
out->pts_us = f.pts; out->frame_id = ++cursor; out->flags = f.key ? CALL_VIDEO_KEY : 0;
out->received_us = received;
out->size = out->length = f.data.size(); auto* data = static_cast<uint8_t*>(u_malloc(out->size));
@@ -112,12 +113,13 @@ int main(int argc, char** argv) {
QImage image(640, 360, QImage::Format_RGB32); image.fill(Qt::blue);
for (int i = 0; i < 90; ++i) {
QList<LiveEncodedFrame> output; CallVideoRateCompletion completion;
if (!encoder.encode(image, 1000000 + i * 33333ll, i == 0, output, completion) || output.size() != 1) return 3;
if (!encoder.encode(image, 1000000 + i * 33333ll, i == 0 || i == 45, output, completion) || output.size() != 1) return 3;
frames.push_back(output.front());
}
CallVideoEngine engine; engine.setPresentationDriven(true); std::vector<int64_t> published, presented;
quint64 lastSerial = 0;
QObject::connect(&engine, &CallVideoEngine::imageChanged, [&](const QImage& frame, quint64 serial) {
if (!frame.isNull()) published.push_back(nowUs());
if (!frame.isNull()) { published.push_back(nowUs()); lastSerial = serial; }
if (holdAck) return;
// Подтверждение потребления отдельно от доставки signal, как paintEvent окна.
QTimer::singleShot(5, &engine, [&, serial, video = !frame.isNull()] {
@@ -156,6 +158,13 @@ int main(int argc, char** argv) {
}
const int64_t stopAt = nowUs(); engine.stop(); app.processEvents();
if (nowUs() - stopAt > 250000 || unsafeTimer) return 1;
const quint64 oldSerial = lastSerial;
cursor = 0; published.clear(); displayThread = 0; start = nowUs(); engine.start(7); stalled.restart();
while (stalled.elapsed() < 250) { app.processEvents(); engine.imagePresented(oldSerial); QThread::msleep(1); }
if (published.size() != 1 || cursor != 3 || lastSerial == oldSerial) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "video stale ACK consumed restarted slot published=%zu inputs=%d", published.size(), cursor.load()); return 1;
}
engine.stop(); app.processEvents();
DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS stop without GUI ACK: one selected + two ready, decoder and port never run on display thread");
return 0;
}