perf: eliminate mock capture allocation churn, add idle bypass, and deduplicate filenames
This commit is contained in:
parent
b7a0257b03
commit
22e3dbda4c
4 changed files with 94 additions and 16 deletions
17
install.sh
17
install.sh
|
|
@ -25,9 +25,22 @@ ln -sf "$BIN_DIR/scythe-daemon" "$BIN_DIR/vrec-daemon"
|
|||
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec-ui"
|
||||
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec"
|
||||
|
||||
if pgrep -x scythe-daemon > /dev/null; then
|
||||
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
|
||||
if [ -f "$SYSTEMD_USER_DIR/vrec-daemon.service" ] || systemctl --user is-active --quiet vrec-daemon.service 2>/dev/null; then
|
||||
echo "Migrating legacy vrec-daemon systemd service to scythe-daemon.service..."
|
||||
systemctl --user stop vrec-daemon.service 2>/dev/null || true
|
||||
systemctl --user disable vrec-daemon.service 2>/dev/null || true
|
||||
rm -f "$SYSTEMD_USER_DIR/vrec-daemon.service"
|
||||
rm -f "$SYSTEMD_USER_DIR/default.target.wants/vrec-daemon.service"
|
||||
fi
|
||||
|
||||
if systemctl --user is-active --quiet scythe-daemon.service 2>/dev/null; then
|
||||
echo "Restarting active scythe-daemon systemd service..."
|
||||
systemctl --user restart scythe-daemon.service
|
||||
elif pgrep -x scythe-daemon > /dev/null || pgrep -x vrec-daemon > /dev/null; then
|
||||
echo "Restarting active scythe-daemon engine..."
|
||||
pkill -x scythe-daemon || true
|
||||
pkill -x scythe-daemon 2>/dev/null || true
|
||||
pkill -x vrec-daemon 2>/dev/null || true
|
||||
sleep 0.5
|
||||
nohup "$BIN_DIR/scythe-daemon" > /dev/null 2>&1 &
|
||||
disown
|
||||
|
|
|
|||
|
|
@ -488,6 +488,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let mut ticker = crossbeam_channel::tick(std::time::Duration::from_nanos(1_000_000_000 / config.fps.max(1) as u64));
|
||||
let mut latest_frame: Option<Frame> = None;
|
||||
let mut has_new_frame = false;
|
||||
let mut last_encoded_raw_ptr: usize = 0;
|
||||
|
||||
loop {
|
||||
crossbeam_channel::select! {
|
||||
|
|
@ -671,24 +672,44 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
recv(ticker) -> _ => {
|
||||
while ticker.try_recv().is_ok() {}
|
||||
|
||||
if !normal_recording && !config.replay_enabled {
|
||||
has_new_frame = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
let now = std::time::Instant::now();
|
||||
let elapsed = now.duration_since(stream_start);
|
||||
let raw_pts = (elapsed.as_secs_f64() * config.fps as f64).round() as i64;
|
||||
let pts = if raw_pts > last_video_pts { raw_pts } else { last_video_pts + 1 };
|
||||
last_video_pts = pts;
|
||||
|
||||
let packets_res = if has_new_frame {
|
||||
let is_same_raw_frame = match &latest_frame {
|
||||
Some(Frame::Raw { data, .. }) => {
|
||||
let ptr = Arc::as_ptr(data) as usize;
|
||||
ptr != 0 && ptr == last_encoded_raw_ptr
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let packets_res = if has_new_frame && !is_same_raw_frame {
|
||||
has_new_frame = false;
|
||||
if let Some(ref f) = latest_frame {
|
||||
if let Frame::Raw { data, .. } = f {
|
||||
last_encoded_raw_ptr = Arc::as_ptr(data) as usize;
|
||||
}
|
||||
encoder.encode_frame(f, pts)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
} else {
|
||||
has_new_frame = false;
|
||||
match encoder.encode_cached_frame(pts) {
|
||||
Ok(pkts) => Ok(pkts),
|
||||
Err(_) => {
|
||||
if let Some(ref f) = latest_frame {
|
||||
if let Frame::Raw { data, .. } = f {
|
||||
last_encoded_raw_ptr = Arc::as_ptr(data) as usize;
|
||||
}
|
||||
encoder.encode_frame(f, pts)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
|
|
|
|||
|
|
@ -1,15 +1,32 @@
|
|||
use super::{Frame, FrameSource};
|
||||
use std::time::Duration;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct MockCapture {
|
||||
width: u32,
|
||||
height: u32,
|
||||
frame_count: u64,
|
||||
static_frame: Arc<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl MockCapture {
|
||||
pub fn new() -> Self {
|
||||
Self { width: 1920, height: 1080, frame_count: 0 }
|
||||
let width = 1920;
|
||||
let height = 1080;
|
||||
let mut data = vec![0u8; (width * height * 4) as usize];
|
||||
for chunk in data.chunks_exact_mut(4) {
|
||||
chunk[0] = 64; // B
|
||||
chunk[1] = 64; // G
|
||||
chunk[2] = 64; // R
|
||||
chunk[3] = 255; // A
|
||||
}
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
frame_count: 0,
|
||||
static_frame: Arc::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -22,24 +39,13 @@ impl Default for MockCapture {
|
|||
impl FrameSource for MockCapture {
|
||||
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>> {
|
||||
std::thread::sleep(Duration::from_millis(16)); // ~60fps
|
||||
let mut data = vec![0u8; (self.width * self.height * 4) as usize];
|
||||
|
||||
// Fill with a changing color
|
||||
let color = (self.frame_count % 255) as u8;
|
||||
for chunk in data.chunks_exact_mut(4) {
|
||||
chunk[0] = color; // B
|
||||
chunk[1] = color; // G
|
||||
chunk[2] = 255; // R
|
||||
chunk[3] = 255; // A
|
||||
}
|
||||
|
||||
self.frame_count += 1;
|
||||
|
||||
Ok(Frame::Raw {
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
stride: self.width * 4,
|
||||
data: std::sync::Arc::new(data),
|
||||
data: Arc::clone(&self.static_frame),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,22 @@ impl ScytheConfig {
|
|||
let mut p = Self::expand_tilde(&self.output_directory);
|
||||
let _ = fs::create_dir_all(&p);
|
||||
p.push(filename);
|
||||
|
||||
if !p.exists() {
|
||||
return p.to_string_lossy().to_string();
|
||||
}
|
||||
|
||||
let stem = p.file_stem().and_then(|s| s.to_str()).unwrap_or("recording");
|
||||
let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("mp4");
|
||||
let parent = p.parent().unwrap_or_else(|| std::path::Path::new("."));
|
||||
|
||||
for counter in 1..10000 {
|
||||
let candidate = parent.join(format!("{}_{}.{}", stem, counter, ext));
|
||||
if !candidate.exists() {
|
||||
return candidate.to_string_lossy().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
p.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
|
|
@ -361,4 +377,26 @@ mod tests {
|
|||
assert!(part.chars().all(|c| c.is_ascii_digit()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_save_path_collision() {
|
||||
let temp_dir = std::env::temp_dir().join(format!("scythe_test_collision_{}", std::process::id()));
|
||||
let cfg = ScytheConfig {
|
||||
output_directory: temp_dir.to_string_lossy().to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let path1 = cfg.resolve_save_path("test_clip.mp4");
|
||||
assert!(path1.ends_with("test_clip.mp4"));
|
||||
fs::write(&path1, b"dummy").unwrap();
|
||||
|
||||
let path2 = cfg.resolve_save_path("test_clip.mp4");
|
||||
assert!(path2.ends_with("test_clip_1.mp4"), "Expected collision to append _1: {}", path2);
|
||||
fs::write(&path2, b"dummy2").unwrap();
|
||||
|
||||
let path3 = cfg.resolve_save_path("test_clip.mp4");
|
||||
assert!(path3.ends_with("test_clip_2.mp4"), "Expected collision to append _2: {}", path3);
|
||||
|
||||
let _ = fs::remove_dir_all(temp_dir);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue