[fix](trx-rs): improve FT2 decoder acquisition
Use FT2-specific analysis settings and a wider receive span. Switch server-side FT2 decoding to a rolling async window. Widen FT2 candidate timing search in the vendored decoder. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
Vendored
+14
-1
@@ -189,9 +189,22 @@ static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* cand
|
|||||||
|
|
||||||
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score)
|
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score)
|
||||||
{
|
{
|
||||||
|
bool is_ft2 = (wf->protocol == FTX_PROTOCOL_FT2);
|
||||||
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) =
|
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) =
|
||||||
ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score;
|
ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score;
|
||||||
int num_tones = ftx_protocol_uses_ft4_layout(wf->protocol) ? 4 : 8;
|
int num_tones = ftx_protocol_uses_ft4_layout(wf->protocol) ? 4 : 8;
|
||||||
|
int time_offset_min = -10;
|
||||||
|
int time_offset_max = 20;
|
||||||
|
|
||||||
|
if (is_ft2)
|
||||||
|
{
|
||||||
|
time_offset_min = -2;
|
||||||
|
time_offset_max = wf->num_blocks - FT2_NN + 2;
|
||||||
|
if (time_offset_max <= time_offset_min)
|
||||||
|
{
|
||||||
|
time_offset_max = time_offset_min + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int heap_size = 0;
|
int heap_size = 0;
|
||||||
ftx_candidate_t candidate;
|
ftx_candidate_t candidate;
|
||||||
@@ -203,7 +216,7 @@ int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candi
|
|||||||
{
|
{
|
||||||
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub)
|
for (candidate.freq_sub = 0; candidate.freq_sub < wf->freq_osr; ++candidate.freq_sub)
|
||||||
{
|
{
|
||||||
for (candidate.time_offset = -10; candidate.time_offset < 20; ++candidate.time_offset)
|
for (candidate.time_offset = time_offset_min; candidate.time_offset < time_offset_max; ++candidate.time_offset)
|
||||||
{
|
{
|
||||||
for (candidate.freq_offset = 0; (candidate.freq_offset + num_tones - 1) < wf->num_bins; ++candidate.freq_offset)
|
for (candidate.freq_offset = 0; (candidate.freq_offset + num_tones - 1) < wf->num_bins; ++candidate.freq_offset)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -159,6 +159,13 @@ int ft8_decoder_block_size(const ft8_decoder_t* dec)
|
|||||||
return dec ? dec->mon.block_size : 0;
|
return dec ? dec->mon.block_size : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ft8_decoder_window_samples(const ft8_decoder_t* dec)
|
||||||
|
{
|
||||||
|
if (!dec)
|
||||||
|
return 0;
|
||||||
|
return dec->mon.block_size * dec->mon.wf.max_blocks;
|
||||||
|
}
|
||||||
|
|
||||||
void ft8_decoder_reset(ft8_decoder_t* dec)
|
void ft8_decoder_reset(ft8_decoder_t* dec)
|
||||||
{
|
{
|
||||||
if (!dec)
|
if (!dec)
|
||||||
@@ -186,9 +193,10 @@ int ft8_decoder_decode(ft8_decoder_t* dec, ft8_decode_result_t* out, int max_res
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
const ftx_waterfall_t* wf = &dec->mon.wf;
|
const ftx_waterfall_t* wf = &dec->mon.wf;
|
||||||
const int kMaxCandidates = 200;
|
const bool is_ft2 = (dec->cfg.protocol == FTX_PROTOCOL_FT2);
|
||||||
const int kMinScore = 10;
|
const int kMaxCandidates = is_ft2 ? 400 : 200;
|
||||||
const int kLdpcIters = 30;
|
const int kMinScore = is_ft2 ? 4 : 10;
|
||||||
|
const int kLdpcIters = is_ft2 ? 50 : 30;
|
||||||
|
|
||||||
ftx_candidate_t candidate_list[kMaxCandidates];
|
ftx_candidate_t candidate_list[kMaxCandidates];
|
||||||
int num_candidates = ftx_find_candidates(wf, kMaxCandidates, candidate_list, kMinScore);
|
int num_candidates = ftx_find_candidates(wf, kMaxCandidates, candidate_list, kMinScore);
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ use libc::{c_float, c_int, c_void};
|
|||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
const F_MIN_HZ: f32 = 200.0;
|
const DEFAULT_F_MIN_HZ: f32 = 200.0;
|
||||||
const F_MAX_HZ: f32 = 3000.0;
|
const DEFAULT_F_MAX_HZ: f32 = 3000.0;
|
||||||
const TIME_OSR: i32 = 2;
|
const DEFAULT_TIME_OSR: i32 = 2;
|
||||||
const FREQ_OSR: i32 = 2;
|
const DEFAULT_FREQ_OSR: i32 = 2;
|
||||||
|
const FT2_F_MIN_HZ: f32 = 200.0;
|
||||||
|
const FT2_F_MAX_HZ: f32 = 5000.0;
|
||||||
|
const FT2_TIME_OSR: i32 = 8;
|
||||||
|
const FT2_FREQ_OSR: i32 = 4;
|
||||||
|
|
||||||
const FTX_MAX_MESSAGE_LENGTH: usize = 35;
|
const FTX_MAX_MESSAGE_LENGTH: usize = 35;
|
||||||
const PROTOCOL_FT4: c_int = 0;
|
const PROTOCOL_FT4: c_int = 0;
|
||||||
@@ -44,6 +48,7 @@ extern "C" {
|
|||||||
) -> *mut c_void;
|
) -> *mut c_void;
|
||||||
fn ft8_decoder_free(dec: *mut c_void);
|
fn ft8_decoder_free(dec: *mut c_void);
|
||||||
fn ft8_decoder_block_size(dec: *const c_void) -> c_int;
|
fn ft8_decoder_block_size(dec: *const c_void) -> c_int;
|
||||||
|
fn ft8_decoder_window_samples(dec: *const c_void) -> c_int;
|
||||||
fn ft8_decoder_reset(dec: *mut c_void);
|
fn ft8_decoder_reset(dec: *mut c_void);
|
||||||
fn ft8_decoder_process(dec: *mut c_void, frame: *const c_float);
|
fn ft8_decoder_process(dec: *mut c_void, frame: *const c_float);
|
||||||
fn ft8_decoder_is_ready(dec: *const c_void) -> c_int;
|
fn ft8_decoder_is_ready(dec: *const c_void) -> c_int;
|
||||||
@@ -57,6 +62,7 @@ extern "C" {
|
|||||||
pub struct Ft8Decoder {
|
pub struct Ft8Decoder {
|
||||||
inner: NonNull<c_void>,
|
inner: NonNull<c_void>,
|
||||||
block_size: usize,
|
block_size: usize,
|
||||||
|
window_samples: usize,
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,24 +84,39 @@ impl Ft8Decoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn new_with_protocol(sample_rate: u32, protocol: c_int, label: &str) -> Result<Self, String> {
|
fn new_with_protocol(sample_rate: u32, protocol: c_int, label: &str) -> Result<Self, String> {
|
||||||
|
let (f_min, f_max, time_osr, freq_osr) = match protocol {
|
||||||
|
PROTOCOL_FT2 => (FT2_F_MIN_HZ, FT2_F_MAX_HZ, FT2_TIME_OSR, FT2_FREQ_OSR),
|
||||||
|
_ => (
|
||||||
|
DEFAULT_F_MIN_HZ,
|
||||||
|
DEFAULT_F_MAX_HZ,
|
||||||
|
DEFAULT_TIME_OSR,
|
||||||
|
DEFAULT_FREQ_OSR,
|
||||||
|
),
|
||||||
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ft8_decoder_create(
|
let ptr = ft8_decoder_create(
|
||||||
sample_rate as c_int,
|
sample_rate as c_int,
|
||||||
F_MIN_HZ,
|
f_min,
|
||||||
F_MAX_HZ,
|
f_max,
|
||||||
TIME_OSR as c_int,
|
time_osr as c_int,
|
||||||
FREQ_OSR as c_int,
|
freq_osr as c_int,
|
||||||
protocol,
|
protocol,
|
||||||
);
|
);
|
||||||
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
|
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
|
||||||
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
|
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
|
||||||
|
let window_samples = ft8_decoder_window_samples(inner.as_ptr()) as usize;
|
||||||
if block_size == 0 {
|
if block_size == 0 {
|
||||||
ft8_decoder_free(inner.as_ptr());
|
ft8_decoder_free(inner.as_ptr());
|
||||||
return Err(format!("invalid {label} block size"));
|
return Err(format!("invalid {label} block size"));
|
||||||
}
|
}
|
||||||
|
if window_samples == 0 {
|
||||||
|
ft8_decoder_free(inner.as_ptr());
|
||||||
|
return Err(format!("invalid {label} analysis window"));
|
||||||
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner,
|
inner,
|
||||||
block_size,
|
block_size,
|
||||||
|
window_samples,
|
||||||
sample_rate,
|
sample_rate,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -109,6 +130,10 @@ impl Ft8Decoder {
|
|||||||
self.sample_rate
|
self.sample_rate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn window_samples(&self) -> usize {
|
||||||
|
self.window_samples
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
ft8_decoder_reset(self.inner.as_ptr());
|
ft8_decoder_reset(self.inner.as_ptr());
|
||||||
@@ -178,5 +203,6 @@ mod tests {
|
|||||||
assert!(ft2.block_size() < ft4.block_size());
|
assert!(ft2.block_size() < ft4.block_size());
|
||||||
assert_eq!(ft4.block_size(), 576);
|
assert_eq!(ft4.block_size(), 576);
|
||||||
assert_eq!(ft2.block_size(), 288);
|
assert_eq!(ft2.block_size(), 288);
|
||||||
|
assert_eq!(ft2.window_samples(), 44_928);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-36
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
//! Audio capture, playback, and TCP streaming for trx-server.
|
//! Audio capture, playback, and TCP streaming for trx-server.
|
||||||
|
|
||||||
use std::collections::{HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -54,6 +54,9 @@ const CW_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
|
|||||||
const FT8_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
|
const FT8_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
|
||||||
const WSPR_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
|
const WSPR_HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);
|
||||||
const FT8_SAMPLE_RATE: u32 = 12_000;
|
const FT8_SAMPLE_RATE: u32 = 12_000;
|
||||||
|
const FT2_ASYNC_BUFFER_SAMPLES: usize = 45_000;
|
||||||
|
const FT2_ASYNC_TRIGGER_SAMPLES: usize = 9_000;
|
||||||
|
const FT2_DEDUPE_RETENTION: Duration = Duration::from_secs(8);
|
||||||
const DECODE_AUDIO_GATE_RMS: f32 = 2.5e-4;
|
const DECODE_AUDIO_GATE_RMS: f32 = 2.5e-4;
|
||||||
const AUDIO_STREAM_ERROR_LOG_INTERVAL: Duration = Duration::from_secs(60);
|
const AUDIO_STREAM_ERROR_LOG_INTERVAL: Duration = Duration::from_secs(60);
|
||||||
const AUDIO_STREAM_RECOVERY_DELAY: Duration = Duration::from_secs(1);
|
const AUDIO_STREAM_RECOVERY_DELAY: Duration = Duration::from_secs(1);
|
||||||
@@ -65,6 +68,48 @@ fn current_timestamp_ms() -> i64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn retain_ft2_window(buf: &mut Vec<f32>) {
|
||||||
|
if buf.len() > FT2_ASYNC_BUFFER_SAMPLES {
|
||||||
|
let excess = buf.len() - FT2_ASYNC_BUFFER_SAMPLES;
|
||||||
|
buf.drain(..excess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prune_recent_ft2_decodes(recent: &mut HashMap<String, Instant>, now: Instant) {
|
||||||
|
recent.retain(|_, seen_at| now.duration_since(*seen_at) <= FT2_DEDUPE_RETENTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_emit_ft2_decode(recent: &mut HashMap<String, Instant>, text: &str, freq_hz: f32) -> bool {
|
||||||
|
let now = Instant::now();
|
||||||
|
prune_recent_ft2_decodes(recent, now);
|
||||||
|
let key = format!("{}|{}", text, freq_hz.round() as i32);
|
||||||
|
if recent.contains_key(&key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
recent.insert(key, now);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_ft2_window(
|
||||||
|
decoder: &mut Ft8Decoder,
|
||||||
|
samples: &[f32],
|
||||||
|
max_results: usize,
|
||||||
|
) -> Vec<trx_ft8::Ft8DecodeResult> {
|
||||||
|
let window_samples = decoder.window_samples();
|
||||||
|
if samples.len() < window_samples {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.reset();
|
||||||
|
let block_size = decoder.block_size();
|
||||||
|
let start = samples.len() - window_samples;
|
||||||
|
let window = &samples[start..];
|
||||||
|
for block in window.chunks_exact(block_size) {
|
||||||
|
decoder.process_block(block);
|
||||||
|
}
|
||||||
|
decoder.decode_if_ready(max_results)
|
||||||
|
}
|
||||||
|
|
||||||
struct StreamErrorLogger {
|
struct StreamErrorLogger {
|
||||||
label: &'static str,
|
label: &'static str,
|
||||||
state: Mutex<StreamErrorState>,
|
state: Mutex<StreamErrorState>,
|
||||||
@@ -1874,7 +1919,8 @@ pub async fn run_ft2_decoder(
|
|||||||
let mut active = state_rx.borrow().ft2_decode_enabled
|
let mut active = state_rx.borrow().ft2_decode_enabled
|
||||||
&& matches!(state_rx.borrow().status.mode, RigMode::DIG | RigMode::USB);
|
&& matches!(state_rx.borrow().status.mode, RigMode::DIG | RigMode::USB);
|
||||||
let mut ft2_buf: Vec<f32> = Vec::new();
|
let mut ft2_buf: Vec<f32> = Vec::new();
|
||||||
let mut last_slot: i64 = -1;
|
let mut pending_decode_samples: usize = 0;
|
||||||
|
let mut recent_decodes: HashMap<String, Instant> = HashMap::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !active {
|
if !active {
|
||||||
@@ -1890,8 +1936,9 @@ pub async fn run_ft2_decoder(
|
|||||||
last_reset_seq = state.ft2_decode_reset_seq;
|
last_reset_seq = state.ft2_decode_reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft2_buf.clear();
|
ft2_buf.clear();
|
||||||
|
pending_decode_samples = 0;
|
||||||
|
recent_decodes.clear();
|
||||||
}
|
}
|
||||||
last_slot = -1;
|
|
||||||
}
|
}
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
@@ -1902,17 +1949,6 @@ pub async fn run_ft2_decoder(
|
|||||||
recv = pcm_rx.recv() => {
|
recv = pcm_rx.recv() => {
|
||||||
match recv {
|
match recv {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
|
||||||
Ok(dur) => dur.as_millis() as i64,
|
|
||||||
Err(_) => 0,
|
|
||||||
};
|
|
||||||
let slot = now_ms / 3_750;
|
|
||||||
if slot != last_slot {
|
|
||||||
last_slot = slot;
|
|
||||||
decoder.reset();
|
|
||||||
ft2_buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let reset_seq = {
|
let reset_seq = {
|
||||||
let state = state_rx.borrow();
|
let state = state_rx.borrow();
|
||||||
state.ft2_decode_reset_seq
|
state.ft2_decode_reset_seq
|
||||||
@@ -1921,6 +1957,8 @@ pub async fn run_ft2_decoder(
|
|||||||
last_reset_seq = reset_seq;
|
last_reset_seq = reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft2_buf.clear();
|
ft2_buf.clear();
|
||||||
|
pending_decode_samples = 0;
|
||||||
|
recent_decodes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
@@ -1930,12 +1968,15 @@ pub async fn run_ft2_decoder(
|
|||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
ft2_buf.extend_from_slice(&resampled);
|
ft2_buf.extend_from_slice(&resampled);
|
||||||
|
pending_decode_samples += resampled.len();
|
||||||
|
retain_ft2_window(&mut ft2_buf);
|
||||||
|
|
||||||
while ft2_buf.len() >= decoder.block_size() {
|
while pending_decode_samples >= FT2_ASYNC_TRIGGER_SAMPLES
|
||||||
let block: Vec<f32> = ft2_buf.drain(..decoder.block_size()).collect();
|
&& ft2_buf.len() >= FT2_ASYNC_BUFFER_SAMPLES
|
||||||
|
{
|
||||||
|
pending_decode_samples -= FT2_ASYNC_TRIGGER_SAMPLES;
|
||||||
let results = tokio::task::block_in_place(|| {
|
let results = tokio::task::block_in_place(|| {
|
||||||
decoder.process_block(&block);
|
decode_ft2_window(&mut decoder, &ft2_buf, 100)
|
||||||
decoder.decode_if_ready(100)
|
|
||||||
});
|
});
|
||||||
if !results.is_empty() {
|
if !results.is_empty() {
|
||||||
for res in results {
|
for res in results {
|
||||||
@@ -1956,6 +1997,13 @@ pub async fn run_ft2_decoder(
|
|||||||
},
|
},
|
||||||
message: res.text,
|
message: res.text,
|
||||||
};
|
};
|
||||||
|
if !should_emit_ft2_decode(
|
||||||
|
&mut recent_decodes,
|
||||||
|
&msg.message,
|
||||||
|
msg.freq_hz,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
histories.record_ft2_message(msg.clone());
|
histories.record_ft2_message(msg.clone());
|
||||||
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
||||||
}
|
}
|
||||||
@@ -1978,11 +2026,14 @@ pub async fn run_ft2_decoder(
|
|||||||
last_reset_seq = state.ft2_decode_reset_seq;
|
last_reset_seq = state.ft2_decode_reset_seq;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft2_buf.clear();
|
ft2_buf.clear();
|
||||||
|
pending_decode_samples = 0;
|
||||||
|
recent_decodes.clear();
|
||||||
}
|
}
|
||||||
if !active {
|
if !active {
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
ft2_buf.clear();
|
ft2_buf.clear();
|
||||||
last_slot = -1;
|
pending_decode_samples = 0;
|
||||||
|
recent_decodes.clear();
|
||||||
} else {
|
} else {
|
||||||
pcm_rx = pcm_rx.resubscribe();
|
pcm_rx = pcm_rx.resubscribe();
|
||||||
}
|
}
|
||||||
@@ -2443,23 +2494,12 @@ async fn run_background_ft2_decoder(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut ft2_buf: Vec<f32> = Vec::new();
|
let mut ft2_buf: Vec<f32> = Vec::new();
|
||||||
let mut last_slot: i64 = -1;
|
let mut pending_decode_samples: usize = 0;
|
||||||
|
let mut recent_decodes: HashMap<String, Instant> = HashMap::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match pcm_rx.recv().await {
|
match pcm_rx.recv().await {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
|
||||||
{
|
|
||||||
Ok(dur) => dur.as_millis() as i64,
|
|
||||||
Err(_) => 0,
|
|
||||||
};
|
|
||||||
let slot = now_ms / 3_750;
|
|
||||||
if slot != last_slot {
|
|
||||||
last_slot = slot;
|
|
||||||
decoder.reset();
|
|
||||||
ft2_buf.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mono = downmix_mono(frame, channels);
|
let mut mono = downmix_mono(frame, channels);
|
||||||
apply_decode_audio_gate(&mut mono);
|
apply_decode_audio_gate(&mut mono);
|
||||||
let Some(resampled) = resample_to_12k(&mono, sample_rate) else {
|
let Some(resampled) = resample_to_12k(&mono, sample_rate) else {
|
||||||
@@ -2470,12 +2510,15 @@ async fn run_background_ft2_decoder(
|
|||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
ft2_buf.extend_from_slice(&resampled);
|
ft2_buf.extend_from_slice(&resampled);
|
||||||
|
pending_decode_samples += resampled.len();
|
||||||
|
retain_ft2_window(&mut ft2_buf);
|
||||||
|
|
||||||
while ft2_buf.len() >= decoder.block_size() {
|
while pending_decode_samples >= FT2_ASYNC_TRIGGER_SAMPLES
|
||||||
let block: Vec<f32> = ft2_buf.drain(..decoder.block_size()).collect();
|
&& ft2_buf.len() >= FT2_ASYNC_BUFFER_SAMPLES
|
||||||
|
{
|
||||||
|
pending_decode_samples -= FT2_ASYNC_TRIGGER_SAMPLES;
|
||||||
let results = tokio::task::block_in_place(|| {
|
let results = tokio::task::block_in_place(|| {
|
||||||
decoder.process_block(&block);
|
decode_ft2_window(&mut decoder, &ft2_buf, 100)
|
||||||
decoder.decode_if_ready(100)
|
|
||||||
});
|
});
|
||||||
for res in results {
|
for res in results {
|
||||||
let abs_freq_hz = base_freq_hz as f64 + res.freq_hz as f64;
|
let abs_freq_hz = base_freq_hz as f64 + res.freq_hz as f64;
|
||||||
@@ -2490,6 +2533,13 @@ async fn run_background_ft2_decoder(
|
|||||||
},
|
},
|
||||||
message: res.text,
|
message: res.text,
|
||||||
};
|
};
|
||||||
|
if !should_emit_ft2_decode(
|
||||||
|
&mut recent_decodes,
|
||||||
|
&msg.message,
|
||||||
|
msg.freq_hz,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
let _ = decode_tx.send(DecodedMessage::Ft2(msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user