// Буфер звонка: храним речь в порядке доставки, ускоряем накопление без изменения высоты голоса. #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; 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; } bool log_arrival = j->arrival_window.observe_arrival(received_ms, sent_ms); j->range.reserve_ms = j->arrival_window.reserve; if (log_arrival) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call_jitter: delivery variation=%lldms reserve=%dms queued=%dms", (long long)(j->arrival_window.high - j->arrival_window.low), j->arrival_window.reserve, j->count * CALL_JITTER_FRAME_MS); if (j->count == CALL_JITTER_MAX_FRAMES) { j->head = (j->head + 1) % CALL_JITTER_MAX_FRAMES; j->count--; j->dropped++; if (j->dropped == 1 || j->dropped % 50 == 0) DEBUG_WARN(DEBUG_CATEGORY_CALL, "call_jitter: overflow capacity=15000ms dropped=%u", 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(); bool log_stats = j->depth_window.observe(now_ms, (int)std::lround(depth)); { 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 (log_stats) DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call_jitter: depth=%.0fms min=%lldms max=%lldms reserve=%dms tempo=%.2f", depth, (long long)j->depth_window.low, (long long)j->depth_window.high, reserve, j->tempo); if (!j->primed) { if (depth < reserve) return 0; j->primed = true; DEBUG_INFO(DEBUG_CATEGORY_CALL, "call_jitter: playback resumed depth=%.0fms reserve=%dms", depth, reserve); } 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; if (catching != j->catching_up) { j->catching_up = catching; DEBUG_INFO(DEBUG_CATEGORY_CALL, "call_jitter: catch-up %s depth=%.0fms tempo=%.2f", catching ? "started" : "finished", depth, j->tempo); } uint8_t enc[kMaxEncoded]; while (j->st.numSamples() < (uint)max_samples) { 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) { DEBUG_WARN(DEBUG_CATEGORY_CALL, "call_jitter: decode failed len=%d result=%d", len, n); 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++; } if (n < max_samples) { j->primed = false; DEBUG_DEBUG(DEBUG_CATEGORY_CALL, "call_jitter: waiting for refill pcm=%d/%d processing=%dms", n, max_samples, processing_ms); } return n; } 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; } }