|
|
|
|
@ -48,7 +48,7 @@
|
|
|
|
|
|
|
|
|
|
/* ── VAD авто-PTT (отдельный режим, ручной PTT не меняется) ── */ |
|
|
|
|
#define RADIO_VAD_CONFIRM_WINDOWS 2 /* окон (32мс) выше порога для старта (~64мс) */ |
|
|
|
|
#define RADIO_VAD_BUSY_TB 2000 /* 200мс: канал занят после последнего RX-кадра */ |
|
|
|
|
#define RADIO_VAD_BUSY_TB 2000 /* 200мс после завершения чужой речи и её вывода */ |
|
|
|
|
#define RADIO_PREFETCH_MS 600 /* PCM только до начала передачи */ |
|
|
|
|
#define RADIO_PREFETCH_FRAMES (RADIO_AUDIO_SAMPLE_RATE * RADIO_PREFETCH_MS / 1000) |
|
|
|
|
#define RADIO_PREFETCH_PACKETS (RADIO_PREFETCH_FRAMES / RADIO_AUDIO_FRAME_SAMPLES) |
|
|
|
|
@ -108,6 +108,9 @@ static int g_vad_win_len = 0;
|
|
|
|
|
static struct radio_vad_fsm g_vad_fsm; |
|
|
|
|
static uint64_t g_last_rx_tb = 0; /* последний принятый аудио-кадр (busy) */ |
|
|
|
|
static uint64_t g_last_rx_node = 0; /* источник блокировки авто-PTT */ |
|
|
|
|
static uint64_t g_rx_guard_until = 0; |
|
|
|
|
static uint64_t g_output_position = 0, g_output_voice_end = 0, g_output_played = 0; |
|
|
|
|
static int g_output_tracked = 0, g_vad_blocked = 0; |
|
|
|
|
|
|
|
|
|
/* TX-статистика (кадрирование/кодирование), выводится раз в секунду пока идёт PTT. */ |
|
|
|
|
static uint64_t g_tx_stats_tb = 0; |
|
|
|
|
@ -344,39 +347,52 @@ static void radio_src_free(struct radio_source* s) {
|
|
|
|
|
radio_src_reorder_reset(s); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Вызывается под g_mtx; пауза начинается после последнего из RX, jitter и аппаратного вывода. */ |
|
|
|
|
static void radio_audio_rx_guard(uint64_t until) { |
|
|
|
|
if (until > g_rx_guard_until) g_rx_guard_until = until; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int radio_audio_rx_busy(uint64_t now) { |
|
|
|
|
if (now < g_rx_guard_until || (g_output_tracked && g_output_played < g_output_voice_end)) return 1; |
|
|
|
|
for (int i = 0; i < RADIO_AUDIO_MAX_SOURCES; ++i) if (g_sources[i].used) return 1; |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Получить/создать инстанс источника. Один пир — один инстанс: если stream_id
|
|
|
|
|
* сменился (новая передача PTT того же пира) — сбрасываем jitter, не дублируем. */ |
|
|
|
|
static struct radio_source* radio_src_acquire(uint64_t src, uint16_t stream) { |
|
|
|
|
static struct radio_source* radio_src_acquire(uint64_t src, uint16_t stream, int muted) { |
|
|
|
|
struct radio_source* s = radio_src_find(src); |
|
|
|
|
if (s) { |
|
|
|
|
if (s->stream_id != stream) { |
|
|
|
|
s->stream_id = stream; |
|
|
|
|
s->ending = 0; |
|
|
|
|
radio_src_reorder_reset(s); |
|
|
|
|
rj_destroy(s->rj); |
|
|
|
|
s->rj = radio_rj_create(s->dec); |
|
|
|
|
if (!s->rj) { radio_src_free(s); return NULL; } |
|
|
|
|
if (s->rj) { rj_destroy(s->rj); s->rj = NULL; } |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: source reset src=%016llx stream=%u", RADIO_AUDIO_ID, (unsigned long long)src, stream); |
|
|
|
|
} |
|
|
|
|
return s; |
|
|
|
|
} |
|
|
|
|
for (int i = 0; i < RADIO_AUDIO_MAX_SOURCES; i++) { |
|
|
|
|
} else for (int i = 0; i < RADIO_AUDIO_MAX_SOURCES; i++) { |
|
|
|
|
if (g_sources[i].used) continue; |
|
|
|
|
s = &g_sources[i]; |
|
|
|
|
s->dec = opus_codec_decoder_create(RADIO_AUDIO_SAMPLE_RATE, RADIO_AUDIO_MIX_CHANNELS); |
|
|
|
|
if (!s->dec) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "%s: decoder create failed", RADIO_AUDIO_ID); return NULL; } |
|
|
|
|
s->rj = radio_rj_create(s->dec); |
|
|
|
|
if (!s->rj) { opus_codec_decoder_destroy(s->dec); s->dec = NULL; return NULL; } |
|
|
|
|
s->src_node_id = src; |
|
|
|
|
s->stream_id = stream; |
|
|
|
|
s->used = 1; s->ending = 0; s->last_frame_tb = 0; |
|
|
|
|
radio_src_reorder_reset(s); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: new source src=%016llx stream=%u", RADIO_AUDIO_ID, (unsigned long long)src, stream); |
|
|
|
|
return s; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: too many sources (>=%d), drop src=%016llx", |
|
|
|
|
RADIO_AUDIO_ID, RADIO_AUDIO_MAX_SOURCES, (unsigned long long)src); |
|
|
|
|
return NULL; |
|
|
|
|
if (!s) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: too many sources (>=%d), drop src=%016llx", |
|
|
|
|
RADIO_AUDIO_ID, RADIO_AUDIO_MAX_SOURCES, (unsigned long long)src); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
/* Заглушённый пир занимает эфир, но не требует декодера или jitter. */ |
|
|
|
|
if (!muted && !s->rj) { |
|
|
|
|
if (!s->dec) s->dec = opus_codec_decoder_create(RADIO_AUDIO_SAMPLE_RATE, RADIO_AUDIO_MIX_CHANNELS); |
|
|
|
|
if (s->dec) s->rj = radio_rj_create(s->dec); |
|
|
|
|
if (!s->rj) DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "%s: source audio unavailable src=%016llx", RADIO_AUDIO_ID, |
|
|
|
|
(unsigned long long)src); |
|
|
|
|
} |
|
|
|
|
return s; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static int radio_audio_post_talk(struct UASYNC* ua, uint64_t group_id, int begin); |
|
|
|
|
@ -392,7 +408,9 @@ static void radio_audio_member_mute(struct UTUN_INSTANCE* inst, uint64_t group_i
|
|
|
|
|
if (s) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: clear muted source grp=%016llx src=%016llx stream=%u", |
|
|
|
|
RADIO_AUDIO_ID, (unsigned long long)group_id, (unsigned long long)node_id, s->stream_id); |
|
|
|
|
radio_src_free(s); |
|
|
|
|
if (s->rj) { rj_destroy(s->rj); s->rj = NULL; } |
|
|
|
|
if (s->dec) { opus_codec_decoder_destroy(s->dec); s->dec = NULL; } |
|
|
|
|
radio_src_reorder_reset(s); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
@ -434,6 +452,8 @@ int radio_audio_start(uint64_t group_id, int capture_channels) {
|
|
|
|
|
++g_listen_generation; |
|
|
|
|
g_active = 1; |
|
|
|
|
g_last_rx_tb = g_last_rx_node = 0; |
|
|
|
|
g_rx_guard_until = g_output_position = g_output_voice_end = g_output_played = 0; |
|
|
|
|
g_output_tracked = g_vad_blocked = 0; |
|
|
|
|
memset(g_sources, 0, sizeof(g_sources)); |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: listening grp=%016llx", RADIO_AUDIO_ID, (unsigned long long)group_id); |
|
|
|
|
@ -527,6 +547,7 @@ int radio_audio_capture_start(uint64_t group_id, int capture_channels, int vad_e
|
|
|
|
|
g_vad_diag.last_busy = -1; |
|
|
|
|
} |
|
|
|
|
g_vad_manual_ptt = g_transmitting = g_tx_pcm_frames = g_vad_decimation = 0; |
|
|
|
|
g_vad_blocked = 0; |
|
|
|
|
g_vad_sum = 0; g_tx_burst = 0; |
|
|
|
|
g_prefetch_write = g_prefetch_frames = g_tx_warned = 0; |
|
|
|
|
|
|
|
|
|
@ -600,11 +621,34 @@ void radio_audio_reset_playback(uint64_t group_id) {
|
|
|
|
|
pthread_mutex_lock(&g_mtx); |
|
|
|
|
if (g_active && g_group_id == group_id) { |
|
|
|
|
for (int i = 0; i < RADIO_AUDIO_MAX_SOURCES; ++i) radio_src_free(&g_sources[i]); |
|
|
|
|
g_output_position = g_output_voice_end = g_output_played = 0; |
|
|
|
|
radio_audio_rx_guard(get_time_tb() + RADIO_VAD_BUSY_TB); |
|
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "%s: playback reset group=%016llx", RADIO_AUDIO_ID, (unsigned long long)group_id); |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void radio_audio_playback_progress(uint64_t group_id, uint64_t position, uint64_t delay_tb) { |
|
|
|
|
pthread_mutex_lock(&g_mtx); |
|
|
|
|
if (g_active && g_group_id == group_id) { |
|
|
|
|
if (position < g_output_played || position > g_output_position) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: invalid playback position=%llu played=%llu pulled=%llu", RADIO_AUDIO_ID, |
|
|
|
|
(unsigned long long)position, (unsigned long long)g_output_played, (unsigned long long)g_output_position); |
|
|
|
|
} else { |
|
|
|
|
if (!g_output_tracked) DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: playback tracking group=%016llx", RADIO_AUDIO_ID, |
|
|
|
|
(unsigned long long)group_id); |
|
|
|
|
g_output_tracked = 1; |
|
|
|
|
if (g_output_voice_end > g_output_played && position >= g_output_voice_end) { |
|
|
|
|
radio_audio_rx_guard(get_time_tb() + delay_tb + RADIO_VAD_BUSY_TB); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "VAD output drained group=%016llx position=%llu delay=%.1fms guard=200ms", |
|
|
|
|
(unsigned long long)group_id, (unsigned long long)position, delay_tb / 10.0); |
|
|
|
|
} |
|
|
|
|
g_output_played = position; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int radio_audio_active(void) { |
|
|
|
|
int a = 0; |
|
|
|
|
pthread_mutex_lock(&g_mtx); |
|
|
|
|
@ -789,7 +833,12 @@ static void radio_audio_send_prefetch(struct radio_audio_tx* task) {
|
|
|
|
|
|
|
|
|
|
/* Ручной PTT имеет приоритет; смена manual/VAD при открытой серии не создаёт второй BEGIN. */ |
|
|
|
|
static void radio_audio_update_transmission(void) { |
|
|
|
|
int desired = g_vad_manual_ptt || (g_vad_mode && g_vad_fsm.talking); |
|
|
|
|
int busy = g_vad_mode && radio_audio_rx_busy(get_time_tb()); |
|
|
|
|
if (busy) { |
|
|
|
|
radio_vad_fsm_reset(&g_vad_fsm); |
|
|
|
|
g_prefetch_write = g_prefetch_frames = 0; |
|
|
|
|
} |
|
|
|
|
int desired = g_vad_manual_ptt || (g_vad_mode && g_vad_fsm.talking && !busy); |
|
|
|
|
if (desired == g_transmitting) return; |
|
|
|
|
struct radio_audio_tx* prefetch = NULL; |
|
|
|
|
if (!desired) radio_audio_flush_tx(); |
|
|
|
|
@ -809,6 +858,29 @@ static void radio_audio_update_transmission(void) {
|
|
|
|
|
if (desired && g_vad_mode) radio_audio_send_prefetch(prefetch); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Только TX-writer: RX лишь публикует занятость, не трогая Silero во время inference без g_mtx. */ |
|
|
|
|
static int radio_audio_vad_block(uint64_t now) { |
|
|
|
|
int busy = radio_audio_rx_busy(now); |
|
|
|
|
if (busy != g_vad_blocked) { |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_VAD, "VAD gate group=%016llx reason=%s rx_node=%016llx guard_left=%.1fms " |
|
|
|
|
"output_pending=%llu manual=%d transmitting=%d prefetch=%d", |
|
|
|
|
(unsigned long long)g_group_id, busy ? "rx_busy" : "rx_released", (unsigned long long)g_last_rx_node, |
|
|
|
|
g_rx_guard_until > now ? (g_rx_guard_until - now) / 10.0 : 0.0, |
|
|
|
|
(unsigned long long)(g_output_voice_end > g_output_played ? g_output_voice_end - g_output_played : 0), |
|
|
|
|
g_vad_manual_ptt, g_transmitting, g_prefetch_frames); |
|
|
|
|
g_vad_blocked = busy; |
|
|
|
|
g_vad_win_len = g_vad_decimation = 0; g_vad_sum = 0; |
|
|
|
|
g_vad_pcm_frames = 0; g_vad_input_sum_sq = 0; |
|
|
|
|
g_vad_input_rms = g_vad_output_rms = 0; |
|
|
|
|
if (g_vad_compressor) audio_compressor_reset(g_vad_compressor); |
|
|
|
|
if (g_vad) silero_vad_reset(g_vad); |
|
|
|
|
radio_vad_fsm_reset(&g_vad_fsm); |
|
|
|
|
g_prefetch_write = g_prefetch_frames = 0; |
|
|
|
|
radio_audio_update_transmission(); |
|
|
|
|
} |
|
|
|
|
return busy; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void radio_audio_capture_discontinuity(uint64_t group_id) { |
|
|
|
|
radio_tx_lock(); |
|
|
|
|
if (g_active && g_group_id == group_id && g_encoder) { |
|
|
|
|
@ -852,6 +924,7 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
|
|
|
|
pthread_mutex_lock(&g_mtx); |
|
|
|
|
g_vad_win_len = 0; |
|
|
|
|
uint64_t now = get_time_tb(); |
|
|
|
|
if (radio_audio_vad_block(now)) return; /* RX мог начаться, пока Silero работал без общего mutex. */ |
|
|
|
|
uint64_t inference_tb = now - started; |
|
|
|
|
uint64_t gap_tb = g_vad_diag.last_window_tb ? now - g_vad_diag.last_window_tb : 0; |
|
|
|
|
g_vad_diag.last_window_tb = now; |
|
|
|
|
@ -865,7 +938,7 @@ static void radio_audio_vad_sample(const int16_t* frame) {
|
|
|
|
|
(unsigned long long)g_group_id, (unsigned long long)g_vad_diag.total_windows, result, inference_tb / 10.0); |
|
|
|
|
} |
|
|
|
|
struct radio_vad_fsm before = g_vad_fsm; |
|
|
|
|
int busy = g_last_rx_tb && now - g_last_rx_tb < RADIO_VAD_BUSY_TB; |
|
|
|
|
int busy = 0; |
|
|
|
|
int64_t rx_age_ms = g_last_rx_tb ? (int64_t)((now - g_last_rx_tb) / 10) : -1; |
|
|
|
|
int transition = radio_vad_fsm_update(&g_vad_fsm, probability, g_vad_threshold, RADIO_VAD_CONFIRM_WINDOWS, |
|
|
|
|
(uint64_t)g_vad_hangover_ms * 10, busy, now); |
|
|
|
|
@ -946,7 +1019,7 @@ static void radio_audio_vad_feed(const int16_t* frame) {
|
|
|
|
|
sum_sq += sample * sample; |
|
|
|
|
} |
|
|
|
|
g_vad_output_rms = sqrtf((float)(sum_sq / count)); |
|
|
|
|
for (int i = 0; i < count; i += g_capture_channels) radio_audio_vad_sample(g_vad_pcm + i); |
|
|
|
|
for (int i = 0; i < count && !g_vad_blocked; i += g_capture_channels) radio_audio_vad_sample(g_vad_pcm + i); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Очищенные interleaved отсчёты произвольной длины; границы PTT ставит единственный TX-writer. */ |
|
|
|
|
@ -957,11 +1030,12 @@ int radio_audio_feed_pcm(uint64_t group_id, const int16_t* pcm, int count) {
|
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: invalid capture frame count=%d", RADIO_AUDIO_ID, count); |
|
|
|
|
return -1; |
|
|
|
|
} |
|
|
|
|
if (g_vad_mode) radio_audio_vad_block(get_time_tb()); |
|
|
|
|
radio_audio_update_transmission(); // Повторить BEGIN после освобождения bounded control-очереди.
|
|
|
|
|
for (int offset = 0; offset < count; offset += g_capture_channels) { |
|
|
|
|
if (g_vad_mode) radio_audio_vad_feed(pcm + offset); |
|
|
|
|
if (g_vad_mode && !g_vad_blocked) radio_audio_vad_feed(pcm + offset); |
|
|
|
|
if (!g_transmitting) { |
|
|
|
|
if (g_vad_mode) { |
|
|
|
|
if (g_vad_mode && !g_vad_blocked) { |
|
|
|
|
memcpy(g_prefetch_pcm + g_prefetch_write * g_capture_channels, pcm + offset, g_capture_channels * sizeof(int16_t)); |
|
|
|
|
g_prefetch_write = (g_prefetch_write + 1) % RADIO_PREFETCH_FRAMES; |
|
|
|
|
if (g_prefetch_frames < RADIO_PREFETCH_FRAMES) ++g_prefetch_frames; |
|
|
|
|
@ -988,20 +1062,20 @@ void radio_audio_on_frame(struct UTUN_INSTANCE* inst, uint64_t group_id,
|
|
|
|
|
pthread_mutex_lock(&g_mtx); |
|
|
|
|
if (g_active && group_id == g_group_id) { |
|
|
|
|
if (len > 0) { g_last_rx_tb = now; g_last_rx_node = src_node_id; } /* busy-сигнал: чужой аудио-кадр */ |
|
|
|
|
if (muted) { |
|
|
|
|
if (seq == 0) DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: muted burst grp=%016llx src=%016llx stream=%u; RX busy preserved", |
|
|
|
|
RADIO_AUDIO_ID, (unsigned long long)group_id, (unsigned long long)src_node_id, stream_id); |
|
|
|
|
pthread_mutex_unlock(&g_mtx); return; |
|
|
|
|
} |
|
|
|
|
struct radio_source* s = radio_src_acquire(src_node_id, stream_id); |
|
|
|
|
struct radio_source* s = radio_src_acquire(src_node_id, stream_id, muted); |
|
|
|
|
if (s) { |
|
|
|
|
if (len > 0) radio_src_push_frame(s, seq, opus, len, now); |
|
|
|
|
if (len > 0 && !muted && s->rj) radio_src_push_frame(s, seq, opus, len, now); |
|
|
|
|
s->last_frame_tb = now; |
|
|
|
|
if (fin) { |
|
|
|
|
/* FIN: выдать накопленное pending (не ждём закрытия gap) и дренируем. */ |
|
|
|
|
if (s->reorder_count > 0) radio_src_flush_pending(s); |
|
|
|
|
s->ending = 1; |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: RX FIN src=%016llx stream=%u muted=%d; draining playback", |
|
|
|
|
RADIO_AUDIO_ID, (unsigned long long)src_node_id, stream_id, muted); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
/* Переполнение таблицы не должно разрешать VAD поверх отброшенной передачи. */ |
|
|
|
|
radio_audio_rx_guard(now + RADIO_AUDIO_SOURCE_TIMEOUT_TB + RADIO_VAD_BUSY_TB); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
@ -1034,8 +1108,8 @@ int radio_audio_pull_pcm(uint64_t group_id, int16_t* out, int max_samples) {
|
|
|
|
|
* только для НЕ-ending: ending-источник дренируется до пустого буфера. */ |
|
|
|
|
if (!s->ending && s->last_frame_tb && (now - s->last_frame_tb > RADIO_AUDIO_SOURCE_TIMEOUT_TB)) { |
|
|
|
|
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "%s: source timeout src=%016llx", RADIO_AUDIO_ID, (unsigned long long)s->src_node_id); |
|
|
|
|
radio_src_free(s); |
|
|
|
|
continue; |
|
|
|
|
if (s->reorder_count > 0) radio_src_flush_pending(s); |
|
|
|
|
s->ending = 1; |
|
|
|
|
} |
|
|
|
|
/* reorder: gap висит дольше таймаута — force-flush, чтобы не задерживать звук. */ |
|
|
|
|
if (s->reorder_count > 0 && s->reorder_age_tb && (now - s->reorder_age_tb > RADIO_AUDIO_REORDER_TO_TB)) { |
|
|
|
|
@ -1043,6 +1117,10 @@ int radio_audio_pull_pcm(uint64_t group_id, int16_t* out, int max_samples) {
|
|
|
|
|
RADIO_AUDIO_ID, (unsigned long long)s->src_node_id, s->reorder_count); |
|
|
|
|
radio_src_flush_pending(s); |
|
|
|
|
} |
|
|
|
|
if (!s->rj) { |
|
|
|
|
if (s->ending) { radio_src_free(s); radio_audio_rx_guard(now + RADIO_VAD_BUSY_TB); } |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
/* ending: снять pre-roll (если ещё не снят) и дренировать накопленный остаток. */ |
|
|
|
|
if (s->ending) rj_end(s->rj); |
|
|
|
|
if (do_stats && nst < RADIO_AUDIO_MAX_SOURCES) { |
|
|
|
|
@ -1055,14 +1133,21 @@ int radio_audio_pull_pcm(uint64_t group_id, int16_t* out, int max_samples) {
|
|
|
|
|
int16_t tmp[RADIO_AUDIO_MIX_SAMPLES]; |
|
|
|
|
int n = rj_pull(s->rj, tmp, max_samples); |
|
|
|
|
for (int k = 0; k < n; k++) g_mix[k] += tmp[k]; |
|
|
|
|
if (n > 0) { |
|
|
|
|
uint64_t end = g_output_position + (uint64_t)(n + 1) / RADIO_AUDIO_MIX_CHANNELS; |
|
|
|
|
if (end > g_output_voice_end) g_output_voice_end = end; |
|
|
|
|
if (!g_output_tracked) radio_audio_rx_guard(now + (uint64_t)n * 10000 / (48000 * 2) + RADIO_VAD_BUSY_TB); |
|
|
|
|
} |
|
|
|
|
if (s->ending && n == 0) { |
|
|
|
|
uint32_t dropped = 0, underruns = 0; |
|
|
|
|
rj_get_stats(s->rj, NULL, NULL, &dropped, &underruns); |
|
|
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "%s: source ended src=%016llx dropped=%u underruns=%u", |
|
|
|
|
RADIO_AUDIO_ID, (unsigned long long)s->src_node_id, dropped, underruns); |
|
|
|
|
radio_src_free(s); |
|
|
|
|
radio_audio_rx_guard(now + RADIO_VAD_BUSY_TB); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
g_output_position += (uint64_t)max_samples / RADIO_AUDIO_MIX_CHANNELS; |
|
|
|
|
pthread_mutex_unlock(&g_mtx); |
|
|
|
|
|
|
|
|
|
if (do_stats) { |
|
|
|
|
|