Verify generation changes and stale ACK isolation after restart
This commit is contained in:
1 parent
9913222e03
commit
55e87d20bf
3 files changed
+19
-9
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user