You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
183 lines
8.5 KiB
183 lines
8.5 KiB
// Буфер звонка: храним речь в порядке доставки, ускоряем накопление без изменения высоты голоса. |
|
#include "call_jitter.h" |
|
#include "call_jitter_window.h" |
|
#include <chrono> |
|
#include "../../lib/debug_config.h" |
|
#include "../../lib/mem.h" |
|
#include "soundtouch/SoundTouch.h" |
|
|
|
#include <algorithm> |
|
#include <cmath> |
|
#include <cstring> |
|
#include <mutex> |
|
#include <new> |
|
#include <vector> |
|
|
|
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<typename T> 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<float> 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<std::mutex> 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<std::mutex> 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::milliseconds>( |
|
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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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; |
|
} |
|
}
|
|
|