|
|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
// Буфер звонка: храним речь в порядке доставки, ускоряем накопление без изменения высоты голоса.
|
|
|
|
|
#include "call_jitter.h" |
|
|
|
|
#include "call_jitter_window.h" |
|
|
|
|
#include "call_timeline.h" |
|
|
|
|
#include <chrono> |
|
|
|
|
#include "../../lib/debug_config.h" |
|
|
|
|
#include "../../lib/mem.h" |
|
|
|
|
@ -28,6 +29,9 @@ struct call_jitter {
|
|
|
|
|
std::mutex mtx; |
|
|
|
|
uint8_t ring[CALL_JITTER_MAX_FRAMES][kMaxEncoded]; |
|
|
|
|
int lens[CALL_JITTER_MAX_FRAMES]; |
|
|
|
|
call_capture_time timing[CALL_JITTER_MAX_FRAMES]; |
|
|
|
|
CallTimeline timeline; |
|
|
|
|
uint64_t playback_epoch = 1; |
|
|
|
|
int head = 0, count = 0; |
|
|
|
|
uint32_t dropped = 0, underruns = 0; |
|
|
|
|
call_jitter_diagnostics diagnostics{}; |
|
|
|
|
@ -68,6 +72,13 @@ struct call_jitter* call_jitter_create(call_jitter_decode_fn decode, void* arg,
|
|
|
|
|
j->st.setSetting(SETTING_SEEKWINDOW_MS, 10); |
|
|
|
|
j->st.setSetting(SETTING_OVERLAP_MS, 4); |
|
|
|
|
j->st.setSetting(SETTING_USE_AA_FILTER, 0); |
|
|
|
|
j->timeline.reset(j->playback_epoch); |
|
|
|
|
if (!j->st.setSourceCallback([](void* context, const soundtouch::SourceSpan& span) { |
|
|
|
|
static_cast<call_jitter*>(context)->timeline.source(span); |
|
|
|
|
}, j)) { |
|
|
|
|
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call_jitter: SoundTouch source tracking unavailable"); |
|
|
|
|
j->~call_jitter(); u_free(j); return nullptr; |
|
|
|
|
} |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_CALL, "call_jitter: created capacity=%dms target=%dms max_reserve=%dms tempo=1.0..%.1f ramp=%dms", |
|
|
|
|
CALL_JITTER_MAX_FRAMES * CALL_JITTER_FRAME_MS, kTargetMs, max_reserve_ms, kMaxTempo, kTempoRampMs); |
|
|
|
|
return j; |
|
|
|
|
@ -78,7 +89,8 @@ void call_jitter_destroy(struct call_jitter* j) {
|
|
|
|
|
j->~call_jitter(); u_free(j); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_jitter_push(struct call_jitter* j, const uint8_t* enc, int len, uint32_t sent_ms, int64_t received_ms) { |
|
|
|
|
void call_jitter_push(struct call_jitter* j, const uint8_t* enc, int len, uint32_t sent_ms, int64_t received_ms, |
|
|
|
|
const struct call_capture_time* timing) { |
|
|
|
|
if (!j || !enc || len <= 0 || len > kMaxEncoded || received_ms < 0) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call_jitter: invalid frame len=%d received=%lldms", len, (long long)received_ms); |
|
|
|
|
return; |
|
|
|
|
@ -95,6 +107,7 @@ void call_jitter_push(struct call_jitter* j, const uint8_t* enc, int len, uint32
|
|
|
|
|
j->head = (j->head + 1) % CALL_JITTER_MAX_FRAMES; j->count--; j->dropped++; |
|
|
|
|
} |
|
|
|
|
int idx = (j->head + j->count) % CALL_JITTER_MAX_FRAMES; |
|
|
|
|
j->timing[idx] = timing ? *timing : call_capture_time{INT64_MIN, 0, 0}; |
|
|
|
|
std::memcpy(j->ring[idx], enc, (size_t)len); j->lens[idx] = len; j->count++; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -139,10 +152,12 @@ int call_jitter_pull(struct call_jitter* j, int16_t* out, int max_samples) {
|
|
|
|
|
int attempted = 0; |
|
|
|
|
while (j->st.numSamples() < (uint)max_samples && attempted++ < budget) { |
|
|
|
|
int len; |
|
|
|
|
call_capture_time timing; |
|
|
|
|
{ |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
if (!j->count) break; |
|
|
|
|
len = j->lens[j->head]; std::memcpy(enc, j->ring[j->head], (size_t)len); |
|
|
|
|
timing = j->timing[j->head]; |
|
|
|
|
j->head = (j->head + 1) % CALL_JITTER_MAX_FRAMES; j->count--; |
|
|
|
|
} |
|
|
|
|
int n = j->decode(j->arg, enc, len, j->pcm, kFrameSamples); |
|
|
|
|
@ -152,6 +167,8 @@ int call_jitter_pull(struct call_jitter* j, int16_t* out, int max_samples) {
|
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
for (int i = 0; i < n; i++) j->input[i] = j->pcm[i] / 32768.0f; |
|
|
|
|
int64_t pts = timing.pts_us == INT64_MIN ? INT64_MIN : timing.pts_us - (int64_t)timing.delay_samples * 1000000 / kSampleRate; |
|
|
|
|
j->timeline.feed(pts, (unsigned)n, timing.epoch); |
|
|
|
|
j->st.putSamples(j->input, (uint)n); |
|
|
|
|
} |
|
|
|
|
j->output.resize((size_t)max_samples); |
|
|
|
|
@ -160,6 +177,7 @@ int call_jitter_pull(struct call_jitter* j, int16_t* out, int max_samples) {
|
|
|
|
|
int processing_ms = (int)std::lround((j->st.numUnprocessedSamples() + j->st.numSamples() * j->tempo) * 1000.0 / kSampleRate); |
|
|
|
|
{ |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
j->timeline.read((unsigned)n); |
|
|
|
|
j->processing_ms = processing_ms; j->tempo_x100 = (int)std::lround(j->tempo * 100); |
|
|
|
|
if (n < max_samples) j->underruns++; |
|
|
|
|
j->diagnostics.depth_ms = (int)depth; |
|
|
|
|
@ -185,6 +203,24 @@ void call_jitter_take_diagnostics(struct call_jitter* j, struct call_jitter_diag
|
|
|
|
|
j->diagnostics.resumed = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_jitter_silence(struct call_jitter* j, unsigned samples) { |
|
|
|
|
if (!j) return; |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
j->timeline.silence(samples); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_jitter_progress(struct call_jitter* j, uint64_t epoch, uint64_t position, int64_t at_us, int64_t now_us, int measured) { |
|
|
|
|
if (!j) return; |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
j->timeline.progress(epoch, position, at_us, now_us, measured != 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_jitter_clock(struct call_jitter* j, int64_t now_us, struct call_clock_snapshot* out) { |
|
|
|
|
if (!j || !out) return; |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
*out = j->timeline.clock(now_us); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void call_jitter_reset(struct call_jitter* j) { |
|
|
|
|
if (!j) return; |
|
|
|
|
std::lock_guard<std::mutex> lock(j->mtx); |
|
|
|
|
@ -194,6 +230,7 @@ void call_jitter_reset(struct call_jitter* j) {
|
|
|
|
|
j->count = keep; |
|
|
|
|
j->dropped += discard; |
|
|
|
|
j->st.clear(); j->st.setTempo(1.0); |
|
|
|
|
j->timeline.reset(++j->playback_epoch); |
|
|
|
|
j->primed = false; j->catching_up = false; |
|
|
|
|
j->tempo = 1.0; j->ema_ms = j->arrival_window.reserve; |
|
|
|
|
j->processing_ms = 0; j->tempo_x100 = 100; |
|
|
|
|
|