capturing MJPG

main
_ 2023-09-09 13:31:37 -05:00
parent 28daa28d90
commit 3da0836c8a
6 changed files with 186 additions and 43 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/deps
/target

52
Cargo.lock generated
View File

@ -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"

View File

@ -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"

69
src/capture.rs Normal file
View File

@ -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,
}

View File

@ -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));
}
}

View File

@ -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),
}