diff --git a/src/call/call_timeline.cpp b/src/call/call_timeline.cpp index 423d4dac..500fc44c 100644 --- a/src/call/call_timeline.cpp +++ b/src/call/call_timeline.cpp @@ -143,10 +143,19 @@ void CallTimeline::progress(uint64_t deviceEpoch, uint64_t position, int64_t atU while (output.count > 1 && output.front().first + output.front().count <= minimumPosition) output.pop(); } +/* Устройство меняется, но PCM/PTS, монотонная позиция и сетевой резерв сохраняются. */ +void CallTimeline::suspend_device(uint64_t deviceEpoch) { + if (deviceEpoch != epoch) return; + anchorReceived = -1; + DEBUG_INFO(DEBUG_CATEGORY_CALL, "audio clock device suspended epoch=%llu position=%llu written=%llu", + (unsigned long long)epoch, (unsigned long long)anchorPosition, (unsigned long long)written); +} + call_clock_snapshot CallTimeline::clock(int64_t nowUs) { call_clock_snapshot result{}; result.epoch = epoch; result.written_samples = written; result.pts_us = lastPts; result.measured = measured; - if (outputFailed || deviceFailed || !haveAnchor || !output.count || nowUs < anchorReceived || nowUs - anchorReceived > 250000) return result; + if (outputFailed || deviceFailed || !haveAnchor || !output.count || anchorReceived < 0 || + nowUs < anchorReceived || nowUs - anchorReceived > 250000) return result; uint64_t pending = anchorTime > nowUs ? (uint64_t)std::ceil((anchorTime - nowUs) / sampleUs) : 0; uint64_t position = pending > anchorPosition ? 0 : anchorPosition - pending; position = std::max(position, minimumPosition); diff --git a/src/call/call_timeline.h b/src/call/call_timeline.h index 67ae058c..614e0dd5 100644 --- a/src/call/call_timeline.h +++ b/src/call/call_timeline.h @@ -18,6 +18,7 @@ public: void read(unsigned samples); void silence(unsigned samples); void progress(uint64_t epoch, uint64_t position, int64_t at_us, int64_t received_us, bool measured); + void suspend_device(uint64_t epoch); call_clock_snapshot clock(int64_t now_us); private: struct Span { diff --git a/tools/chatgui/tests/test_call_timeline.cpp b/tools/chatgui/tests/test_call_timeline.cpp index b15a490d..4a5302c6 100644 --- a/tools/chatgui/tests/test_call_timeline.cpp +++ b/tools/chatgui/tests/test_call_timeline.cpp @@ -130,8 +130,27 @@ static void test_failure_and_recovery() { DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS timeline: regressing/impossible device head, overread, bounded overflow, reset recovery"); } +static void test_device_reopen() { + CallTimeline map; + map.reset(7); map.feed(1000000, 1920, 1); + map.source({SourceKind::Copy, SourceError::None, 0, 0, 0, 1920}); map.read(1920); + map.progress(7, 960, 30000, 10000, true); + auto before = map.clock(20000); CHECK(before.valid); + map.suspend_device(6); CHECK(map.clock(20000).valid); + map.suspend_device(7); CHECK(!map.clock(20001).valid); + CHECK(map.clock(20001).written_samples == 1920); + map.progress(7, 1920, 70000, 50000, true); + auto after = map.clock(50000); + CHECK(after.valid && after.epoch == 7 && after.pts_us >= before.pts_us); + CHECK(after.played_samples == 960); + map.suspend_device(7); + map.progress(7, 1000, 80000, 60000, true); // Пересоздание не разрешает регрессию общей позиции. + CHECK(!map.clock(60000).valid); + DEBUG_INFO(DEBUG_CATEGORY_CALL, "PASS timeline: device reopen preserves PCM/epoch and rejects old/regressing anchors"); +} + int main() { debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); - test_boundaries(); test_mix(); test_hardware_anchor_transition(); test_long_run(); test_failure_and_recovery(); + test_boundaries(); test_mix(); test_hardware_anchor_transition(); test_long_run(); test_failure_and_recovery(); test_device_reopen(); return 0; }