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

640x480, 12FPS video streaming

parent 0077adf1
No related branches found
No related tags found
1 merge request!5Video
......@@ -23,6 +23,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "atomic_refcell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31"
[[package]]
name = "autocfg"
version = "1.1.0"
......@@ -38,6 +44,9 @@ dependencies = [
"base64",
"bitvec",
"crc",
"gstreamer",
"gstreamer-app",
"gstreamer-video",
"image",
"kiss-tnc",
"rppal",
......@@ -66,6 +75,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "bitvec"
version = "1.0.1"
......@@ -111,6 +126,16 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-expr"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
......@@ -285,18 +310,69 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures-channel"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-macro"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
]
[[package]]
name = "futures-sink"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]]
name = "futures-task"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]]
name = "futures-util"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [
"futures-core",
"futures-macro",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
......@@ -330,6 +406,197 @@ dependencies = [
"weezl",
]
[[package]]
name = "gio-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
"winapi",
]
[[package]]
name = "glib"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "331156127e8166dd815cf8d2db3a5beb492610c716c03ee6db4f2d07092af0a7"
dependencies = [
"bitflags 2.4.0",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"memchr",
"once_cell",
"smallvec",
"thiserror",
]
[[package]]
name = "glib-macros"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "179643c50bf28d20d2f6eacd2531a88f2f5d9747dd0b86b8af1e8bb5dd0de3c0"
dependencies = [
"heck",
"proc-macro-crate",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.18",
]
[[package]]
name = "glib-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cdb86791dc39a5443f7d08cf3e7ae9c88a94991aba620d177cb5804838201f"
dependencies = [
"cfg-if",
"futures-channel",
"futures-core",
"futures-util",
"glib",
"gstreamer-sys",
"itertools",
"libc",
"muldiv",
"num-integer",
"num-rational",
"option-operations",
"paste",
"pretty-hex",
"smallvec",
"thiserror",
]
[[package]]
name = "gstreamer-app"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6da0a8e5671d836ac70741dff0a3e7688ec9ac7ab8dc62c3135dece85df63d4"
dependencies = [
"futures-core",
"futures-sink",
"glib",
"gstreamer",
"gstreamer-app-sys",
"gstreamer-base",
"libc",
]
[[package]]
name = "gstreamer-app-sys"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb093f39bd9b3d0c4ef8f1c4d46028d35bce033f9bda1a0449e21ef17349e03"
dependencies = [
"glib-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-base"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fe38a6d5c1e516ce3fd6069e972a540d315448ed69fdadad739e6c6c6eb2a01"
dependencies = [
"atomic_refcell",
"cfg-if",
"glib",
"gstreamer",
"gstreamer-base-sys",
"libc",
]
[[package]]
name = "gstreamer-base-sys"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b9c029583ed61fa5258076a42df91732dc7f5582044ea7ee66a721641e6af4"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-sys"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a70e3a99118bcd1221f8a62d7a905bae5e5cc2cda678bb46bf3cd36e0f899d33"
dependencies = [
"glib-sys",
"gobject-sys",
"libc",
"system-deps",
]
[[package]]
name = "gstreamer-video"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0db8adfc000cd58f8ece0fe6b4beb79e19e4a6135cfb81138fdb016b603f7d60"
dependencies = [
"cfg-if",
"futures-channel",
"glib",
"gstreamer",
"gstreamer-base",
"gstreamer-video-sys",
"libc",
]
[[package]]
name = "gstreamer-video-sys"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0284250a09fa824b21df1a21967eef4a5d85b5e0c1e335ed2ba9b9be1424dae"
dependencies = [
"glib-sys",
"gobject-sys",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps",
]
[[package]]
name = "half"
version = "2.2.1"
......@@ -345,6 +612,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
......@@ -383,6 +656,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
......@@ -497,6 +779,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "muldiv"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0"
[[package]]
name = "nanorand"
version = "0.7.0"
......@@ -552,6 +840,15 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "option-operations"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0"
dependencies = [
"paste",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
......@@ -575,6 +872,12 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pin-project"
version = "1.1.0"
......@@ -592,7 +895,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
]
[[package]]
......@@ -601,19 +904,71 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "png"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide 0.7.1",
]
[[package]]
name = "pretty-hex"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6fa0831dd7cc608c38a5e323422a0077678fa5744aa2be4ad91c4ece8eec8d5"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.59"
......@@ -675,7 +1030,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
......@@ -710,7 +1065,7 @@ checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
]
[[package]]
......@@ -747,6 +1102,15 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
[[package]]
name = "slab"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
......@@ -782,6 +1146,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.18"
......@@ -793,12 +1167,31 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a"
[[package]]
name = "thiserror"
version = "1.0.40"
......@@ -816,7 +1209,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
]
[[package]]
......@@ -857,7 +1250,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
]
[[package]]
......@@ -906,6 +1299,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.4"
......@@ -939,7 +1338,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
"wasm-bindgen-shared",
]
......@@ -961,7 +1360,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.18",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
......
......@@ -29,3 +29,6 @@ sha3 = "0.10.7"
subprocess = "0.2.9"
tokio = { version = "1.27.0", features = ["full"] }
toml = "0.7.3"
gstreamer = "0.21.0"
gstreamer-app = "0.21.0"
gstreamer-video = "0.21.0"
......@@ -10,12 +10,14 @@ use crate::{
aprs::CommandHandler,
config::Config,
img::ImgManager,
packet::{FecPacket, Packet},
packet::{FecPacket, Packet, RawPacket},
radio::McuRadio,
ssdv::ssdv_encode,
video::{start_video, VideoPacker},
};
const IMAGE_PACKET_QUEUE_LENGTH: usize = 8192;
const VIDEO_PACKET_QUEUE_LENGTH: usize = 1024;
const TEMP_REFRESH_INTERVAL: Duration = Duration::from_secs(5);
pub struct Controller {
......@@ -29,12 +31,13 @@ impl Controller {
pub fn run_forever(self) {
let (img_tx, img_rx) = mpsc::sync_channel(IMAGE_PACKET_QUEUE_LENGTH);
let (vid_tx, vid_rx) = mpsc::sync_channel(VIDEO_PACKET_QUEUE_LENGTH);
let (telem_tx, telem_rx) = mpsc::channel();
let (cmd_tx, cmd_rx) = mpsc::channel();
{
let callsign = self.config.callsign.clone();
thread::spawn(|| Self::tx_thread(callsign, img_rx, telem_rx));
thread::spawn(|| Self::tx_thread(callsign, img_rx, vid_rx, telem_rx));
}
{
......@@ -42,6 +45,10 @@ impl Controller {
thread::spawn(|| Self::aprs_thread(config, cmd_tx, telem_tx));
}
{
start_video(vid_tx);
}
let mut manager = ImgManager::new(self.config.paths.clone(), cmd_rx);
loop {
......@@ -84,7 +91,12 @@ impl Controller {
}
// manages our transceiver
fn tx_thread(callsign: String, image_rx: Receiver<FecPacket>, telem_rx: Receiver<Packet>) {
fn tx_thread(
callsign: String,
image_rx: Receiver<FecPacket>,
vid_rx: Receiver<Vec<u8>>,
telem_rx: Receiver<Packet>,
) {
let mut radio = loop {
let r = McuRadio::new();
......@@ -99,14 +111,17 @@ impl Controller {
};
let mut text_msg_id = 0;
let mut video_packer = VideoPacker::new();
let mut last_got_temp = Instant::now();
loop {
if let Err(e) = Self::tx_thread_single_iter(
&callsign,
&image_rx,
&vid_rx,
&telem_rx,
&mut text_msg_id,
&mut last_got_temp,
&mut video_packer,
&mut radio,
) {
eprintln!("Radio error: {}", e);
......@@ -115,12 +130,16 @@ impl Controller {
}
}
// TODO this should probably be a struct
#[allow(clippy::too_many_arguments)]
fn tx_thread_single_iter(
callsign: &str,
image_rx: &Receiver<FecPacket>,
vid_rx: &Receiver<Vec<u8>>,
telem_rx: &Receiver<Packet>,
text_msg_id: &mut u16,
last_got_temp: &mut Instant,
video_packer: &mut VideoPacker,
radio: &mut McuRadio,
) -> anyhow::Result<()> {
while let Ok(tm) = telem_rx.try_recv() {
......@@ -129,12 +148,32 @@ impl Controller {
radio.send_packet(&tm)?;
}
while let Ok(vid) = vid_rx.try_recv() {
video_packer.pack(&vid, |pkt| radio.send_packet(&pkt.into()))?;
}
if let Ok(img) = image_rx.try_recv() {
radio.send_packet(&img)?;
} else {
radio.flush()?;
thread::sleep(Duration::from_millis(50));
// send garbage. We want our radio to be active at all times, for AGC purposes on the downlink side
const GARBAGE: [u8; 256] = [
102, 54, 91, 67, 89, 223, 198, 83, 22, 16, 48, 184, 117, 97, 153, 246, 169, 167,
89, 85, 49, 67, 128, 123, 83, 210, 58, 104, 248, 102, 219, 195, 121, 57, 101, 172,
57, 223, 190, 106, 90, 36, 39, 156, 99, 92, 87, 10, 56, 29, 137, 71, 144, 89, 82,
182, 127, 72, 93, 249, 214, 6, 155, 164, 177, 22, 84, 111, 52, 60, 68, 235, 30, 13,
174, 101, 49, 43, 95, 61, 214, 89, 110, 24, 77, 208, 103, 209, 87, 12, 218, 147,
224, 85, 178, 49, 28, 233, 65, 132, 61, 238, 70, 164, 177, 90, 158, 99, 180, 77,
251, 17, 227, 43, 109, 33, 120, 15, 89, 172, 69, 213, 25, 166, 59, 254, 220, 31,
21, 247, 246, 12, 204, 223, 134, 136, 100, 92, 20, 182, 204, 79, 239, 120, 8, 40,
138, 222, 239, 85, 15, 196, 169, 36, 38, 193, 207, 165, 7, 4, 33, 4, 120, 250, 114,
240, 128, 3, 22, 62, 254, 139, 13, 56, 153, 15, 63, 96, 62, 44, 128, 241, 25, 22,
125, 127, 0, 137, 165, 145, 156, 39, 90, 94, 145, 86, 156, 17, 187, 217, 249, 193,
112, 160, 238, 216, 183, 46, 27, 74, 38, 127, 233, 188, 184, 35, 194, 249, 90, 195,
33, 21, 67, 56, 75, 243, 140, 6, 187, 93, 49, 224, 20, 34, 204, 204, 141, 132, 252,
101, 3, 149, 107, 173, 139, 125, 41, 133, 251, 42, 171, 130, 254, 145, 34, 56,
];
radio.send_packet(&RawPacket(GARBAGE).into())?;
}
if Instant::now() - *last_got_temp > TEMP_REFRESH_INTERVAL {
......
......@@ -10,6 +10,7 @@ mod ldpc;
mod packet;
mod radio;
mod ssdv;
mod video;
fn main() -> anyhow::Result<()> {
let config = Config::load()?;
......
......@@ -3,10 +3,12 @@ use crc::{Crc, CRC_16_IBM_3740};
use crate::ldpc::ldpc_encode;
const TEXT_MESSAGE_LEN: usize = 252;
pub const VIDEO_LEN: usize = 255;
const FEC_PACKET_LEN: usize = 256 + 2 + 65;
pub enum Packet {
TextMessage(u8, [u8; TEXT_MESSAGE_LEN]),
Video([u8; VIDEO_LEN]),
}
impl Packet {
......@@ -33,6 +35,10 @@ impl Packet {
Self::TextMessage(len, out)
}
pub fn new_video(data: [u8; VIDEO_LEN]) -> Self {
Self::Video(data)
}
}
impl Packet {
......@@ -52,12 +58,21 @@ impl Packet {
RawPacket(out)
}
Packet::Video(data) => {
let mut out = [0; 256];
out[0] = 0x04; // packet type
out[1..].clone_from_slice(&data);
RawPacket(out)
}
}
}
}
pub struct RawPacket(pub [u8; 256]);
#[derive(Debug)]
pub struct FecPacket(pub [u8; FEC_PACKET_LEN]);
impl From<RawPacket> for FecPacket {
......
......@@ -57,13 +57,6 @@ impl McuRadio {
Ok(self.get_info()?.1)
}
// sends a bunch of nops over SPI to clear out its DMA buffer
pub fn flush(&mut self) -> anyhow::Result<()> {
self.spi.write(&[NOP_CMD; 256])?;
Ok(())
}
// try 10 times and then give up
fn get_info(&mut self) -> anyhow::Result<(bool, f32)> {
for _ in 0..9 {
......
use std::{
sync::mpsc::{SyncSender, TrySendError},
thread,
time::Duration,
};
use crate::packet::{Packet, RawPacket, VIDEO_LEN};
use anyhow::Context;
use gstreamer::{
element_error,
prelude::{Cast, ElementExtManual, GstBinExtManual},
traits::{ElementExt, GstObjectExt},
Bus, Caps, ClockTime, FlowError, FlowSuccess, Pipeline, ResourceError, State,
};
use gstreamer_app::{AppSink, AppSinkCallbacks};
use gstreamer_video::{VideoCapsBuilder, VideoFormat};
const DATA_WHITENER: [u8; 255] = [
102, 54, 91, 67, 89, 223, 198, 83, 22, 16, 48, 184, 117, 97, 153, 246, 169, 167, 89, 85, 49,
67, 128, 123, 83, 210, 58, 104, 248, 102, 219, 195, 121, 57, 101, 172, 57, 223, 190, 106, 90,
36, 39, 156, 99, 92, 87, 10, 56, 29, 137, 71, 144, 89, 82, 182, 127, 72, 93, 249, 214, 6, 155,
164, 177, 22, 84, 111, 52, 60, 68, 235, 30, 13, 174, 101, 49, 43, 95, 61, 214, 89, 110, 24, 77,
208, 103, 209, 87, 12, 218, 147, 224, 85, 178, 49, 28, 233, 65, 132, 61, 238, 70, 164, 177, 90,
158, 99, 180, 77, 251, 17, 227, 43, 109, 33, 120, 15, 89, 172, 69, 213, 25, 166, 59, 254, 220,
31, 21, 247, 246, 12, 204, 223, 134, 136, 100, 92, 20, 182, 204, 79, 239, 120, 8, 40, 138, 222,
239, 85, 15, 196, 169, 36, 38, 193, 207, 165, 7, 4, 33, 4, 120, 250, 114, 240, 128, 3, 22, 62,
254, 139, 13, 56, 153, 15, 63, 96, 62, 44, 128, 241, 25, 22, 125, 127, 0, 137, 165, 145, 156,
39, 90, 94, 145, 86, 156, 17, 187, 217, 249, 193, 112, 160, 238, 216, 183, 46, 27, 74, 38, 127,
233, 188, 184, 35, 194, 249, 90, 195, 33, 21, 67, 56, 75, 243, 140, 6, 187, 93, 49, 224, 20,
34, 204, 204, 141, 132, 252, 101, 3, 149, 107, 173, 139, 125, 41, 133, 251, 42, 171, 130, 254,
145, 34,
];
pub struct VideoPacker {
buf: [u8; VIDEO_LEN],
buf_i: usize,
}
impl VideoPacker {
pub fn new() -> Self {
Self {
buf: [0; VIDEO_LEN],
buf_i: 0,
}
}
// theoretically suboptimal. If one packet fails to send the loop terminates and we throw away all the passed in data
pub fn pack<E, F>(&mut self, data: &[u8], mut f: F) -> Result<(), E>
where
F: FnMut(RawPacket) -> Result<(), E>,
{
for d in data {
self.buf[self.buf_i] = *d;
self.buf_i += 1;
if self.buf_i >= VIDEO_LEN {
// data whitening
// makes our SDR happy
for (b, w) in self.buf.iter_mut().zip(DATA_WHITENER.iter()) {
*b ^= w;
}
let pkt = Packet::new_video(self.buf);
self.buf_i = 0;
// very garbage. we only pass in a text id here
// because the function requires it. it's not used
f(pkt.into_raw(&mut 0))?;
}
}
Ok(())
}
}
pub fn start_video(sender: SyncSender<Vec<u8>>) {
thread::spawn(move || loop {
match init(sender.clone()) {
Ok((pipeline, bus)) => handle_pipeline(pipeline, bus),
Err(e) => {
eprintln!("Could not restart video pipeline: {e:?}")
}
}
thread::sleep(Duration::from_secs(1));
});
}
fn init(sender: SyncSender<Vec<u8>>) -> anyhow::Result<(Pipeline, Bus)> {
gstreamer::init()?;
let src = gstreamer::ElementFactory::make("libcamerasrc")
.name("source")
.build()
.context("Could not create source element")?;
let capsfilter = gstreamer::ElementFactory::make("capsfilter")
.property(
"caps",
VideoCapsBuilder::new()
.width(640)
.height(480)
.framerate((12, 1).into())
.format(VideoFormat::Nv21)
.build(),
)
.build()
.context("Could not build capsfilter")?;
let conv = gstreamer::ElementFactory::make("videoconvert")
.name("conv")
.build()
.context("Could not build converter")?;
let enc = gstreamer::ElementFactory::make("x265enc")
.name("enc")
.property("bitrate", 200u32)
.property("key-int-max", 48)
.property_from_str("speed-preset", "ultrafast")
.build()
.context("Could not build encoder")?;
let parse = gstreamer::ElementFactory::make("h265parse")
.name("parse")
.property("config-interval", -1)
.build()
.context("Could not build parser")?;
let mpegts = gstreamer::ElementFactory::make("mpegtsmux")
.name("mpegtsmux")
.build()
.context("Could not create mpegts element")?;
let appsink = AppSink::builder().caps(&Caps::new_any()).build();
let pipeline = gstreamer::Pipeline::with_name("pipeline");
pipeline.add_many([
&src,
&capsfilter,
&conv,
&enc,
&parse,
&mpegts,
appsink.upcast_ref(),
])?;
src.link(&capsfilter)?;
capsfilter.link(&conv)?;
conv.link(&enc)?;
enc.link(&parse)?;
parse.link(&mpegts)?;
mpegts.link(&appsink)?;
appsink.set_callbacks(
AppSinkCallbacks::builder()
.new_sample(move |appsink| {
let sample = appsink.pull_sample().map_err(|_| FlowError::Eos)?;
let buffer = sample.buffer().ok_or_else(|| {
element_error!(
appsink,
ResourceError::Failed,
("Failed to get buffer from appsink")
);
FlowError::Error
})?;
let map = buffer
.map_readable()
.map_err(|_| -> FlowError { FlowError::Error })?;
match sender.try_send(map.as_ref().to_vec()) {
Ok(_) => {}
Err(TrySendError::Full(_)) => {
eprintln!("Video buffer overrun. Skipping frames");
}
Err(TrySendError::Disconnected(_)) => {
element_error!(appsink, ResourceError::Failed, ("Channel disconnected"));
return Err(FlowError::Error);
}
}
Ok(FlowSuccess::Ok)
})
.build(),
);
pipeline
.set_state(State::Playing)
.context("Unable to set the pipeline to the `Playing` state")?;
let bus = pipeline.bus().context("No pipeline bus")?;
Ok((pipeline, bus))
}
fn handle_pipeline(pipeline: Pipeline, bus: Bus) {
// Wait until error or EOS
for msg in bus.iter_timed(ClockTime::NONE) {
use gstreamer::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
eprintln!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
break;
}
_ => (),
}
}
// Shutdown pipeline
if let Err(e) = pipeline.set_state(State::Null) {
println!("Unable to set the pipeline to the `Null` state: {e}");
}
}
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