diff --git a/src/call/call_clock.h b/src/call/call_clock.h new file mode 100644 index 00000000..98ab5d7c --- /dev/null +++ b/src/call/call_clock.h @@ -0,0 +1,14 @@ +/* Снимок часов входящего аудио. PTS принадлежит шкале захвата удалённой стороны. + * epoch — локальная эпоха устройства; source_epoch — поколение удалённого encoder. + * valid=0 запрещает продвижение видео по этому снимку. measured отличает аппаратную + * отметку от оценки backend. Тишина устройства/тоны удерживают PTS, remote mute — нет. */ +#ifndef CALL_CLOCK_H +#define CALL_CLOCK_H +#include +struct call_clock_snapshot { + uint64_t epoch, source_epoch, played_samples, written_samples; + int64_t pts_us; + uint32_t held_back_us; + int valid, measured; +}; +#endif diff --git a/src/call/call_timeline.h b/src/call/call_timeline.h new file mode 100644 index 00000000..d4b56997 --- /dev/null +++ b/src/call/call_timeline.h @@ -0,0 +1,55 @@ +/* Карта SoundTouch → PTS → устройство. Встроенные кольца, без аллокаций в DSP callback. + * feed/source/read — один playback worker. read/silence/progress/clock синхронизирует + * владелец с читателями clock; source/feed общих выходных данных не касаются. + * reset — после остановки доступа. Source Reset обнуляет только локальную карту DSP. + * Ограничения не меняют PCM: ошибка карты делает clock недостоверным до reset. */ +#ifndef CALL_TIMELINE_H +#define CALL_TIMELINE_H +#include "call_clock.h" +#include "soundtouch/SourceSpan.h" +#include +#include + +class CallTimeline { +public: + void reset(uint64_t epoch); + void feed(int64_t pts_us, unsigned samples, uint64_t source_epoch); + void source(const soundtouch::SourceSpan& span); + 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); + call_clock_snapshot clock(int64_t now_us); +private: + struct Span { + uint64_t first = 0, epoch = 0; + unsigned count = 0; + double pts = 0, end = 0; + bool valid = false; + }; + template struct Ring { + std::array data{}; + size_t head = 0, count = 0; + Span& front() { return data[head]; } + const Span& back() const { return data[(head + count - 1) % N]; } + void pop() { head = (head + 1) % N; --count; } + void clear() { head = count = 0; } + bool push(const Span& s) { + if (count == N) return false; + data[(head + count++) % N] = s; return true; + } + }; + Ring<256> input; // >5с входа; SoundTouch звонка держит лишь несколько блоков. + Ring<1024> ready; + Ring<2048> output; + uint64_t inputWritten = 0, generated = 0, consumed = 0, written = 0, epoch = 0; + uint64_t anchorPosition = 0, minimumPosition = 0, lastSourceEpoch = 0; + int64_t anchorTime = 0, anchorReceived = 0, lastPts = -1; + double endPts = 0; + uint64_t endEpoch = 0; + int sourceError = 0; + bool outputFailed = false, haveAnchor = false, measured = false, endValid = false; + static double at(const Span& s, double position); + const Span* findInput(uint64_t position) const; + void appendOutput(const Span& s); +}; +#endif