Skip to content

Commit e8d06a1

Browse files
committed
Bump version to 0.9.1 and fix video desync/stalling on scrub
1 parent adfe75d commit e8d06a1

2 files changed

Lines changed: 59 additions & 32 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rusttracker"
3-
version = "0.9.0"
3+
version = "0.9.1"
44
edition = "2024"
55
default-run = "rusttracker"
66
description = "High-Performance Vulkan Tracker Module Visualizer"

src/audio.rs

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -155,17 +155,20 @@ pub fn spawn_dsp_thread(
155155
// avoiding the visual "freeze" caused by searching the entire history buffer.
156156
let search_window = (sample_rate as f32 / 30.0) as usize;
157157
let search_start = msg.audio_data.len().saturating_sub(actual_window_samples + search_window);
158-
let search_limit = msg.audio_data.len().saturating_sub(actual_window_samples);
158+
// Ensure i+1 stays in bounds during zero-crossing search
159+
let search_limit = msg.audio_data.len().saturating_sub(actual_window_samples).min(msg.audio_data.len().saturating_sub(1));
159160

160161
let mut start_idx = search_start;
161162
let mut best_slope = 0.0;
162163

163-
for i in search_start..search_limit {
164-
if msg.audio_data[i] <= 0.0 && msg.audio_data[i + 1] > 0.0 {
165-
let slope = msg.audio_data[i + 1] - msg.audio_data[i];
166-
if slope > best_slope {
167-
best_slope = slope;
168-
start_idx = i;
164+
if msg.audio_data.len() >= 2 {
165+
for i in search_start..search_limit {
166+
if msg.audio_data[i] <= 0.0 && msg.audio_data[i + 1] > 0.0 {
167+
let slope = msg.audio_data[i + 1] - msg.audio_data[i];
168+
if slope > best_slope {
169+
best_slope = slope;
170+
start_idx = i;
171+
}
169172
}
170173
}
171174
}
@@ -174,10 +177,12 @@ pub fn spawn_dsp_thread(
174177
state.raw_waveform.resize(visual_width, 0.0);
175178
}
176179

180+
if !msg.audio_data.is_empty() {
177181
for i in 0..visual_width {
178182
let sample_idx = start_idx + (i as f32 * stride) as usize;
179183
state.raw_waveform[i] = msg.audio_data[sample_idx.min(msg.audio_data.len() - 1)];
180184
}
185+
}
181186

182187
let now = Instant::now();
183188
if now.duration_since(last_waveform_push) >= waveform_push_interval {
@@ -239,7 +244,7 @@ pub trait AudioSource: Send {
239244
fn pre_format_tracker_data(&mut self) -> Vec<Vec<String>> { Vec::new() }
240245
fn get_current_row_string(&mut self) -> String { String::new() }
241246
fn get_video_info(&mut self) -> Option<String> { None }
242-
fn attach_video_queue(&mut self, _tx: crossbeam_channel::Sender<ffmpeg_next::Packet>) {}
247+
fn attach_video_queue(&mut self, _tx: crossbeam_channel::Sender<(u64, ffmpeg_next::Packet)>) {}
243248
fn take_video_parameters(&mut self) -> Option<(ffmpeg_next::codec::Parameters, ffmpeg_next::Rational)> { None }
244249
}
245250

@@ -810,7 +815,8 @@ struct FfmpegSource {
810815
video_info: Option<String>,
811816
channel_vus: Vec<f32>,
812817
video_stream_index: Option<usize>,
813-
video_tx: Option<crossbeam_channel::Sender<ffmpeg_next::Packet>>,
818+
video_tx: Option<crossbeam_channel::Sender<(u64, ffmpeg_next::Packet)>>,
819+
video_epoch: u64,
814820
video_params: Option<ffmpeg_next::codec::Parameters>,
815821
video_time_base: Option<ffmpeg_next::Rational>,
816822
}
@@ -853,7 +859,7 @@ impl FfmpegSource {
853859
for (stream, packet) in self.ictx.packets() {
854860
if Some(stream.index()) == self.video_stream_index {
855861
if let Some(tx) = &self.video_tx {
856-
let _ = tx.try_send(packet.clone());
862+
let _ = tx.try_send((self.video_epoch, packet.clone()));
857863
}
858864
} else if stream.index() == self.stream_index {
859865
// Send the packet to the decoder
@@ -988,6 +994,7 @@ impl AudioSource for FfmpegSource {
988994
self.decoder.flush();
989995
self.buf_pos = self.sample_buf.len();
990996
self.current_time = pos;
997+
self.video_epoch += 1;
991998
}
992999

9931000
fn get_num_channels(&mut self) -> i32 { self.channels as i32 }
@@ -1005,7 +1012,7 @@ impl AudioSource for FfmpegSource {
10051012

10061013
fn get_video_info(&mut self) -> Option<String> { self.video_info.clone() }
10071014

1008-
fn attach_video_queue(&mut self, tx: crossbeam_channel::Sender<ffmpeg_next::Packet>) {
1015+
fn attach_video_queue(&mut self, tx: crossbeam_channel::Sender<(u64, ffmpeg_next::Packet)>) {
10091016
self.video_tx = Some(tx);
10101017
}
10111018

@@ -1024,7 +1031,8 @@ impl AudioSource for FfmpegSource {
10241031
struct VideoOnlySource {
10251032
ictx: ffmpeg_next::format::context::Input,
10261033
video_stream_index: usize,
1027-
video_tx: Option<crossbeam_channel::Sender<ffmpeg_next::Packet>>,
1034+
video_tx: Option<crossbeam_channel::Sender<(u64, ffmpeg_next::Packet)>>,
1035+
video_epoch: u64,
10281036
video_params: Option<ffmpeg_next::codec::Parameters>,
10291037
video_time_base: Option<ffmpeg_next::Rational>,
10301038
current_time: f64,
@@ -1045,7 +1053,7 @@ impl AudioSource for VideoOnlySource {
10451053
if let Some((stream, packet)) = self.ictx.packets().next() {
10461054
if stream.index() == self.video_stream_index {
10471055
if let Some(tx) = &self.video_tx {
1048-
let _ = tx.try_send(packet.clone());
1056+
let _ = tx.try_send((self.video_epoch, packet.clone()));
10491057
}
10501058
}
10511059
packets_read += 1;
@@ -1075,6 +1083,7 @@ impl AudioSource for VideoOnlySource {
10751083
}
10761084
}
10771085
self.current_time = pos;
1086+
self.video_epoch += 1;
10781087
}
10791088

10801089
fn get_num_channels(&mut self) -> i32 { 2 }
@@ -1091,7 +1100,7 @@ impl AudioSource for VideoOnlySource {
10911100
fn get_current_row(&mut self) -> i32 { 0 }
10921101
fn get_video_info(&mut self) -> Option<String> { self.video_info.clone() }
10931102

1094-
fn attach_video_queue(&mut self, tx: crossbeam_channel::Sender<ffmpeg_next::Packet>) {
1103+
fn attach_video_queue(&mut self, tx: crossbeam_channel::Sender<(u64, ffmpeg_next::Packet)>) {
10951104
self.video_tx = Some(tx);
10961105
}
10971106

@@ -1145,6 +1154,7 @@ fn try_ffmpeg(file_path: &str) -> Result<Box<dyn AudioSource>> {
11451154
ictx,
11461155
video_stream_index: v_idx,
11471156
video_tx: None,
1157+
video_epoch: 0,
11481158
video_params,
11491159
video_time_base: video_tb,
11501160
current_time: 0.0,
@@ -1195,6 +1205,7 @@ fn try_ffmpeg(file_path: &str) -> Result<Box<dyn AudioSource>> {
11951205
channel_vus: vec![0.0; channels as usize],
11961206
video_stream_index,
11971207
video_tx: None,
1208+
video_epoch: 0,
11981209
video_params,
11991210
video_time_base: video_tb,
12001211
}))
@@ -1495,9 +1506,6 @@ pub fn start_audio_thread(file_path: &str, mic: bool, shared_state: Arc<Mutex<Ap
14951506
state.num_instruments = audio_source.get_num_instruments();
14961507
state.num_patterns = audio_source.get_num_patterns();
14971508

1498-
let _intrinsic = audio_source.get_num_channels();
1499-
let _intrinsic = audio_source.get_num_channels();
1500-
15011509
if !mic {
15021510
state.tracker_channels = tracker_channels;
15031511
state.tracker_patterns_by_order = audio_source.pre_format_tracker_data();
@@ -1561,7 +1569,7 @@ where
15611569
let (ready_tx, ready_rx) = crossbeam_channel::bounded::<AudioChunk>(pool_size);
15621570
let (free_tx, free_rx) = crossbeam_channel::bounded::<AudioChunk>(pool_size);
15631571

1564-
let (video_packet_tx, video_packet_rx) = crossbeam_channel::bounded::<ffmpeg_next::Packet>(4096);
1572+
let (video_packet_tx, video_packet_rx) = crossbeam_channel::bounded::<(u64, ffmpeg_next::Packet)>(4096);
15651573
audio_source.attach_video_queue(video_packet_tx);
15661574

15671575
if let Some((params, time_base)) = audio_source.take_video_parameters() {
@@ -1582,6 +1590,7 @@ where
15821590
bit_depth: 8,
15831591
color_space: 0,
15841592
color_range: 0,
1593+
color_trc: 0,
15851594
});
15861595
}
15871596

@@ -1599,14 +1608,18 @@ where
15991608
let mut local_epoch = 0;
16001609
let mut fallback_pts_seconds = 0.0;
16011610

1602-
while let Ok(packet) = video_packet_rx_for_video.recv() {
1603-
let mut track_ended = false;
1604-
if let Ok(state) = state_for_video.try_lock() {
1605-
track_ended = state.track_ended;
1611+
while let Ok((packet_epoch, packet)) = video_packet_rx_for_video.recv() {
1612+
let mut track_ended = {
1613+
let state = state_for_video.lock().unwrap();
16061614
if state.seek_epoch > local_epoch {
16071615
decoder.flush();
16081616
local_epoch = state.seek_epoch;
16091617
}
1618+
state.track_ended
1619+
};
1620+
1621+
if packet_epoch < local_epoch {
1622+
continue;
16101623
}
16111624

16121625
if track_ended { return; }
@@ -1625,17 +1638,21 @@ where
16251638
fallback_pts_seconds = pts + (1.0 / 30.0);
16261639
}
16271640

1641+
let mut skip_push = false;
16281642
loop {
16291643
let (cached_seconds, current_epoch) = {
1630-
if let Ok(state) = state_for_video.try_lock() {
1631-
track_ended = state.track_ended;
1632-
(state.current_seconds, state.seek_epoch)
1633-
} else {
1634-
continue;
1635-
}
1644+
let state = state_for_video.lock().unwrap();
1645+
track_ended = state.track_ended;
1646+
(state.current_seconds, state.seek_epoch)
16361647
};
16371648

16381649
if track_ended || current_epoch > local_epoch {
1650+
skip_push = true;
1651+
break;
1652+
}
1653+
1654+
if pts < cached_seconds - 0.05 {
1655+
skip_push = true;
16391656
break;
16401657
}
16411658

@@ -1645,6 +1662,10 @@ where
16451662
std::thread::sleep(std::time::Duration::from_millis(2));
16461663
}
16471664

1665+
if skip_push {
1666+
continue;
1667+
}
1668+
16481669
if let Ok(mut frame) = free_video_frame_rx.recv() {
16491670
frame.pts = pts;
16501671
frame.width = decoded.width();
@@ -1654,6 +1675,7 @@ where
16541675
frame.bit_depth = if format_name.contains("10LE") { 10 } else if format_name.contains("12LE") { 12 } else { 8 };
16551676
frame.color_space = decoded.color_space() as u32;
16561677
frame.color_range = decoded.color_range() as u32;
1678+
frame.color_trc = decoded.color_transfer_characteristic() as u32;
16571679

16581680
frame.y_stride = decoded.stride(0);
16591681
frame.u_stride = decoded.stride(1);
@@ -1723,6 +1745,7 @@ where
17231745
if let Ok(mut state) = state_for_decoder.try_lock() {
17241746
if let Some(pos) = state.seek_request.take() {
17251747
audio_source.set_position_seconds(pos);
1748+
state.current_seconds = pos;
17261749
state.seek_epoch += 1;
17271750
while let Ok(chunk) = ready_rx_for_decoder.try_recv() {
17281751
let _ = free_tx_for_decoder.try_send(chunk);
@@ -1789,7 +1812,7 @@ where
17891812
state.stats.decode_us = state.stats.decode_us * 0.9 + decode_elapsed * 0.1;
17901813
let fill_pct = (ready_rx_for_decoder.len() as f32 / pool_size as f32) * 100.0;
17911814
state.stats.audio_buffer_fill_pct = fill_pct;
1792-
let video_pct = (video_rx_for_decoder.len() as f32 / 256.0) * 100.0;
1815+
let video_pct = (video_rx_for_decoder.len() as f32 / 4096.0) * 100.0;
17931816
state.stats.video_buffer_fill_pct = video_pct;
17941817
state.stats.clipping_events += clips;
17951818
}
@@ -1963,6 +1986,8 @@ where
19631986
let mut fft_buffer: Vec<f32> = vec![0.0; window_size];
19641987
let mut channel_fft_buffers: Vec<Vec<f32>> = vec![vec![0.0; window_size]; channels];
19651988
let mut windowed_buffer: Vec<f32> = vec![0.0; window_size];
1989+
let mut windowed_channels: Vec<Vec<f32>> = vec![vec![0.0; window_size]; channels];
1990+
let mut spare_channels: Vec<Vec<f32>> = vec![vec![0.0; window_size]; channels];
19661991
let mut fft_index = 0;
19671992

19681993
let mut was_paused = false;
@@ -2000,7 +2025,6 @@ where
20002025
fft_index = (fft_index + 1) % window_size;
20012026
}
20022027

2003-
let mut windowed_channels = vec![vec![0.0; window_size]; channels];
20042028
for i in 0..window_size {
20052029
let idx = (fft_index + i) % window_size;
20062030
windowed_buffer[i] = fft_buffer[idx];
@@ -2009,6 +2033,9 @@ where
20092033
}
20102034
}
20112035

2036+
// Swap filled buffers with spare set — zero allocations in the hot path
2037+
std::mem::swap(&mut windowed_channels, &mut spare_channels);
2038+
20122039
let mut channel_vus = Vec::new();
20132040
if channels >= 1 { channel_vus.push(left_peak); }
20142041
if channels >= 2 { channel_vus.push(right_peak); }
@@ -2022,7 +2049,7 @@ where
20222049
speed: 0,
20232050
current_seconds: 0.0,
20242051
current_row_string: String::new(),
2025-
channel_audio_data: windowed_channels,
2052+
channel_audio_data: std::mem::replace(&mut spare_channels, vec![vec![0.0; window_size]; channels]),
20262053
};
20272054

20282055
let _ = tx.try_send(msg);

0 commit comments

Comments
 (0)