Browse Source

Preserve capture PTS and expose call playback clock to platforms

master
evgeny 5 days ago
parent
commit
9aba8e278c
  1. 65
      src/call/call_audio.c
  2. 15
      src/call/call_audio.h

65
src/call/call_audio.c

@ -48,6 +48,9 @@ static int g_prepared_capture = 0;
static int g_aec_enabled = 1; /* по умолчанию: настройка aec_enabled */
static struct audio_compressor* g_compressor = NULL;
static uint64_t g_tx_generation;
static uint64_t g_capture_epoch;
static int64_t g_capture_end = INT64_MIN;
static int g_codec_delay;
static unsigned int g_tx_pending, g_tx_dropped, g_tx_reported;
static unsigned int g_tx_reasons;
static int g_tx_encode_error;
@ -59,6 +62,7 @@ struct call_audio_tx {
struct UTUN_INSTANCE* inst;
uint64_t call_id, generation, created_tb;
int len;
struct call_capture_time timing;
uint8_t opus[256];
};
@ -73,7 +77,7 @@ static void call_audio_send(void* arg) {
if (current) { ++g_tx_dropped; g_tx_reasons |= 16; }
}
pthread_mutex_unlock(&g_mtx);
if (send) call_send_media(tx->inst, tx->call_id, tx->opus, tx->len);
if (send) call_send_media(tx->inst, tx->call_id, tx->opus, tx->len, &tx->timing);
}
static const struct call_audio_ops g_ops = {
@ -113,12 +117,12 @@ void call_audio_destroy(struct UTUN_INSTANCE* inst) {
/* ── uasync-поток: приём медиа (кладём кодированный кадр в jitter-буфер) ── */
void call_audio_on_media(struct UTUN_INSTANCE* inst, uint64_t call_id,
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len) {
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len, const struct call_capture_time* timing) {
(void)inst; (void)seq;
pthread_mutex_lock(&g_mtx);
if (g_active && call_id == g_call_id && g_vj && opus && len > 0) {
call_jitter_push(g_vj, opus, len, ts_ms, (int64_t)(get_time_tb() / 10));
call_jitter_push(g_vj, opus, len, ts_ms, (int64_t)(get_time_tb() / 10), timing);
}
pthread_mutex_unlock(&g_mtx);
}
@ -151,6 +155,13 @@ static int call_audio_start_impl(uint64_t call_id, int prepared) {
return -1;
}
opus_codec_encoder_bitrate_set(enc, CALL_AUDIO_BITRATE);
int codec_delay = 0;
if (opus_codec_encoder_delay(enc, &codec_delay) < 0) {
opus_codec_encoder_destroy(enc);
pthread_mutex_unlock(&g_mtx);
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: encoder delay unavailable", CALL_AUDIO_ID);
return -1;
}
opus_codec_decoder_t* dec = opus_codec_decoder_create(CALL_AUDIO_SAMPLE_RATE, 1);
if (!dec) {
@ -218,6 +229,7 @@ static int call_audio_start_impl(uint64_t call_id, int prepared) {
g_call_id = call_id;
g_active = 1;
++g_tx_generation; g_tx_dropped = g_tx_reported = 0;
g_capture_epoch = 1; g_capture_end = INT64_MIN; g_codec_delay = codec_delay;
g_tx_reasons = 0; g_tx_encode_error = 0;
g_ending = 0;
g_ended_started = 0;
@ -300,6 +312,7 @@ void call_audio_reset_io(uint64_t call_id, int playback) {
if (g_active && g_call_id == call_id) {
if (g_aec) speex_aec_reset(g_aec);
if (!playback && g_compressor) audio_compressor_reset(g_compressor);
if (!playback) { ++g_capture_epoch; g_capture_end = INT64_MIN; }
if (playback) {
call_jitter_reset(g_vj);
g_gap_samples = 0;
@ -353,8 +366,10 @@ void call_audio_set_aec_delay_frames(int frames) {
/* ── аудио-поток: TX / RX ── */
static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count, int muted, const uint8_t* mute_mask, int prepared) {
if (!pcm || count != CALL_AUDIO_FRAME_SAMPLES || (prepared && !mute_mask)) {
static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count, int muted, const uint8_t* mute_mask,
int prepared, int64_t capture_pts_us) {
if (!pcm || count != CALL_AUDIO_FRAME_SAMPLES || (prepared && !mute_mask)
|| (capture_pts_us != INT64_MIN && (capture_pts_us < 0 || capture_pts_us > INT64_MAX / 2))) {
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: invalid capture frame count=%d prepared=%d", CALL_AUDIO_ID, count, prepared);
return -1;
}
@ -375,6 +390,14 @@ static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count,
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: capture policy mismatch prepared=%d", CALL_AUDIO_ID, prepared);
return -1;
}
if (capture_pts_us != INT64_MIN) {
if (g_capture_end != INT64_MIN && (capture_pts_us < g_capture_end - 2000 || capture_pts_us > g_capture_end + 2000)) {
++g_capture_epoch;
DEBUG_WARN(DEBUG_CATEGORY_CALL, "call capture discontinuity epoch=%llu expected=%lld actual=%lld",
(unsigned long long)g_capture_epoch, (long long)g_capture_end, (long long)capture_pts_us);
}
g_capture_end = capture_pts_us + 20000;
} else g_capture_end = INT64_MIN;
const int16_t* tx = pcm;
if (g_aec) {
if (speex_aec_process_capture(g_aec, pcm, count, cleaned) == count)
@ -415,6 +438,7 @@ static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count,
}
task->inst = inst; task->call_id = call_id;
task->generation = g_tx_generation; task->created_tb = get_time_tb();
task->timing = (struct call_capture_time){capture_pts_us, g_capture_epoch, (uint32_t)g_codec_delay};
task->len = len;
memcpy(task->opus, opus, (size_t)len);
task->task.callback = call_audio_send; task->task.arg = task;
@ -425,16 +449,16 @@ static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count,
}
/* Маска соответствует границам mute в том же упорядоченном потоке, что и PCM. */
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask) {
return call_audio_feed_impl(call_id, pcm, CALL_AUDIO_FRAME_SAMPLES, 0, mute_mask, 1);
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us) {
return call_audio_feed_impl(call_id, pcm, CALL_AUDIO_FRAME_SAMPLES, 0, mute_mask, 1, capture_pts_us);
}
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted) {
return call_audio_feed_impl(call_id, pcm, count, muted, NULL, 0);
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted, int64_t capture_pts_us) {
return call_audio_feed_impl(call_id, pcm, count, muted, NULL, 0, capture_pts_us);
}
/* Незамьюченный захват для платформ без программного mute. */
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count) {
return call_audio_feed_impl(call_id, pcm, count, 0, NULL, 0);
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t capture_pts_us) {
return call_audio_feed_impl(call_id, pcm, count, 0, NULL, 0, capture_pts_us);
}
int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) {
@ -468,6 +492,7 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) {
}
if (call_tones_ended_pending(tones))
n = call_tones_render(tones, out, max_samples);
call_jitter_silence(j, (unsigned)n);
} else {
n = call_jitter_pull(j, out, max_samples);
int previous_gap = g_gap_samples;
@ -477,6 +502,7 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) {
g_gap_samples = 0;
}
int missing = max_samples - n;
call_jitter_silence(j, (unsigned)missing);
/* Подготовка устройств и начальное накопление PCM не являются потерей звука. */
int quiet = g_playback_started ? CALL_STALL_SAMPLES - g_gap_samples : missing;
if (quiet > missing) quiet = missing;
@ -512,6 +538,23 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) {
return n;
}
void call_audio_playback_progress(uint64_t call_id, uint64_t epoch, uint64_t position,
int64_t at_us, int64_t now_us, int measured) {
pthread_mutex_lock(&g_mtx);
if (g_active && call_id == g_call_id && g_vj) call_jitter_progress(g_vj, epoch, position, at_us, now_us, measured);
pthread_mutex_unlock(&g_mtx);
}
int call_audio_get_clock(uint64_t call_id, int64_t now_us, struct call_clock_snapshot* out) {
if (!out) { DEBUG_ERROR(DEBUG_CATEGORY_CALL, "call clock missing output"); return -1; }
memset(out, 0, sizeof(*out)); out->pts_us = -1;
pthread_mutex_lock(&g_mtx);
int present = g_active && call_id == g_call_id && g_vj;
if (present) call_jitter_clock(g_vj, now_us, out);
pthread_mutex_unlock(&g_mtx);
return present ? 0 : -1;
}
/* ── uasync-поток: метрики ── */
int call_audio_get_stats(uint64_t call_id, int* buffer_ms, int* tempo_x100,

15
src/call/call_audio.h

@ -51,11 +51,11 @@ void call_audio_release(uint64_t call_id);
void call_audio_stop(void);
/* аудио-поток: закодировать PCM-кадр (ровно CALL_AUDIO_FRAME_SAMPLES) и отправить пиру. */
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count);
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count, int64_t capture_pts_us);
/* Mute после AEC/AGC: фильтр получает настоящий микрофон, Opus — нулевой PCM. */
/* Подготовленный PCM и маска mute (960 элементов); AEC уже выполнен до этого API. */
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask);
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted);
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask, int64_t capture_pts_us);
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted, int64_t capture_pts_us);
/* аудио-поток: выдать до max_samples финального PCM (jitter + stretch + тоны).
* Возвращает число сэмплов (может быть < max — андерфлоу/конец тона). */
@ -64,7 +64,14 @@ int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples);
/* uasync-поток (из call.c): принятый Opus-кадр — кладём в джиттер-буфер (call_jitter_push,
* декодирование произойдёт на pull в аудио-потоке). */
void call_audio_on_media(struct UTUN_INSTANCE* inst, uint64_t call_id,
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len);
uint16_t seq, uint32_t ts_ms, const uint8_t* opus, int len, const struct call_capture_time* timing);
/* Playback worker: номер PCM-фрейма и время его вывода, epoch берётся из get_clock после start/reset.
* UI/video: get_clock возвращает 0 для существующего звонка; valid внутри снимка может быть 0.
* now_us/at_us — локальные монотонные часы, та же база, что и callback аудиоустройства. */
void call_audio_playback_progress(uint64_t call_id, uint64_t epoch, uint64_t position,
int64_t at_us, int64_t now_us, int measured);
int call_audio_get_clock(uint64_t call_id, int64_t now_us, struct call_clock_snapshot* out);
/* uasync-поток: метрики джиттер-буфера для CALL_STATS (dropped/underruns — необязательные). */
int call_audio_get_stats(uint64_t call_id, int* buffer_ms, int* tempo_x100,

Loading…
Cancel
Save