#ifndef VOICE_JITTER_H #define VOICE_JITTER_H // voice_jitter.h — адаптивный джиттер-буфер голоса с time-stretch (SoundTouch), C API. // // Хранит КОДИРОВАННЫЕ (Opus) кадры: uasync-поток кладёт кодированные кадры, // аудио-поток на pull декодирует их (через коллбэк decode) и пропускает через // SoundTouch с плавно меняющимся темпом. Излишек голоса держится до декодирования — // декодируется только то, что готово к воспроизведению (экономия памяти ~48x и // разгрузка uasync-потока). // // Потоки: // - vj_push — uasync-поток (producer); // - vj_pull — аудио-поток (consumer; единственный владелец decode/SoundTouch); // - vj_get_stats — любой поток. // Кольцо кодированных кадров защищено мьютексом (SPSC, дёшево: ~50 кадров/с). #include #ifdef __cplusplus extern "C" { #endif struct vj; /* decode: закодированный кадр → PCM. Возвращает число декодированных сэмплов. */ typedef int (*vj_decode_fn)(void* arg, const uint8_t* enc, int len, int16_t* pcm, int max_samples); /* Полная конфигурация адаптивного джиттер-буфера. */ struct vj_cfg { int sample_rate; int channels; int frame_samples; /* сэмплов в кадре (960 @20мс/48k) */ int frame_ms; /* длительность кадра в мс */ int max_frames; /* ёмкость кодированных кадров (50 = 1000мс) */ double target_ms; /* целевая глубина буфера */ double min_tempo; /* темп при пустом буфере (<1.0 = замедление, накопление) */ double max_tempo; /* темп при глубоком буфере (>1.0 = догон live) */ int pre_roll; /* 1 = не отдавать звук, пока глубина < target_ms */ vj_decode_fn decode; void* decode_arg; }; /* Создать буфер с полной конфигурацией (pre-roll, симметричный темп). */ struct vj* vj_create_cfg(const struct vj_cfg* cfg); /* Создать буфер с конфигурацией по умолчанию (совместимо со звонком: без pre-roll, * темп 1.0..1.6, target 60мс). frame_samples — сэмплов в кадре (960 @20мс/48k). */ struct vj* vj_create(int sample_rate, int channels, int frame_samples, int frame_ms, int max_frames, vj_decode_fn decode, void* decode_arg); void vj_destroy(struct vj* j); /* producer: положить кодированный кадр (копируется). При переполнении дропает старейший. */ void vj_push(struct vj* j, const uint8_t* enc, int len); /* consumer: выдать до max_samples декодированного+растянутого PCM. Может вернуть * < max_samples при нехватке данных (андерфлоу). */ int vj_pull(struct vj* j, int16_t* out, int max_samples); /* audio-поток: вход закончен (FIN) — принудительно снять pre-roll, чтобы дренировать * накопленный остаток до конца. Безопасно вызывать многократно. */ void vj_end(struct vj* j); /* Диагностика: глубина буфера (мс), темп (×100, 100 = 1.0x), дропнуто кадров * при переполнении, число underrun-ов (pull вернул меньше запрошенного). */ void vj_get_stats(struct vj* j, int* depth_ms, int* tempo_x100, uint32_t* dropped, uint32_t* underruns); /* Число дропнутых кадров при переполнении (для диагностики/тестов). */ uint32_t vj_dropped(struct vj* j); /* Число хард-ресинков к live (после сталла аудио-девайса) — диагностика. */ uint32_t vj_resyncs(struct vj* j); #ifdef __cplusplus } #endif #endif /* VOICE_JITTER_H */