Skip to content
Snippets Groups Projects
Commit 964aab47 authored by Stephen D's avatar Stephen D
Browse files

wire up requesting high quality images

parent 6886b843
No related branches found
No related tags found
No related merge requests found
......@@ -39,7 +39,7 @@ pub struct CommandHandler {
burst_command: String,
cutdown_command: String,
img_request_queue: Sender<u8>,
img_request: Sender<u8>,
}
impl CommandHandler {
......@@ -48,7 +48,7 @@ impl CommandHandler {
secret: String,
burst_command: String,
cutdown_command: String,
img_request_queue: Sender<u8>,
img_request: Sender<u8>,
) -> Self {
Self {
callsign,
......@@ -57,11 +57,11 @@ impl CommandHandler {
burst_command,
cutdown_command,
img_request_queue,
img_request,
}
}
fn process_forever(&self) {
pub fn process_forever(&self) {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
......
use std::{
io::{Read, Write},
path::PathBuf,
sync::mpsc::{self, Receiver, SyncSender},
sync::mpsc::{self, Receiver, Sender, SyncSender},
thread,
time::Duration,
};
......@@ -9,36 +8,38 @@ use std::{
use anyhow::Context;
use serial::{BaudRate::BaudOther, SerialPort};
use crate::{img::ImgManager, packet::FecPacket, ssdv::ssdv_encode};
use crate::{
aprs::CommandHandler, config::Config, img::ImgManager, packet::FecPacket, ssdv::ssdv_encode,
};
const IMAGE_PACKET_QUEUE_LENGTH: usize = 1024;
pub struct Controller {
callsign: String,
manager: ImgManager,
config: Config,
}
impl Controller {
pub fn new(callsign: String, paths: Vec<PathBuf>) -> Self {
Self {
callsign,
manager: ImgManager::new(paths),
}
pub fn new(config: Config) -> Self {
Self { config }
}
pub fn run_forever(&mut self) {
let (tx, rx) = mpsc::sync_channel(IMAGE_PACKET_QUEUE_LENGTH);
pub fn run_forever(self) {
let (img_tx, img_rx) = mpsc::sync_channel(IMAGE_PACKET_QUEUE_LENGTH);
let (cmd_tx, cmd_rx) = mpsc::channel();
thread::spawn(|| Self::tx_thread(img_rx));
{
let config = self.config.clone();
thread::spawn(|| Self::aprs_thread(config, cmd_tx));
}
thread::spawn(|| Self::tx_thread(rx));
let mut manager = ImgManager::new(self.config.paths.clone(), cmd_rx);
let mut idx = 0;
loop {
while let Some(bytes) = self.manager.next() {
if let Err(e) = self.process_image(&bytes, idx, &tx) {
while let Some((idx, bytes, lossless)) = manager.next() {
if let Err(e) = self.process_image(&bytes, idx, &img_tx, lossless) {
eprintln!("Error processing image: {e}");
}
idx = idx.wrapping_add(1);
}
// after we don't find anything, sleep for a bit while
......@@ -47,14 +48,35 @@ impl Controller {
}
}
fn process_image(&self, img: &[u8], idx: u8, tx: &SyncSender<FecPacket>) -> anyhow::Result<()> {
for p in ssdv_encode(&self.callsign, img, idx)? {
fn process_image(
&self,
img: &[u8],
idx: u8,
tx: &SyncSender<FecPacket>,
lossless: bool,
) -> anyhow::Result<()> {
for p in ssdv_encode(&self.config.callsign, img, idx, lossless)? {
tx.send(p.into()).context("TX thread died")?;
}
Ok(())
}
// manages incoming APRS packets
// used to request HD images, as well as
// to initiate burst/cutdown
fn aprs_thread(config: Config, tx: Sender<u8>) {
let handler = CommandHandler::new(
config.callsign,
config.secret,
config.burst_command,
config.cutdown_command,
tx,
);
handler.process_forever();
}
// manages our transceiver
// TODO currently very hacky, because we're going to rip
// most of this out when we stop using a lobotomized NPR-70
......
use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat};
use std::{fs, io::Cursor, path::PathBuf};
use std::{fs, io::Cursor, path::PathBuf, sync::mpsc::Receiver};
const IMG_DIM: u32 = 1024;
const JPEG_QUALITY: u8 = 100; // let ssdv do the compression
......@@ -43,35 +43,78 @@ impl ImgInfo {
Ok(bytes)
}
pub fn lossless_jpeg_bytes(&self) -> anyhow::Result<Vec<u8>> {
let img = ImageReader::open(&self.path)?.decode()?;
// turn image into a square, making it bigger in the process
// must be divisible by 16
let mut dim = img.width().max(img.height());
let offset = dim % 16;
dim += (16 - offset) % 16;
img.crop_imm(0, 0, dim, dim);
let mut bytes = vec![];
img.write_to(
&mut Cursor::new(&mut bytes),
ImageOutputFormat::Jpeg(JPEG_QUALITY),
)?;
Ok(bytes)
}
}
pub struct ImgManager {
paths: Vec<PathBuf>,
imgs: Vec<ImgInfo>,
// used to receive image requests, which will be sent at full resolution
rx: Receiver<u8>,
// state for iterator
iter: u64,
path_idx: usize,
found: bool,
tx_idx: u8,
// map between sent image ids, the the images
// in the array above
// used for decoding rx requests
img_map: [usize; 256],
}
impl ImgManager {
pub fn new(paths: Vec<PathBuf>) -> Self {
pub fn new(paths: Vec<PathBuf>, rx: Receiver<u8>) -> Self {
Self {
paths,
imgs: vec![],
rx,
iter: 0,
path_idx: 0,
found: false,
tx_idx: 0,
img_map: [0; 256],
}
}
// not making this an iterator since
// it's entirely valid to have a Some(...)
// after a None
pub fn next(&mut self) -> Option<Vec<u8>> {
pub fn next(&mut self) -> Option<(u8, Vec<u8>, bool)> {
loop {
if let Ok(req) = self.rx.try_recv() {
let img = self
.img_map
.get(usize::from(req))
.and_then(|id| self.imgs.get(*id))
.and_then(|img| img.lossless_jpeg_bytes().ok());
if let Some(img) = img {
return Some((self.inc_tx(None), img, true));
}
}
if let Err(e) = self.scan_path() {
eprintln!(
"Error scanning {}: {}",
......@@ -84,21 +127,22 @@ impl ImgManager {
let new = self
.imgs
.iter_mut()
.filter(|img| !img.txed) // non-sent images only
.filter(|img| img.path_idx == self.path_idx)
.filter(|img| img.found_on_iter == self.iter)
.max_by_key(|img| img.size_bytes);
.enumerate()
.filter(|(_, img)| !img.txed) // non-sent images only
.filter(|(_, img)| img.path_idx == self.path_idx)
.filter(|(_, img)| img.found_on_iter == self.iter)
.max_by_key(|(_, img)| img.size_bytes);
if let Some(new) = new {
if let Some((new_id, new)) = new {
new.txed = true;
let bytes = new.compressed_jpeg_bytes();
self.inc_iter(true);
// not perfect. ideally we would keep looking throug the new images
// not perfect. ideally we would keep looking through the new images
// if this Err'd
if let Ok(x) = bytes {
return Some(x);
return Some((self.inc_tx(Some(new_id)), x, false));
}
}
......@@ -106,18 +150,19 @@ impl ImgManager {
let old = self
.imgs
.iter_mut()
.filter(|img| !img.txed) // non-sent images only
.filter(|img| img.path_idx == self.path_idx)
.max_by_key(|img| img.size_bytes);
.enumerate()
.filter(|(_, img)| !img.txed) // non-sent images only
.filter(|(_, img)| img.path_idx == self.path_idx)
.max_by_key(|(_, img)| img.size_bytes);
if let Some(old) = old {
if let Some((old_idx, old)) = old {
old.txed = true;
let bytes = old.compressed_jpeg_bytes();
self.inc_iter(true);
if let Ok(x) = bytes {
return Some(x);
return Some((self.inc_tx(Some(old_idx)), x, false));
}
}
......@@ -146,6 +191,17 @@ impl ImgManager {
true
}
fn inc_tx(&mut self, id: Option<usize>) -> u8 {
let idx = self.tx_idx;
self.tx_idx = self.tx_idx.wrapping_add(1);
if let Some(id) = id {
self.img_map[usize::from(idx)] = id;
}
idx
}
fn scan_path(&mut self) -> anyhow::Result<()> {
// imgs we don't already have
let new_imgs: Vec<_> = fs::read_dir(&self.paths[self.path_idx])?
......
......@@ -13,7 +13,7 @@ mod ssdv;
fn main() -> anyhow::Result<()> {
let config = Config::load()?;
let mut controller = Controller::new(config.callsign, config.paths);
let controller = Controller::new(config);
controller.run_forever();
......
......@@ -4,7 +4,14 @@ use crate::packet::RawPacket;
// TODO eventually rewrite Ssdv in Rust?
// Don't want to use FFI because then segfaults can hurt us
pub fn ssdv_encode(callsign: &str, img: &[u8], img_idx: u8) -> anyhow::Result<Vec<RawPacket>> {
pub fn ssdv_encode(
callsign: &str,
img: &[u8],
img_idx: u8,
lossless: bool,
) -> anyhow::Result<Vec<RawPacket>> {
let quality = if lossless { "7" } else { "6" };
let (stdout, stderr) = subprocess::Exec::cmd("ssdv")
.args(&[
"-e",
......@@ -12,7 +19,7 @@ pub fn ssdv_encode(callsign: &str, img: &[u8], img_idx: u8) -> anyhow::Result<Ve
callsign,
"-n",
"-q",
"6",
quality,
"-i",
&format!("{}", img_idx),
])
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment