⭐ capturing MJPG
parent
28daa28d90
commit
3da0836c8a
|
@ -1 +1,2 @@
|
|||
/deps
|
||||
/target
|
||||
|
|
|
@ -8,6 +8,12 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -44,7 +50,7 @@ dependencies = [
|
|||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"memoffset 0.9.0",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
|
@ -67,6 +73,7 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
|||
name = "five_five_five"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"linuxvideo",
|
||||
"rayon",
|
||||
"thiserror",
|
||||
]
|
||||
|
@ -83,6 +90,30 @@ version = "0.2.147"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
|
@ -92,6 +123,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
|
@ -102,6 +146,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
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
|
||||
|
||||
[dependencies]
|
||||
linuxvideo = { path = "deps/LinuxVideo" }
|
||||
rayon = "1.7.0"
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -147,7 +147,8 @@ struct TxPipeline
|
|||
capture_is_busy: bool,
|
||||
encoder_has_data: bool,
|
||||
encoder_is_busy: bool,
|
||||
network_busy: bool,
|
||||
network_congested: bool,
|
||||
transmitter_is_busy: bool,
|
||||
}
|
||||
|
||||
#[derive (Debug)]
|
||||
|
@ -206,9 +207,10 @@ impl TxPipeline
|
|||
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
|
||||
|
@ -223,10 +225,11 @@ impl TxPipeline
|
|||
|
||||
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 ()
|
||||
{
|
||||
self.transmitter_is_busy = true;
|
||||
return Some (TxPipelineEvent::Transmit (buf_enc));
|
||||
}
|
||||
}
|
||||
|
|
91
src/main.rs
91
src/main.rs
|
@ -10,17 +10,44 @@ use std::{
|
|||
};
|
||||
|
||||
use rayon::ThreadPool;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::controller::*;
|
||||
|
||||
mod capture;
|
||||
mod controller;
|
||||
|
||||
fn main() -> Result <(), TaskError>
|
||||
fn main() -> Result <(), Error>
|
||||
{
|
||||
let pool = rayon::ThreadPoolBuilder::new ().build ().unwrap ();
|
||||
let mut driver = Driver::new (&pool);
|
||||
driver.main ()?;
|
||||
let mut args = std::env::args ();
|
||||
let _exe_name = args.next ();
|
||||
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 (())
|
||||
}
|
||||
|
||||
|
@ -50,14 +77,14 @@ struct Driver <'a>
|
|||
|
||||
ctl: Controller,
|
||||
|
||||
capture: Task <Capture, ()>,
|
||||
capture: Task <capture::Capture, ()>,
|
||||
encoder: Task <Encoder, EncoderTaskMetadata>,
|
||||
transmitter: Task <Transmitter, ()>,
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -67,22 +94,22 @@ impl <'a> Driver <'_>
|
|||
send,
|
||||
};
|
||||
|
||||
Driver
|
||||
Ok (Driver
|
||||
{
|
||||
thread_sys,
|
||||
recv,
|
||||
|
||||
ctl: Default::default (),
|
||||
|
||||
capture: Capture::default ().into (),
|
||||
capture: capture::Capture::new ()?.into (),
|
||||
encoder: Encoder::default ().into (),
|
||||
transmitter: Transmitter::default ().into (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn main (&mut self) -> Result <(), TaskError>
|
||||
{
|
||||
for _ in 0..10
|
||||
for _ in 0..50
|
||||
{
|
||||
let ev = self.ctl.poll ();
|
||||
|
||||
|
@ -132,6 +159,9 @@ impl <'a> Driver <'_>
|
|||
match event
|
||||
{
|
||||
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 =>
|
||||
{
|
||||
let encoder = self.encoder.try_inner_mut ()?;
|
||||
|
@ -147,9 +177,7 @@ impl <'a> Driver <'_>
|
|||
{
|
||||
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 (())
|
||||
|
@ -164,7 +192,7 @@ enum Task <S, R>
|
|||
Running (R),
|
||||
}
|
||||
|
||||
#[derive (Debug, Error)]
|
||||
#[derive (Debug, thiserror::Error)]
|
||||
enum TaskError
|
||||
{
|
||||
#[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
|
||||
|
||||
#[derive (Default)]
|
||||
|
@ -352,7 +362,7 @@ impl Transmitter
|
|||
|
||||
struct CaptureWork
|
||||
{
|
||||
cap: Capture,
|
||||
cap: capture::Capture,
|
||||
buf_raw: BufRaw,
|
||||
}
|
||||
|
||||
|
@ -364,7 +374,7 @@ impl CaptureWork
|
|||
let mut cap = driver.capture.start (metadata)?;
|
||||
driver.thread_sys.dispatch (move ||
|
||||
{
|
||||
let dur_capture = Duration::from_millis (30);
|
||||
let dur_capture = Duration::from_millis (1000);
|
||||
thread::sleep (dur_capture);
|
||||
let buf_raw = BufRaw
|
||||
{
|
||||
|
@ -441,7 +451,16 @@ impl TransmitWork
|
|||
fn finish (self, driver: &mut Driver) -> Result <(), TaskError>
|
||||
{
|
||||
driver.transmitter.stop (self.tx)?;
|
||||
driver.ctl.handle_network_busy (self.busy);
|
||||
driver.ctl.handle_transmitted (self.busy);
|
||||
Ok (())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive (Debug, thiserror::Error)]
|
||||
enum Error
|
||||
{
|
||||
#[error ("capture")]
|
||||
Capture (#[from] capture::Error),
|
||||
#[error ("task")]
|
||||
Task (#[from] TaskError),
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue