⭐ capturing MJPG
parent
28daa28d90
commit
3da0836c8a
|
@ -1 +1,2 @@
|
||||||
|
/deps
|
||||||
/target
|
/target
|
||||||
|
|
|
@ -8,6 +8,12 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -44,7 +50,7 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"memoffset",
|
"memoffset 0.9.0",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -67,6 +73,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
name = "five_five_five"
|
name = "five_five_five"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"linuxvideo",
|
||||||
"rayon",
|
"rayon",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -83,6 +90,30 @@ version = "0.2.147"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linuxvideo"
|
||||||
|
version = "0.3.1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"log",
|
||||||
|
"nix",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -92,6 +123,19 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.26.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"memoffset 0.7.1",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
@ -102,6 +146,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.66"
|
version = "1.0.66"
|
||||||
|
|
|
@ -6,5 +6,6 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
linuxvideo = { path = "deps/LinuxVideo" }
|
||||||
rayon = "1.7.0"
|
rayon = "1.7.0"
|
||||||
thiserror = "1.0.48"
|
thiserror = "1.0.48"
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/// Wraps non-thread-safe capture device like a v4l webcam
|
||||||
|
|
||||||
|
use linuxvideo::
|
||||||
|
{
|
||||||
|
format::
|
||||||
|
{
|
||||||
|
PixFormat,
|
||||||
|
PixelFormat,
|
||||||
|
},
|
||||||
|
Device,
|
||||||
|
stream::ReadStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Capture
|
||||||
|
{
|
||||||
|
size_image: usize,
|
||||||
|
stream: ReadStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Capture
|
||||||
|
{
|
||||||
|
pub fn new () -> Result <Self, Error>
|
||||||
|
{
|
||||||
|
let x = Device::open ("/dev/video0")?;
|
||||||
|
dbg! (x.formats (linuxvideo::BufType::VIDEO_CAPTURE).collect::<Vec <_>> ());
|
||||||
|
let x = x.video_capture (PixFormat::new (u32::MAX, u32::MAX, PixelFormat::MJPG))?;
|
||||||
|
dbg! (x.format ());
|
||||||
|
let size_image = usize::try_from (x.format ().size_image ()).unwrap ();
|
||||||
|
let stream = x.into_stream ()?;
|
||||||
|
|
||||||
|
Ok (Self
|
||||||
|
{
|
||||||
|
size_image,
|
||||||
|
stream,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_image (&self) -> usize
|
||||||
|
{
|
||||||
|
self.size_image
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Blocks until the capture device gets us a frame
|
||||||
|
|
||||||
|
pub fn wait_for_frame (&mut self, output: &mut [u8]) -> Result <usize, Error>
|
||||||
|
{
|
||||||
|
if output.len () < self.size_image ()
|
||||||
|
{
|
||||||
|
return Err (Error::OutputBufferTooSmall);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok (self.stream.dequeue (|view|
|
||||||
|
{
|
||||||
|
let bytesused = usize::try_from (view.bytesused ()).unwrap ();
|
||||||
|
let input = &view [0..bytesused];
|
||||||
|
&mut output [0..bytesused].copy_from_slice (&input);
|
||||||
|
Ok (input.len ())
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive (Debug, thiserror::Error)]
|
||||||
|
pub enum Error
|
||||||
|
{
|
||||||
|
#[error ("I/O")]
|
||||||
|
Io (#[from] std::io::Error),
|
||||||
|
#[error ("output buffer too small")]
|
||||||
|
OutputBufferTooSmall,
|
||||||
|
}
|
|
@ -41,9 +41,9 @@ impl Controller
|
||||||
self.tx_pipe.handle_encoded_packet (buf_enc)
|
self.tx_pipe.handle_encoded_packet (buf_enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_network_busy (&mut self, x: bool)
|
pub fn handle_transmitted (&mut self, x: bool)
|
||||||
{
|
{
|
||||||
self.tx_pipe.handle_network_busy (x)
|
self.tx_pipe.handle_transmitted (x)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll (&mut self) -> ControlEvent
|
pub fn poll (&mut self) -> ControlEvent
|
||||||
|
@ -147,7 +147,8 @@ struct TxPipeline
|
||||||
capture_is_busy: bool,
|
capture_is_busy: bool,
|
||||||
encoder_has_data: bool,
|
encoder_has_data: bool,
|
||||||
encoder_is_busy: bool,
|
encoder_is_busy: bool,
|
||||||
network_busy: bool,
|
network_congested: bool,
|
||||||
|
transmitter_is_busy: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Debug)]
|
#[derive (Debug)]
|
||||||
|
@ -206,9 +207,10 @@ impl TxPipeline
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_network_busy (&mut self, busy: bool)
|
fn handle_transmitted (&mut self, busy: bool)
|
||||||
{
|
{
|
||||||
self.network_busy = busy;
|
self.network_congested = busy;
|
||||||
|
self.transmitter_is_busy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_encoded_packet (&self) -> bool
|
fn has_encoded_packet (&self) -> bool
|
||||||
|
@ -223,10 +225,11 @@ impl TxPipeline
|
||||||
|
|
||||||
fn poll (&mut self) -> Option <TxPipelineEvent>
|
fn poll (&mut self) -> Option <TxPipelineEvent>
|
||||||
{
|
{
|
||||||
if ! self.network_busy
|
if ! self.network_congested && ! self.transmitter_is_busy
|
||||||
{
|
{
|
||||||
if let Some (buf_enc) = self.buf_enc.take ()
|
if let Some (buf_enc) = self.buf_enc.take ()
|
||||||
{
|
{
|
||||||
|
self.transmitter_is_busy = true;
|
||||||
return Some (TxPipelineEvent::Transmit (buf_enc));
|
return Some (TxPipelineEvent::Transmit (buf_enc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
91
src/main.rs
91
src/main.rs
|
@ -10,17 +10,44 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use rayon::ThreadPool;
|
use rayon::ThreadPool;
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::controller::*;
|
use crate::controller::*;
|
||||||
|
|
||||||
|
mod capture;
|
||||||
mod controller;
|
mod controller;
|
||||||
|
|
||||||
fn main() -> Result <(), TaskError>
|
fn main() -> Result <(), Error>
|
||||||
{
|
{
|
||||||
let pool = rayon::ThreadPoolBuilder::new ().build ().unwrap ();
|
let mut args = std::env::args ();
|
||||||
let mut driver = Driver::new (&pool);
|
let _exe_name = args.next ();
|
||||||
driver.main ()?;
|
match args.next ().as_deref ()
|
||||||
|
{
|
||||||
|
None =>
|
||||||
|
{
|
||||||
|
let pool = rayon::ThreadPoolBuilder::new ().build ().unwrap ();
|
||||||
|
let mut driver = Driver::new (&pool)?;
|
||||||
|
driver.main ()?;
|
||||||
|
},
|
||||||
|
Some ("capture") =>
|
||||||
|
{
|
||||||
|
use std::io::Write;
|
||||||
|
let mut cap = capture::Capture::new ()?;
|
||||||
|
let mut buf = vec! [0u8; cap.size_image ()];
|
||||||
|
let mut bytesused = 0;
|
||||||
|
|
||||||
|
for _ in 0..5
|
||||||
|
{
|
||||||
|
let rc = cap.wait_for_frame (&mut buf);
|
||||||
|
bytesused = rc.unwrap ();
|
||||||
|
dbg! (bytesused);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut f = std::fs::File::create ("data.jpeg").unwrap ();
|
||||||
|
f.write_all (&buf [0..bytesused]).unwrap ();
|
||||||
|
},
|
||||||
|
Some (_) => eprintln! ("Unknown subcommand"),
|
||||||
|
}
|
||||||
|
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,14 +77,14 @@ struct Driver <'a>
|
||||||
|
|
||||||
ctl: Controller,
|
ctl: Controller,
|
||||||
|
|
||||||
capture: Task <Capture, ()>,
|
capture: Task <capture::Capture, ()>,
|
||||||
encoder: Task <Encoder, EncoderTaskMetadata>,
|
encoder: Task <Encoder, EncoderTaskMetadata>,
|
||||||
transmitter: Task <Transmitter, ()>,
|
transmitter: Task <Transmitter, ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl <'a> Driver <'_>
|
impl <'a> Driver <'_>
|
||||||
{
|
{
|
||||||
fn new (pool: &'a ThreadPool) -> Driver <'a>
|
fn new (pool: &'a ThreadPool) -> Result <Driver <'a>, Error>
|
||||||
{
|
{
|
||||||
let (send, recv) = mpsc::sync_channel (8);
|
let (send, recv) = mpsc::sync_channel (8);
|
||||||
|
|
||||||
|
@ -67,22 +94,22 @@ impl <'a> Driver <'_>
|
||||||
send,
|
send,
|
||||||
};
|
};
|
||||||
|
|
||||||
Driver
|
Ok (Driver
|
||||||
{
|
{
|
||||||
thread_sys,
|
thread_sys,
|
||||||
recv,
|
recv,
|
||||||
|
|
||||||
ctl: Default::default (),
|
ctl: Default::default (),
|
||||||
|
|
||||||
capture: Capture::default ().into (),
|
capture: capture::Capture::new ()?.into (),
|
||||||
encoder: Encoder::default ().into (),
|
encoder: Encoder::default ().into (),
|
||||||
transmitter: Transmitter::default ().into (),
|
transmitter: Transmitter::default ().into (),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main (&mut self) -> Result <(), TaskError>
|
fn main (&mut self) -> Result <(), TaskError>
|
||||||
{
|
{
|
||||||
for _ in 0..10
|
for _ in 0..50
|
||||||
{
|
{
|
||||||
let ev = self.ctl.poll ();
|
let ev = self.ctl.poll ();
|
||||||
|
|
||||||
|
@ -132,6 +159,9 @@ impl <'a> Driver <'_>
|
||||||
match event
|
match event
|
||||||
{
|
{
|
||||||
TxPipelineEvent::Capture => CaptureWork::dispatch (self, ())?,
|
TxPipelineEvent::Capture => CaptureWork::dispatch (self, ())?,
|
||||||
|
TxPipelineEvent::Encode (buf_raw) => EncodeWork::dispatch (self, buf_raw)?,
|
||||||
|
TxPipelineEvent::Transmit (buf_enc) => TransmitWork::dispatch (self, buf_enc)?,
|
||||||
|
|
||||||
TxPipelineEvent::PollEncoder =>
|
TxPipelineEvent::PollEncoder =>
|
||||||
{
|
{
|
||||||
let encoder = self.encoder.try_inner_mut ()?;
|
let encoder = self.encoder.try_inner_mut ()?;
|
||||||
|
@ -147,9 +177,7 @@ impl <'a> Driver <'_>
|
||||||
{
|
{
|
||||||
self.ctl.handle_encoded_packet (None).unwrap ();
|
self.ctl.handle_encoded_packet (None).unwrap ();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
TxPipelineEvent::Encode (buf_raw) => EncodeWork::dispatch (self, buf_raw)?,
|
|
||||||
TxPipelineEvent::Transmit (buf_enc) => TransmitWork::dispatch (self, buf_enc)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok (())
|
Ok (())
|
||||||
|
@ -164,7 +192,7 @@ enum Task <S, R>
|
||||||
Running (R),
|
Running (R),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive (Debug, Error)]
|
#[derive (Debug, thiserror::Error)]
|
||||||
enum TaskError
|
enum TaskError
|
||||||
{
|
{
|
||||||
#[error ("tried to start already-running task")]
|
#[error ("tried to start already-running task")]
|
||||||
|
@ -245,24 +273,6 @@ impl fmt::Debug for TaskOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps non-thread-safe capture device like a v4l webcam
|
|
||||||
|
|
||||||
#[derive (Default)]
|
|
||||||
struct Capture
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Capture
|
|
||||||
{
|
|
||||||
/// Blocks until the capture device gets us a frame
|
|
||||||
|
|
||||||
fn wait_for_frame (&mut self, _buf: &mut [u8]) -> bool
|
|
||||||
{
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wraps non-thread-safe encoder state like from ffmpeg
|
/// Wraps non-thread-safe encoder state like from ffmpeg
|
||||||
|
|
||||||
#[derive (Default)]
|
#[derive (Default)]
|
||||||
|
@ -352,7 +362,7 @@ impl Transmitter
|
||||||
|
|
||||||
struct CaptureWork
|
struct CaptureWork
|
||||||
{
|
{
|
||||||
cap: Capture,
|
cap: capture::Capture,
|
||||||
buf_raw: BufRaw,
|
buf_raw: BufRaw,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,7 +374,7 @@ impl CaptureWork
|
||||||
let mut cap = driver.capture.start (metadata)?;
|
let mut cap = driver.capture.start (metadata)?;
|
||||||
driver.thread_sys.dispatch (move ||
|
driver.thread_sys.dispatch (move ||
|
||||||
{
|
{
|
||||||
let dur_capture = Duration::from_millis (30);
|
let dur_capture = Duration::from_millis (1000);
|
||||||
thread::sleep (dur_capture);
|
thread::sleep (dur_capture);
|
||||||
let buf_raw = BufRaw
|
let buf_raw = BufRaw
|
||||||
{
|
{
|
||||||
|
@ -441,7 +451,16 @@ impl TransmitWork
|
||||||
fn finish (self, driver: &mut Driver) -> Result <(), TaskError>
|
fn finish (self, driver: &mut Driver) -> Result <(), TaskError>
|
||||||
{
|
{
|
||||||
driver.transmitter.stop (self.tx)?;
|
driver.transmitter.stop (self.tx)?;
|
||||||
driver.ctl.handle_network_busy (self.busy);
|
driver.ctl.handle_transmitted (self.busy);
|
||||||
Ok (())
|
Ok (())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive (Debug, thiserror::Error)]
|
||||||
|
enum Error
|
||||||
|
{
|
||||||
|
#[error ("capture")]
|
||||||
|
Capture (#[from] capture::Error),
|
||||||
|
#[error ("task")]
|
||||||
|
Task (#[from] TaskError),
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue