diff --git a/Cargo.lock b/Cargo.lock index 38edde1..a2e88c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,6 +236,25 @@ dependencies = [ "web-sys", ] +[[package]] +name = "cptv-decoder" +version = "0.1.0" +dependencies = [ + "codec", + "console_error_panic_hook", + "console_log", + "flate2", + "js-sys", + "log", + "nom", + "pollster", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "cptv-downloader" version = "0.1.0" @@ -1435,25 +1454,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "wasm-bindings" -version = "0.1.0" -dependencies = [ - "codec", - "console_error_panic_hook", - "console_log", - "flate2", - "js-sys", - "log", - "nom", - "pollster", - "serde", - "serde-wasm-bindgen", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" version = "0.3.69" diff --git a/cptv-codec-rs/src/common/cptv_frame.rs b/cptv-codec-rs/src/common/cptv_frame.rs index a4b535b..23d43af 100644 --- a/cptv-codec-rs/src/common/cptv_frame.rs +++ b/cptv-codec-rs/src/common/cptv_frame.rs @@ -11,7 +11,7 @@ use core::time::Duration; use crate::common::cptv_field_type::FieldType; use crate::common::{HEIGHT, WIDTH}; -use byteorder::{ByteOrder, LittleEndian}; +use byteorder::{BigEndian, ByteOrder, LittleEndian}; use core::fmt::Debug; use core::ops::{Index, IndexMut}; use log::warn; @@ -70,20 +70,13 @@ impl<'a> Iterator for BitUnpacker<'a> { Some(out) } } -fn u8_slice_as_u16_slice(p: &[u8]) -> &[u16] { - assert_eq!( - (p.len() / 2) * 2, - p.len(), - "slice must be evenly divisible by 2" - ); - unsafe { core::slice::from_raw_parts((p as *const [u8]) as *const u16, p.len() / 2) } -} fn unpack_frame_v2( prev_frame: &CptvFrame, data: &[u8], bit_width: u8, snake_sequence: &[usize], + is_tc2: bool ) -> FrameData { let initial_px = LittleEndian::read_i32(&data[0..4]); let num_remaining_px = (WIDTH * HEIGHT) - 1; @@ -92,14 +85,30 @@ fn unpack_frame_v2( let mut current_px = initial_px; // Seed the initial pixel value let prev_px = prev_frame.image_data[0][0] as i32; - assert!(prev_px + current_px <= u16::MAX as i32); - assert!(prev_px + current_px >= 0); + debug_assert!(prev_px + current_px <= u16::MAX as i32); + debug_assert!(prev_px + current_px >= 0); let mut image_data = FrameData::new(); image_data[0][0] = (prev_px + current_px) as u16; - if bit_width == 8 { + if bit_width == 16 { + debug_assert_eq!(frame_size % 2, 0, "Frame size should be multiple of 2"); + let unpack_u16 = if is_tc2 { |chunk| LittleEndian::read_u16(chunk) } else { |chunk| BigEndian::read_u16(chunk) }; + for (&index, delta) in snake_sequence + .iter() + .zip(i.chunks(2).map(unpack_u16).take(num_remaining_px)) + { + current_px += (delta as i16) as i32; + let prev_px = unsafe { *prev_frame.image_data.data.get_unchecked(index) } as i32; + debug_assert!(prev_px + current_px <= u16::MAX as i32, "prev_px {}, current_px {}", prev_px, current_px); + debug_assert!(prev_px + current_px >= 0, "prev_px {}, current_px {}", prev_px, current_px); + let px = (prev_px + current_px) as u16; + *unsafe { image_data.data.get_unchecked_mut(index) } = px; + } + } else if bit_width == 8 { for (&index, delta) in snake_sequence.iter().zip(i.iter().take(num_remaining_px)) { current_px += (*delta as i8) as i32; let prev_px = unsafe { *prev_frame.image_data.data.get_unchecked(index) } as i32; + debug_assert!(prev_px + current_px <= u16::MAX as i32, "prev_px {}, current_px {}", prev_px, current_px); + debug_assert!(prev_px + current_px >= 0, "prev_px {}, current_px {}", prev_px, current_px); let px = (prev_px + current_px) as u16; *unsafe { image_data.data.get_unchecked_mut(index) } = px; } @@ -110,6 +119,8 @@ fn unpack_frame_v2( { current_px += delta; let prev_px = unsafe { *prev_frame.image_data.data.get_unchecked(index) } as i32; + debug_assert!(prev_px + current_px <= u16::MAX as i32, "prev_px {}, current_px {}", prev_px, current_px); + debug_assert!(prev_px + current_px >= 0, "prev_px {}, current_px {}", prev_px, current_px); let px = (prev_px + current_px) as u16; *unsafe { image_data.data.get_unchecked_mut(index) } = px; } @@ -166,6 +177,7 @@ impl CptvFrame { data: &'a [u8], prev_frame: &Option, sequence: &[usize], + is_tc2: bool ) -> nom::IResult<&'a [u8], CptvFrame, (&'a [u8], nom::error::ErrorKind)> { let (i, val) = take(1usize)(data)?; let (_, _) = char('F')(val)?; @@ -234,7 +246,7 @@ impl CptvFrame { &empty_frame } }; - let image_data = unpack_frame_v2(prev_frame, data, bit_width, sequence); + let image_data = unpack_frame_v2(prev_frame, data, bit_width, sequence, is_tc2); Ok(( i, CptvFrame { diff --git a/cptv-codec-rs/src/common/cptv_header.rs b/cptv-codec-rs/src/common/cptv_header.rs index 9d725b5..23685df 100644 --- a/cptv-codec-rs/src/common/cptv_header.rs +++ b/cptv-codec-rs/src/common/cptv_header.rs @@ -1,7 +1,7 @@ use crate::common::cptv_field_type::FieldType; use crate::common::{HEIGHT, WIDTH}; use alloc::string::String; -use core::fmt::{Debug, Formatter}; +use core::fmt::{Debug, Display, Formatter}; use log::warn; use nom::bytes::streaming::{tag, take}; use nom::character::streaming::char; @@ -22,6 +22,12 @@ impl Debug for CptvString { } } +impl Display for CptvString { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "\"{:?}\"", self.inner) + } +} + impl Into for CptvString { fn into(self) -> String { self.inner diff --git a/cptv-codec-rs/src/decode/mod.rs b/cptv-codec-rs/src/decode/mod.rs index db7ba1b..64c0b57 100644 --- a/cptv-codec-rs/src/decode/mod.rs +++ b/cptv-codec-rs/src/decode/mod.rs @@ -9,6 +9,7 @@ use std::path::Path; pub use crate::common::cptv_frame::CptvFrame; pub use crate::common::cptv_header::CptvHeader; use crate::common::{HEIGHT, WIDTH}; +use crate::common::cptv_header::CptvString; struct DoubleBuffer { buffer_a: Vec, @@ -169,15 +170,16 @@ impl CptvDecoder { pub fn next_frame(&mut self) -> io::Result<&CptvFrame> { let header = self.get_header(); if !header.is_ok() { - return Err(header.err().unwrap()); + Err(header.err().unwrap()) } else { // Get each frame. The decoder will need to hold onto the previous frame in order // to decode the next. let mut buffer = [0u8; 1024]; // Read 1024 bytes at a time until we can decode the frame. let cptv_frame: CptvFrame; + let is_tc2 = header.expect("should have header").firmware_version.unwrap_or(CptvString::new()).as_string().contains("/"); loop { let initial_len = self.buffer.len(); - match CptvFrame::from_bytes(&self.buffer, &self.prev_frame, &self.sequence) { + match CptvFrame::from_bytes(&self.buffer, &self.prev_frame, &self.sequence, is_tc2) { Ok((remaining, frame)) => { cptv_frame = frame; self.prev_frame = Some(cptv_frame); @@ -254,7 +256,7 @@ impl CptvDecoder { self.buffer.extend_from_slice(&buffer[0..bytes_read]); Ok(()) } else { - return Err(Error::new(ErrorKind::Other, "Reached end of input")); + Err(Error::new(ErrorKind::Other, "Reached end of input")) } } Err(e) => { @@ -263,7 +265,7 @@ impl CptvDecoder { // Let the loop continue and retry Ok(()) } - _ => return Err(e), + _ => Err(e), } } } diff --git a/cptv-codec-rs/src/lib.rs b/cptv-codec-rs/src/lib.rs index e564e92..12e78c2 100644 --- a/cptv-codec-rs/src/lib.rs +++ b/cptv-codec-rs/src/lib.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +extern crate core; pub mod common; pub mod decode; diff --git a/python-bindings/pyproject.toml b/python-bindings/pyproject.toml index 6f291d4..8791003 100644 --- a/python-bindings/pyproject.toml +++ b/python-bindings/pyproject.toml @@ -8,7 +8,7 @@ features = ["pyo3/extension-module"] [project] name = "python-cptv" -version = "0.0.5" +version = "0.0.6" authors = [ { name="Jon Hardie", email="Jon@cacophony.org.nz" }, { name = "Giampaolo Feraro", email = "Giampaolo@Cacophony.org.nz"} diff --git a/wasm-bindings/Cargo.toml b/wasm-bindings/Cargo.toml index 677bddf..ae2da3e 100644 --- a/wasm-bindings/Cargo.toml +++ b/wasm-bindings/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasm-bindings" +name = "cptv-decoder" version = "0.1.0" edition = "2021" diff --git a/wasm-bindings/build.sh b/wasm-bindings/build.sh index e3134e4..2db9ca9 100755 --- a/wasm-bindings/build.sh +++ b/wasm-bindings/build.sh @@ -1,3 +1,3 @@ #!/bin/bash -wasm-pack build . --target nodejs \ No newline at end of file +wasm-pack build . --target web diff --git a/wasm-bindings/examples/node-cptv-decoder.mjs b/wasm-bindings/examples/node-cptv-decoder.mjs index 69b99e8..40981a0 100644 --- a/wasm-bindings/examples/node-cptv-decoder.mjs +++ b/wasm-bindings/examples/node-cptv-decoder.mjs @@ -1,4 +1,4 @@ -import {CptvDecoderContext} from "../pkg/wasm_bindings.js"; +import loadWasm, {CptvDecoderContext} from "../pkg/cptv_decoder.js"; import fs from "fs"; const FakeReader = function (bytes, maxChunkSize = 0) { @@ -44,18 +44,21 @@ const FakeReader = function (bytes, maxChunkSize = 0) { }; (async function main() { - const buffer = fs.readFileSync("../../cptv-codec-rs/tests/fixtures/748923-20201221.cptv"); + await loadWasm(fs.readFileSync("../pkg/cptv_decoder_bg.wasm")); + const buffer = fs.readFileSync("../../cptv-codec-rs/tests/fixtures/20240917-1921337.cptv"); const reader = new FakeReader(buffer, 100000); const start = performance.now(); // TODO: Handle stream cancellation const decoderContext = CptvDecoderContext.newWithReadableStream(reader); - const _header = await decoderContext.getHeader(); + const header = await decoderContext.getHeader(); let frame; let num = 0; - while (frame = await decoderContext.nextFrameOwned() && frame !== null) { + while ((frame = await decoderContext.nextFrameOwned())) { + console.log(frame); num++; } + console.log(header); console.log(performance.now() - start); console.log(num); // TODO: Should header be filled with minValue, maxValue, totalFrames if it doesn't have those fields? -}()); \ No newline at end of file +}()); diff --git a/wasm-bindings/src/lib.rs b/wasm-bindings/src/lib.rs index 8ed7bd7..0647332 100644 --- a/wasm-bindings/src/lib.rs +++ b/wasm-bindings/src/lib.rs @@ -39,7 +39,7 @@ impl WebReader { return Ok(bytes_read as usize); } } - return Ok(0); + Ok(0) } Err(_e) => Err(io::Error::new(ErrorKind::UnexpectedEof, "Stream error")), }