Compare commits
4 Commits
2d49da5cee
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 497cc3b61c | |||
| 7b3bd7e2e5 | |||
| 3d42a7b61b | |||
| 8efb9c031e |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,2 +1,6 @@
|
|||||||
/target
|
/target
|
||||||
test.iq
|
# test.iq
|
||||||
|
test.py
|
||||||
|
/frames
|
||||||
|
*.png
|
||||||
|
*.mp4
|
||||||
|
|||||||
718
Cargo.lock
generated
718
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -7,5 +7,6 @@ edition = "2024"
|
|||||||
eframe = "0.34.3"
|
eframe = "0.34.3"
|
||||||
egui = "0.34.3"
|
egui = "0.34.3"
|
||||||
egui_plot = "0.35.0"
|
egui_plot = "0.35.0"
|
||||||
|
image = "0.25.10"
|
||||||
indicatif = "0.18.4"
|
indicatif = "0.18.4"
|
||||||
num-complex = "0.4.6"
|
num-complex = "0.4.6"
|
||||||
|
|||||||
44
src/frame.rs
Normal file
44
src/frame.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use image::{GrayImage, Luma};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::line_assembler::VideoFrame;
|
||||||
|
|
||||||
|
// PAL: 1280 samples per line
|
||||||
|
const WIDTH: u32 = 1280;
|
||||||
|
const HEIGHT: u32 = 480; // NTSC (576 for PAL)
|
||||||
|
|
||||||
|
pub fn save_frames(frames: &[VideoFrame], output_dir: &str, sig_min: f32, sig_max: f32) {
|
||||||
|
fs::create_dir_all(output_dir).expect("Could not create output directory");
|
||||||
|
|
||||||
|
let range = (sig_max - sig_min).max(1e-6);
|
||||||
|
let mut saved = 0;
|
||||||
|
|
||||||
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
|
if frame.lines.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut img = GrayImage::new(WIDTH, HEIGHT);
|
||||||
|
|
||||||
|
for y in 0..HEIGHT as usize {
|
||||||
|
let line = frame.lines.get(y);
|
||||||
|
|
||||||
|
for x in 0..WIDTH as usize {
|
||||||
|
let s = line
|
||||||
|
.and_then(|l| l.samples.get(x))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(sig_min);
|
||||||
|
|
||||||
|
let luma = ((s - sig_min) / range * 255.0).clamp(0.0, 255.0) as u8;
|
||||||
|
img.put_pixel(x as u32, y as u32, Luma([luma]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = Path::new(output_dir).join(format!("frame_{:04}.png", i));
|
||||||
|
img.save(&path).expect("Could not save image");
|
||||||
|
saved += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{saved} image(s) saved to '{output_dir}/'");
|
||||||
|
}
|
||||||
95
src/line_assembler.rs
Normal file
95
src/line_assembler.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
use crate::{fm_demod::Sample, sync::SyncEvent};
|
||||||
|
|
||||||
|
pub struct VideoLine {
|
||||||
|
pub samples: Vec<Sample>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VideoFrame {
|
||||||
|
pub lines: Vec<VideoLine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LINES_PER_FRAME: usize = 480; // NTSC (576 for PAL)
|
||||||
|
|
||||||
|
pub struct LineAssembler<I> {
|
||||||
|
pub inner: I,
|
||||||
|
pub current_line: Vec<Sample>,
|
||||||
|
pub current_frame: Vec<VideoLine>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> LineAssembler<I> {
|
||||||
|
pub fn new(inner: I) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
current_line: Vec::new(),
|
||||||
|
current_frame: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, E> Iterator for LineAssembler<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Result<SyncEvent, E>>,
|
||||||
|
{
|
||||||
|
type Item = Result<VideoFrame, E>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
match self.inner.next()? {
|
||||||
|
Ok(SyncEvent::VideoSample(s)) => {
|
||||||
|
self.current_line.push(s);
|
||||||
|
}
|
||||||
|
Ok(SyncEvent::HSync) => {
|
||||||
|
let line = VideoLine {
|
||||||
|
samples: std::mem::take(&mut self.current_line),
|
||||||
|
};
|
||||||
|
self.current_frame.push(line);
|
||||||
|
|
||||||
|
if self.current_frame.len() >= LINES_PER_FRAME {
|
||||||
|
let frame = VideoFrame {
|
||||||
|
lines: std::mem::take(&mut self.current_frame),
|
||||||
|
};
|
||||||
|
return Some(Ok(frame));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(SyncEvent::VSync) => {
|
||||||
|
// test without vsync
|
||||||
|
}
|
||||||
|
Err(e) => return Some(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<I, E> Iterator for LineAssembler<I>
|
||||||
|
// where
|
||||||
|
// I: Iterator<Item = Result<SyncEvent, E>>,
|
||||||
|
// {
|
||||||
|
// type Item = Result<VideoFrame, E>;
|
||||||
|
//
|
||||||
|
// fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
// loop {
|
||||||
|
// match self.inner.next()? {
|
||||||
|
// Ok(SyncEvent::VideoSample(s)) => {
|
||||||
|
// self.current_line.push(s);
|
||||||
|
// }
|
||||||
|
// Ok(SyncEvent::HSync) => {
|
||||||
|
// // Save the finished line
|
||||||
|
// let line = VideoLine {
|
||||||
|
// samples: std::mem::take(&mut self.current_line),
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// self.current_frame.push(line);
|
||||||
|
// }
|
||||||
|
// Ok(SyncEvent::VSync) => {
|
||||||
|
// // Frame finished
|
||||||
|
// let frame = VideoFrame {
|
||||||
|
// lines: std::mem::take(&mut self.current_frame),
|
||||||
|
// };
|
||||||
|
// self.current_line.clear();
|
||||||
|
// return Some(Ok(frame));
|
||||||
|
// }
|
||||||
|
// Err(e) => return Some(Err(e)),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
263
src/main.rs
263
src/main.rs
@ -1,65 +1,244 @@
|
|||||||
|
use crate::fm_demod::Sample;
|
||||||
use crate::iq_reader::FileSource;
|
use crate::iq_reader::FileSource;
|
||||||
use crate::pipeline::{AfterDemodPiplineExt, DspPipelineExt};
|
use crate::pipeline::{AfterDemodPiplineExt, DspPipelineExt, SyncPipelineExt};
|
||||||
use egui_plot::{Line, Plot};
|
use egui_plot::{HLine, Line, Plot, VLine};
|
||||||
use indicatif::ProgressIterator;
|
use indicatif::ProgressIterator;
|
||||||
|
|
||||||
mod agc;
|
mod agc;
|
||||||
mod dc_blocker;
|
mod dc_blocker;
|
||||||
mod fir;
|
mod fir;
|
||||||
mod fm_demod;
|
mod fm_demod;
|
||||||
|
mod frame;
|
||||||
mod iq_reader;
|
mod iq_reader;
|
||||||
|
mod line_assembler;
|
||||||
mod low_pass;
|
mod low_pass;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod sync;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
fn main() -> eframe::Result {
|
const SAMPLE_RATE: f32 = 20_000_000.0;
|
||||||
let source = FileSource::new("test.iq", 32769).unwrap();
|
const SYNC_THRESHOLD: f32 = -0.4;
|
||||||
let taps = [0.5; 64];
|
const LOWPASS_ALPHA: f32 = 0.7;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// fn main() {
|
||||||
|
let taps: [f32; 64] = [
|
||||||
|
-0.00015813,
|
||||||
|
-0.00047836,
|
||||||
|
-0.00080166,
|
||||||
|
-0.00110309,
|
||||||
|
-0.00132172,
|
||||||
|
-0.00136203,
|
||||||
|
-0.00111203,
|
||||||
|
-0.00047680,
|
||||||
|
0.00057973,
|
||||||
|
0.00199526,
|
||||||
|
0.00358456,
|
||||||
|
0.00504093,
|
||||||
|
0.00597041,
|
||||||
|
0.00595859,
|
||||||
|
0.00466189,
|
||||||
|
0.00190795,
|
||||||
|
-0.00221520,
|
||||||
|
-0.00730261,
|
||||||
|
-0.01262639,
|
||||||
|
-0.01719077,
|
||||||
|
-0.01984753,
|
||||||
|
-0.01945971,
|
||||||
|
-0.01509023,
|
||||||
|
-0.00618502,
|
||||||
|
0.00728221,
|
||||||
|
0.02473253,
|
||||||
|
0.04499119,
|
||||||
|
0.06638976,
|
||||||
|
0.08695132,
|
||||||
|
0.10463402,
|
||||||
|
0.11759778,
|
||||||
|
0.12445312,
|
||||||
|
0.12445312,
|
||||||
|
0.11759778,
|
||||||
|
0.10463402,
|
||||||
|
0.08695132,
|
||||||
|
0.06638976,
|
||||||
|
0.04499119,
|
||||||
|
0.02473253,
|
||||||
|
0.00728221,
|
||||||
|
-0.00618502,
|
||||||
|
-0.01509023,
|
||||||
|
-0.01945971,
|
||||||
|
-0.01984753,
|
||||||
|
-0.01719077,
|
||||||
|
-0.01262639,
|
||||||
|
-0.00730261,
|
||||||
|
-0.00221520,
|
||||||
|
0.00190795,
|
||||||
|
0.00466189,
|
||||||
|
0.00595859,
|
||||||
|
0.00597041,
|
||||||
|
0.00504093,
|
||||||
|
0.00358456,
|
||||||
|
0.00199526,
|
||||||
|
0.00057973,
|
||||||
|
-0.00047680,
|
||||||
|
-0.00111203,
|
||||||
|
-0.00136203,
|
||||||
|
-0.00132172,
|
||||||
|
-0.00110309,
|
||||||
|
-0.00080166,
|
||||||
|
-0.00047836,
|
||||||
|
-0.00015813,
|
||||||
|
];
|
||||||
|
|
||||||
|
let source = FileSource::new("test.iq", 32769).unwrap();
|
||||||
let samples: Vec<f32> = source
|
let samples: Vec<f32> = source
|
||||||
|
.skip(10_000)
|
||||||
|
.take(10_000)
|
||||||
|
.agc(SAMPLE_RATE, 1.0, 0.001, 100.0)
|
||||||
|
// .fir(taps, 1)
|
||||||
|
.demodulate()
|
||||||
|
.dc_block(0.997)
|
||||||
|
.lowpass(LOWPASS_ALPHA)
|
||||||
|
.filter_map(|r| r.ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let min = samples.iter().cloned().fold(f32::INFINITY, f32::min);
|
||||||
|
let max = samples.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
let mean: f32 = samples.iter().sum::<f32>() / samples.len() as f32;
|
||||||
|
|
||||||
|
println!("min={:.3} max={:.3} mean={:.3}", min, max, mean);
|
||||||
|
|
||||||
|
let source = FileSource::new("test.iq", 32769).unwrap();
|
||||||
|
|
||||||
|
let frames: Vec<_> = source
|
||||||
|
.skip(10_000)
|
||||||
|
.take(5_000_000)
|
||||||
|
.progress()
|
||||||
|
.agc(SAMPLE_RATE, 1.0, 0.001, 100.0)
|
||||||
|
// .fir(taps, 1)
|
||||||
|
.demodulate()
|
||||||
|
.dc_block(0.997)
|
||||||
|
.lowpass(LOWPASS_ALPHA)
|
||||||
|
.sync_separate(SAMPLE_RATE, SYNC_THRESHOLD)
|
||||||
|
.assemble_frames()
|
||||||
|
.filter_map(|r| r.ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!("{} frames decoded", frames.len());
|
||||||
|
|
||||||
|
frame::save_frames(&frames, "frames", min, max);
|
||||||
|
|
||||||
|
let source = FileSource::new("test.iq", 32769).unwrap();
|
||||||
|
let samples: Vec<Sample> = source
|
||||||
|
.skip(40_000)
|
||||||
|
.take(1_000_000)
|
||||||
.progress()
|
.progress()
|
||||||
.agc(20_000_000.0, 1.0, 0.001, 100.0)
|
.agc(20_000_000.0, 1.0, 0.001, 100.0)
|
||||||
//.fir::<64>(taps, 4)
|
// .fir::<64>(taps, 1)
|
||||||
.demodulate()
|
.demodulate()
|
||||||
.dc_block(0.995)
|
.dc_block(0.997)
|
||||||
.lowpass(0.75)
|
.lowpass(0.75)
|
||||||
.filter_map(|r| r.ok())
|
.filter_map(|r| r.ok())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
eframe::run_ui_native("DSP", Default::default(), move |ui, _frame| {
|
let source = FileSource::new("test.iq", 32769).unwrap();
|
||||||
egui::CentralPanel::default().show(ui, |ui| {
|
let sync_events: Vec<(usize, &str)> = {
|
||||||
Plot::new("plot").show(ui, |plot_ui| {
|
use crate::sync::SyncEvent;
|
||||||
plot_ui.line(Line::new(
|
let mut events = Vec::new();
|
||||||
"Signal",
|
|
||||||
samples
|
let source = FileSource::new("test.iq", 32769).unwrap();
|
||||||
.iter()
|
let mut separator = source
|
||||||
.take(10000)
|
.progress()
|
||||||
.enumerate()
|
.skip(40_000)
|
||||||
.map(|(i, x)| [i as f64, *x as f64])
|
.take(1_000_000)
|
||||||
.collect::<Vec<_>>(),
|
.agc(SAMPLE_RATE, 1.0, 0.001, 100.0)
|
||||||
))
|
.demodulate()
|
||||||
})
|
.dc_block(0.997)
|
||||||
});
|
.lowpass(LOWPASS_ALPHA)
|
||||||
})
|
.sync_separate(SAMPLE_RATE, SYNC_THRESHOLD);
|
||||||
|
|
||||||
|
while let Some(r) = separator.next() {
|
||||||
|
if let Ok(evt) = r {
|
||||||
|
let pos = separator.samples_consumed;
|
||||||
|
match evt {
|
||||||
|
SyncEvent::HSync => events.push((pos, "h")),
|
||||||
|
SyncEvent::VSync => {
|
||||||
|
events.push((pos, "v"));
|
||||||
|
println!("vsync\n")
|
||||||
|
}
|
||||||
|
SyncEvent::VideoSample(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = eframe::run_native(
|
||||||
|
"DSP debug",
|
||||||
|
Default::default(),
|
||||||
|
Box::new(move |_cc| {
|
||||||
|
Ok(Box::new(DebugApp {
|
||||||
|
samples,
|
||||||
|
sync_events,
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// let _ = eframe::run_ui_native("DSP", Default::default(), move |ui, _frame| {
|
||||||
|
// egui::CentralPanel::default().show(ui, |ui| {
|
||||||
|
// Plot::new("plot").show(ui, |plot_ui| {
|
||||||
|
// plot_ui.line(Line::new(
|
||||||
|
// "Signal",
|
||||||
|
// samples
|
||||||
|
// .iter()
|
||||||
|
// .take(10000)
|
||||||
|
// .enumerate()
|
||||||
|
// .map(|(i, x)| [i as f64, *x as f64])
|
||||||
|
// .collect::<Vec<_>>(),
|
||||||
|
// ))
|
||||||
|
// })
|
||||||
|
// });
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn main() -> Result<(), Box<dyn Error>> {
|
struct DebugApp {
|
||||||
// let source = FileSource::new("test.iq", 32769)?;
|
samples: Vec<Sample>,
|
||||||
//
|
sync_events: Vec<(usize, &'static str)>,
|
||||||
// // Fir coefs
|
}
|
||||||
// let taps = [0.5; 64];
|
|
||||||
//
|
impl eframe::App for DebugApp {
|
||||||
// let pipeline = source
|
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||||
// .agc(20_000_000.0, 1.0, 0.001, 100.0)
|
Plot::new("plot").show(ui, |plot_ui| {
|
||||||
// .fir::<64>(taps, 4)
|
plot_ui.line(Line::new(
|
||||||
// .demodulate()
|
"Signal",
|
||||||
// .dc_block(0.995)
|
self.samples
|
||||||
// .lowpass(0.75);
|
.iter()
|
||||||
//
|
.enumerate()
|
||||||
// for s_r in pipeline {
|
.map(|(i, x)| [i as f64, *x as f64])
|
||||||
// let s = s_r?;
|
.collect::<Vec<_>>(),
|
||||||
// println!("phase : {}", s);
|
));
|
||||||
// }
|
|
||||||
//
|
plot_ui.hline(
|
||||||
// Ok(())
|
HLine::new("Threshold", SYNC_THRESHOLD as f64)
|
||||||
// }
|
.color(egui::Color32::YELLOW)
|
||||||
|
.width(4.0),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (pos, kind) in &self.sync_events {
|
||||||
|
let x = *pos as f64;
|
||||||
|
match *kind {
|
||||||
|
"h" => plot_ui.vline(
|
||||||
|
VLine::new("HSync", x)
|
||||||
|
.color(egui::Color32::from_rgb(80, 160, 255))
|
||||||
|
.width(4.0),
|
||||||
|
),
|
||||||
|
"v" => plot_ui.vline(
|
||||||
|
VLine::new("VSync", x)
|
||||||
|
.color(egui::Color32::BROWN)
|
||||||
|
.width(10.0),
|
||||||
|
),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,9 @@ use crate::dc_blocker::DcBlocker;
|
|||||||
use crate::fir::Fir;
|
use crate::fir::Fir;
|
||||||
use crate::fm_demod::{FmDemod, Sample};
|
use crate::fm_demod::{FmDemod, Sample};
|
||||||
use crate::iq_reader::IqSample;
|
use crate::iq_reader::IqSample;
|
||||||
|
use crate::line_assembler::LineAssembler;
|
||||||
use crate::low_pass::LowPass;
|
use crate::low_pass::LowPass;
|
||||||
|
use crate::sync::{SyncEvent, SyncSeparator};
|
||||||
|
|
||||||
pub trait DspPipelineExt<E>: Iterator<Item = Result<IqSample, E>> + Sized {
|
pub trait DspPipelineExt<E>: Iterator<Item = Result<IqSample, E>> + Sized {
|
||||||
fn agc(self, sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Agc<Self> {
|
fn agc(self, sample_rate: f32, target_power: f32, min_gain: f32, max_gain: f32) -> Agc<Self> {
|
||||||
@ -29,6 +31,17 @@ pub trait AfterDemodPiplineExt<E>: Iterator<Item = Result<Sample, E>> + Sized {
|
|||||||
fn lowpass(self, alpha: f32) -> LowPass<Self> {
|
fn lowpass(self, alpha: f32) -> LowPass<Self> {
|
||||||
LowPass::new(self, alpha)
|
LowPass::new(self, alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_separate(self, sample_rate: f32, sync_threshold: f32) -> SyncSeparator<Self> {
|
||||||
|
SyncSeparator::new(self, sample_rate, sync_threshold)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, E> AfterDemodPiplineExt<E> for I where I: Iterator<Item = Result<Sample, E>> {}
|
impl<I, E> AfterDemodPiplineExt<E> for I where I: Iterator<Item = Result<Sample, E>> {}
|
||||||
|
|
||||||
|
pub trait SyncPipelineExt<E>: Iterator<Item = Result<SyncEvent, E>> + Sized {
|
||||||
|
fn assemble_frames(self) -> LineAssembler<Self> {
|
||||||
|
LineAssembler::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I, E> SyncPipelineExt<E> for I where I: Iterator<Item = Result<SyncEvent, E>> {}
|
||||||
|
|||||||
89
src/sync.rs
Normal file
89
src/sync.rs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
use crate::fm_demod::Sample;
|
||||||
|
|
||||||
|
pub enum SyncState {
|
||||||
|
Active,
|
||||||
|
InPulse { count: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SyncEvent {
|
||||||
|
HSync,
|
||||||
|
VSync,
|
||||||
|
VideoSample(Sample),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SyncSeparator<I> {
|
||||||
|
pub inner: I,
|
||||||
|
pub state: SyncState,
|
||||||
|
// Threshold for considering a pulse
|
||||||
|
pub sync_threshold: f32,
|
||||||
|
pub hsync_min: usize,
|
||||||
|
pub hsync_max: usize,
|
||||||
|
pub vsync_min: usize,
|
||||||
|
pub samples_consumed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> SyncSeparator<I> {
|
||||||
|
pub fn new(inner: I, sample_rate: f32, sync_threshold: f32) -> Self {
|
||||||
|
// µs -> samples
|
||||||
|
let us = |micros: f32| (micros * sample_rate / 1_000_000.0) as usize;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
state: SyncState::Active,
|
||||||
|
sync_threshold,
|
||||||
|
hsync_min: us(2.0),
|
||||||
|
hsync_max: us(10.0),
|
||||||
|
vsync_min: us(25.0),
|
||||||
|
samples_consumed: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_sample(&mut self, sample: Sample) -> Option<SyncEvent> {
|
||||||
|
self.samples_consumed += 1;
|
||||||
|
match self.state {
|
||||||
|
SyncState::Active => {
|
||||||
|
if sample < self.sync_threshold {
|
||||||
|
self.state = SyncState::InPulse { count: 1 };
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(SyncEvent::VideoSample(sample))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SyncState::InPulse { ref mut count } => {
|
||||||
|
if sample < self.sync_threshold {
|
||||||
|
*count += 1;
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let pulse_len = *count;
|
||||||
|
self.state = SyncState::Active;
|
||||||
|
if pulse_len >= self.vsync_min {
|
||||||
|
Some(SyncEvent::VSync)
|
||||||
|
} else if pulse_len >= self.hsync_min && pulse_len <= self.hsync_max {
|
||||||
|
Some(SyncEvent::HSync)
|
||||||
|
} else {
|
||||||
|
Some(SyncEvent::VideoSample(sample))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, E> Iterator for SyncSeparator<I>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = Result<Sample, E>>,
|
||||||
|
{
|
||||||
|
type Item = Result<SyncEvent, E>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
match self.inner.next()? {
|
||||||
|
Ok(sample) => {
|
||||||
|
if let Some(event) = self.process_sample(sample) {
|
||||||
|
return Some(Ok(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Some(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user