Skip to content

Commit

Permalink
Merge pull request #4 from TheCacophonyProject/fix-end-of-stream
Browse files Browse the repository at this point in the history
- Turns out that low power decoder packs bytes in little endian order as per spec, w…
  • Loading branch information
gferraro authored Oct 2, 2024
2 parents e808b73 + d2362ed commit 786094e
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 46 deletions.
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 25 additions & 13 deletions cptv-codec-rs/src/common/cptv_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -166,6 +177,7 @@ impl CptvFrame {
data: &'a [u8],
prev_frame: &Option<CptvFrame>,
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)?;
Expand Down Expand Up @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion cptv-codec-rs/src/common/cptv_header.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String> for CptvString {
fn into(self) -> String {
self.inner
Expand Down
10 changes: 6 additions & 4 deletions cptv-codec-rs/src/decode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
Expand Down Expand Up @@ -169,15 +170,16 @@ impl<R: Read> CptvDecoder<R> {
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);
Expand Down Expand Up @@ -254,7 +256,7 @@ impl<R: Read> CptvDecoder<R> {
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) => {
Expand All @@ -263,7 +265,7 @@ impl<R: Read> CptvDecoder<R> {
// Let the loop continue and retry
Ok(())
}
_ => return Err(e),
_ => Err(e),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions cptv-codec-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
extern crate core;

pub mod common;
pub mod decode;
Expand Down
2 changes: 1 addition & 1 deletion python-bindings/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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="[email protected]" },
{ name = "Giampaolo Feraro", email = "[email protected]"}
Expand Down
2 changes: 1 addition & 1 deletion wasm-bindings/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "wasm-bindings"
name = "cptv-decoder"
version = "0.1.0"
edition = "2021"

Expand Down
2 changes: 1 addition & 1 deletion wasm-bindings/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

wasm-pack build . --target nodejs
wasm-pack build . --target web
13 changes: 8 additions & 5 deletions wasm-bindings/examples/node-cptv-decoder.mjs
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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?
}());
}());
2 changes: 1 addition & 1 deletion wasm-bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
}
Expand Down

0 comments on commit 786094e

Please sign in to comment.