// Буфер звонка: храним речь в порядке доставки, ускоряем накопление без изменения высоты голоса. #include "call_jitter.h" #include "call_jitter_window.h" #include #include "../../lib/debug_config.h" #include "../../lib/mem.h" #include "soundtouch/SoundTouch.h" #include #include #include #include #include #include static constexpr int kSampleRate = 48000; static constexpr int kFrameSamples = 960; static constexpr int kMaxEncoded = 512; static constexpr int kTargetMs = 60; static constexpr double kMaxTempo = 1.6; static constexpr int kTempoRampMs = 500; template static T clamp_value(T value, T low, T high) { return std::max(low, std::min(value, high)); } struct call_jitter { std::mutex mtx; uint8_t ring[CALL_JITTER_MAX_FRAMES][kMaxEncoded]; int lens[CALL_JITTER_MAX_FRAMES]; int head = 0, count = 0; uint32_t dropped = 0, underruns = 0; call_jitter_diagnostics diagnostics{}; int processing_ms = 0, tempo_x100 = 100; /* снимок аудиопотока под mtx */ call_jitter_window depth_window, arrival_window; call_jitter_range range = {0, 0, 60}; /* опубликованный снимок под mtx */ call_jitter_decode_fn decode; void* arg; soundtouch::SoundTouch st; int16_t pcm[kFrameSamples]; float input[kFrameSamples]; std::vector output; bool primed = false, catching_up = false; double tempo = 1.0, ema_ms = kTargetMs; }; extern "C" { struct call_jitter* call_jitter_create(call_jitter_decode_fn decode, void* arg, int max_reserve_ms) { if (max_reserve_ms < 500 || max_reserve_ms > 2000) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call_jitter: invalid max reserve=%dms", max_reserve_ms); return nullptr; } if (!decode) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call_jitter: missing decoder"); return nullptr; } void* memory = u_malloc(sizeof(call_jitter)); if (!memory) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call_jitter: allocation failed"); return nullptr; } auto* j = new (memory) call_jitter; j->arrival_window.max_reserve = max_reserve_ms; j->decode = decode; j->arg = arg; j->st.setSampleRate(kSampleRate); j->st.setChannels(1); j->st.setTempo(1.0); j->st.setSetting(SETTING_USE_QUICKSEEK, 1); j->st.setSetting(SETTING_SEQUENCE_MS, 20); j->st.setSetting(SETTING_SEEKWINDOW_MS, 10); j->st.setSetting(SETTING_OVERLAP_MS, 4); j->st.setSetting(SETTING_USE_AA_FILTER, 0); 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; } void call_jitter_destroy(struct call_jitter* j) { if (!j) return; 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) { 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; } std::lock_guard lock(j->mtx); if (received_ms < j->arrival_window.last_received_ms) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "call_jitter: receive clock moved backwards previous=%lldms now=%lldms", (long long)j->arrival_window.last_received_ms, (long long)received_ms); return; } j->arrival_window.observe_arrival(received_ms, sent_ms); j->range.reserve_ms = j->arrival_window.reserve; if (j->count == CALL_JITTER_MAX_FRAMES) { j->head = (j->head + 1) % CALL_JITTER_MAX_FRAMES; j->count--; j->dropped++; } int idx = (j->head + j->count) % CALL_JITTER_MAX_FRAMES; std::memcpy(j->ring[idx], enc, (size_t)len); j->lens[idx] = len; j->count++; } int call_jitter_pull(struct call_jitter* j, int16_t* out, int max_samples) { if (!j || !out || max_samples <= 0) return 0; int count, reserve; { std::lock_guard lock(j->mtx); count = j->count; reserve = j->arrival_window.reserve; } // Готовый PCM учитываем в длительности исходной речи. double depth = count * CALL_JITTER_FRAME_MS + (j->st.numUnprocessedSamples() + j->st.numSamples() * j->tempo) * 1000.0 / kSampleRate; int64_t now_ms = std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); j->depth_window.observe(now_ms, (int)std::lround(depth)); int resumed = 0; { std::lock_guard lock(j->mtx); j->range.min_ms = (int)j->depth_window.low; j->range.max_ms = (int)j->depth_window.high; } if (!j->primed) { if (depth < reserve) { std::lock_guard lock(j->mtx); j->diagnostics.depth_ms = (int)depth; j->diagnostics.reserve_ms = reserve; j->diagnostics.tempo_x100 = j->tempo_x100; j->diagnostics.waiting = 1; return 0; } j->primed = true; resumed = 1; } j->ema_ms += 0.2 * (depth - j->ema_ms); double target = 1.0 + (kMaxTempo - 1.0) * clamp_value((std::min(depth, j->ema_ms) - reserve - 15.0) / 925.0, 0.0, 1.0); // Линейный предел 1.0→1.6 за 500мс PCM. Снижение следует за глубиной буфера с тем же пределом. double step = (kMaxTempo - 1.0) * max_samples * 1000.0 / (kSampleRate * kTempoRampMs); j->tempo += clamp_value(target - j->tempo, -step, step); j->st.setTempo(j->tempo); bool catching = j->tempo > 1.05; j->catching_up = catching; uint32_t decode_errors = 0; int error_result = 0, error_len = 0; uint8_t enc[kMaxEncoded]; // Бюджет учитывает ускорение 1.6 и начальное накопление SoundTouch, включая битые пакеты. int budget = std::min(CALL_JITTER_MAX_FRAMES, 8 + max_samples / 480); int attempted = 0; while (j->st.numSamples() < (uint)max_samples && attempted++ < budget) { int len; { std::lock_guard lock(j->mtx); if (!j->count) break; len = j->lens[j->head]; std::memcpy(enc, j->ring[j->head], (size_t)len); j->head = (j->head + 1) % CALL_JITTER_MAX_FRAMES; j->count--; } int n = j->decode(j->arg, enc, len, j->pcm, kFrameSamples); if (n <= 0 || n > kFrameSamples) { ++decode_errors; error_result = n; error_len = len; continue; } for (int i = 0; i < n; i++) j->input[i] = j->pcm[i] / 32768.0f; j->st.putSamples(j->input, (uint)n); } j->output.resize((size_t)max_samples); int n = (int)j->st.receiveSamples(j->output.data(), (uint)max_samples); for (int i = 0; i < n; i++) out[i] = (int16_t)std::lrintf(clamp_value(j->output[i] * 32767.0f, -32768.0f, 32767.0f)); int processing_ms = (int)std::lround((j->st.numUnprocessedSamples() + j->st.numSamples() * j->tempo) * 1000.0 / kSampleRate); { std::lock_guard lock(j->mtx); 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; j->diagnostics.reserve_ms = reserve; j->diagnostics.tempo_x100 = j->tempo_x100; j->diagnostics.decode_errors += decode_errors; if (decode_errors) { j->diagnostics.error_result = error_result; j->diagnostics.error_len = error_len; } if (attempted > budget) ++j->diagnostics.budget_hits; j->diagnostics.resumed |= resumed; j->diagnostics.catching_up = catching; j->diagnostics.waiting = n < max_samples; } if (n < max_samples) { j->primed = false; } return n; } void call_jitter_take_diagnostics(struct call_jitter* j, struct call_jitter_diagnostics* out) { std::lock_guard lock(j->mtx); *out = j->diagnostics; j->diagnostics.decode_errors = j->diagnostics.budget_hits = 0; j->diagnostics.resumed = 0; } void call_jitter_reset(struct call_jitter* j) { if (!j) return; std::lock_guard lock(j->mtx); int keep = std::min(j->count, (j->arrival_window.reserve + CALL_JITTER_FRAME_MS - 1) / CALL_JITTER_FRAME_MS); int discard = j->count - keep; j->head = (j->head + discard) % CALL_JITTER_MAX_FRAMES; j->count = keep; j->dropped += discard; j->st.clear(); j->st.setTempo(1.0); 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; j->depth_window = {}; j->diagnostics = {}; j->range.min_ms = j->range.max_ms = keep * CALL_JITTER_FRAME_MS; DEBUG_INFO(DEBUG_CATEGORY_CALL, "call_jitter: device discontinuity discarded=%dms kept=%dms reserve=%dms", discard * CALL_JITTER_FRAME_MS, keep * CALL_JITTER_FRAME_MS, j->arrival_window.reserve); } void call_jitter_get_stats(struct call_jitter* j, int* depth_ms, int* tempo_x100, uint32_t* dropped, uint32_t* underruns, struct call_jitter_range* range) { if (!j) return; std::lock_guard lock(j->mtx); if (depth_ms) *depth_ms = j->count * CALL_JITTER_FRAME_MS + j->processing_ms; if (tempo_x100) *tempo_x100 = j->tempo_x100; if (range) *range = j->range; if (dropped) *dropped = j->dropped; if (underruns) *underruns = j->underruns; } }