Skip to content

Commit b7d98d5

Browse files
committed
save-png
1 parent c5624bc commit b7d98d5

File tree

3 files changed

+122
-3
lines changed

3 files changed

+122
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ bitflags = "1.2.1"
1515
[dev-dependencies]
1616
env_logger = { version = "0.10.0", default-features = false }
1717
anyhow = "1.0.68"
18+
png = "0.17.13"

examples/save-jpeg.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ fn main() -> anyhow::Result<()> {
2424

2525
let device = args
2626
.next()
27-
.ok_or_else(|| anyhow!("usage: save-stream <device> <file> [<count>]"))?;
27+
.ok_or_else(|| anyhow!("usage: save-jpeg <device> <file> [<count>]"))?;
2828

2929
let file_path = args
3030
.next()
31-
.ok_or_else(|| anyhow!("usage: save-stream <device> <file> [<count>]"))?;
31+
.ok_or_else(|| anyhow!("usage: save-jpeg <device> <file> [<count>]"))?;
3232

3333
let count: u32 = args.next().map_or(1, |osstr| {
3434
osstr
3535
.to_str()
3636
.expect("invalid UTF-8")
3737
.parse()
38-
.expect("invalid valud for <count>")
38+
.expect("invalid value for <count>")
3939
});
4040

4141
let device = Device::open(Path::new(&device))?;

examples/save-png.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! Captures BGRA video frames and encodes them as a PNG file (animated if more than one frame is
2+
//! captured).
3+
//!
4+
//! Uses the [`linuxvideo::stream::ReadStream`] returned by [`linuxvideo::VideoCaptureDevice::into_stream`]
5+
//! to read image data.
6+
7+
use std::{
8+
env,
9+
fs::File,
10+
io::{self, stdout, BufWriter, Write},
11+
path::Path,
12+
time::Instant,
13+
};
14+
15+
use anyhow::{anyhow, bail};
16+
use linuxvideo::{
17+
format::{PixFormat, PixelFormat},
18+
BufType, Device,
19+
};
20+
21+
fn main() -> anyhow::Result<()> {
22+
env_logger::init();
23+
24+
let mut args = env::args_os().skip(1);
25+
26+
let device = args
27+
.next()
28+
.ok_or_else(|| anyhow!("usage: save-png <device> <file> [<count>]"))?;
29+
30+
let file_path = args
31+
.next()
32+
.ok_or_else(|| anyhow!("usage: save-png <device> <file> [<count>]"))?;
33+
34+
let count: u32 = args.next().map_or(1, |osstr| {
35+
osstr
36+
.to_str()
37+
.expect("invalid UTF-8")
38+
.parse()
39+
.expect("invalid value for <count>")
40+
});
41+
if count == 0 {
42+
bail!("'capture zero frames', statements dreamed up by the utterly deranged");
43+
}
44+
45+
let device = Device::open(Path::new(&device))?;
46+
println!(
47+
"capabilities: {:?}",
48+
device.capabilities()?.device_capabilities()
49+
);
50+
51+
let formats = device
52+
.formats(BufType::VIDEO_CAPTURE)
53+
.map(|res| res.map(|f| f.pixel_format()))
54+
.collect::<io::Result<Vec<_>>>()?;
55+
let format = if formats.contains(&PixelFormat::ABGR32) {
56+
PixelFormat::ABGR32
57+
} else {
58+
bail!(
59+
"save-png does not support any of the device's formats (device supports {:?})",
60+
formats
61+
);
62+
};
63+
64+
let capture = device.video_capture(PixFormat::new(u32::MAX, u32::MAX, format))?;
65+
println!("negotiated format: {:?}", capture.format());
66+
67+
let width = capture.format().width();
68+
let height = capture.format().height();
69+
let mut stream = capture.into_stream()?;
70+
println!("stream started, waiting for data");
71+
72+
let file = File::create(&file_path)?;
73+
let mut enc = png::Encoder::new(BufWriter::new(file), width, height);
74+
enc.set_animated(count, 0)?;
75+
enc.set_color(png::ColorType::Rgba);
76+
enc.validate_sequence(true);
77+
// We're not using the stream writer since the basic `Writer` is already streaming on a per-frame
78+
// basis, which is enough for this.
79+
let mut writer = enc.write_header()?;
80+
let mut cur_frame = vec![0; (width * height * 4) as usize];
81+
let mut prev_frame: Option<(Vec<u8>, _)> = None;
82+
for _ in 0..count + 1 {
83+
let arrival = stream.dequeue(|buf| {
84+
let arrival = Instant::now(); // FIXME: use buffer timecode instead
85+
if buf.is_error() {
86+
eprintln!("WARNING: error flag is set on buffer");
87+
}
88+
89+
match format {
90+
PixelFormat::ABGR32 => {
91+
// Source order: B G R A
92+
assert_eq!(cur_frame.len(), buf.len());
93+
for (dest, src) in cur_frame.chunks_exact_mut(4).zip(buf.chunks_exact(4)) {
94+
assert_eq!(dest.len(), 4);
95+
let &[b, g, r, a] = src else { unreachable!() };
96+
dest.copy_from_slice(&[r, g, b, a]);
97+
}
98+
}
99+
_ => unreachable!(),
100+
}
101+
102+
Ok(arrival)
103+
})?;
104+
if let Some((frame, prev_arrival)) = &prev_frame {
105+
let millis = arrival.saturating_duration_since(*prev_arrival).as_millis();
106+
writer.set_frame_delay(millis as u16, 1000)?;
107+
writer.write_image_data(frame)?;
108+
}
109+
prev_frame = Some((cur_frame.clone(), arrival));
110+
print!(".");
111+
stdout().flush()?;
112+
}
113+
println!();
114+
115+
writer.finish()?;
116+
117+
Ok(())
118+
}

0 commit comments

Comments
 (0)