├── .gitignore ├── Cargo.toml ├── README.md └── src ├── bitstream.rs ├── codec.rs ├── frame.rs ├── lib.rs └── testdata ├── tears_of_steel_12130.tif └── tears_of_steel_12209.tif /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello-video-codec" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | thiserror = "1.0.25" 8 | tiff = "0.7.0" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello, Video Codec! 2 | 3 | This is a "hello, world" video codec. See our [blog post](https://blog.tempus-ex.com/hello-video-codec/) for details. 4 | -------------------------------------------------------------------------------- /src/bitstream.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Bytes, Error, ErrorKind, Read, Result, Write}; 2 | 3 | pub struct Bitstream { 4 | inner: Bytes, 5 | next_bits: u128, 6 | next_bits_length: usize, 7 | } 8 | 9 | impl Bitstream { 10 | pub fn new(inner: T) -> Self { 11 | Self { 12 | inner: inner.bytes(), 13 | next_bits: 0, 14 | next_bits_length: 0, 15 | } 16 | } 17 | 18 | pub fn next_bits(&mut self, n: usize) -> Result { 19 | while self.next_bits_length < n { 20 | let b = match self.inner.next().transpose()? { 21 | Some(b) => b as u128, 22 | None => { 23 | return Err(Error::new( 24 | ErrorKind::UnexpectedEof, 25 | "unexpected end of bitstream", 26 | )) 27 | } 28 | }; 29 | self.next_bits = (self.next_bits << 8) | b; 30 | self.next_bits_length += 8; 31 | } 32 | Ok( 33 | ((self.next_bits >> (self.next_bits_length - n)) & (0xffff_ffff_ffff_ffff >> (64 - n))) 34 | as u64, 35 | ) 36 | } 37 | 38 | pub fn read_bits(&mut self, n: usize) -> Result { 39 | let ret = self.next_bits(n)?; 40 | self.next_bits_length -= n; 41 | Ok(ret) 42 | } 43 | } 44 | 45 | pub struct BitstreamWriter { 46 | inner: T, 47 | next_bits: u128, 48 | next_bits_length: usize, 49 | } 50 | 51 | impl BitstreamWriter { 52 | pub fn new(inner: T) -> Self { 53 | Self { 54 | inner, 55 | next_bits: 0, 56 | next_bits_length: 0, 57 | } 58 | } 59 | 60 | // Writes the given bits to the bitstream. If an error occurs, it is undefined how many bits 61 | // were actually written to the underlying bitstream. 62 | pub fn write_bits(&mut self, bits: u64, mut len: usize) -> Result<()> { 63 | while len >= 128 { 64 | self.write_bits(0, 64)?; 65 | len -= 64; 66 | } 67 | if len > 64 { 68 | self.write_bits(0, len - 64)?; 69 | len = 64; 70 | } 71 | self.next_bits = (self.next_bits << len) | bits as u128; 72 | self.next_bits_length += len; 73 | while self.next_bits_length >= 8 { 74 | let next_byte = (self.next_bits >> (self.next_bits_length - 8)) as u8; 75 | self.inner.write_all(&[next_byte])?; 76 | self.next_bits_length -= 8; 77 | } 78 | Ok(()) 79 | } 80 | 81 | // Writes the remaining bits to the underlying writer if there are any, and flushes it. If the 82 | // bitstream is not byte-aligned, zero-bits will be appended until it is. 83 | pub fn flush(&mut self) -> Result<()> { 84 | if self.next_bits_length > 0 { 85 | let next_byte = (self.next_bits << (8 - self.next_bits_length)) as u8; 86 | self.inner.write_all(&[next_byte])?; 87 | self.next_bits_length = 0; 88 | } 89 | self.inner.flush() 90 | } 91 | } 92 | 93 | impl Drop for BitstreamWriter { 94 | fn drop(&mut self) { 95 | // if users need the error, they should explicitly invoke flush before dropping 96 | let _ = self.flush(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/codec.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | bitstream::{Bitstream, BitstreamWriter}, 3 | frame::{self, Plane}, 4 | }; 5 | use std::io::{Read, Result, Write}; 6 | 7 | pub struct Codec; 8 | 9 | pub fn fixed_prediction(a: u16, b: u16, c: u16) -> i32 { 10 | let min_a_b = a.min(b); 11 | let max_a_b = a.max(b); 12 | if c >= max_a_b { 13 | min_a_b as _ 14 | } else if c <= min_a_b { 15 | max_a_b as _ 16 | } else { 17 | a as i32 + b as i32 - c as i32 18 | } 19 | } 20 | 21 | pub fn encode_value(k: u32, x: i32, dest: &mut BitstreamWriter) -> Result<()> { 22 | let x = ((x >> 30) ^ (2 * x)) as u32; 23 | let high_bits = x >> k; 24 | dest.write_bits(1, (high_bits + 1) as _)?; 25 | dest.write_bits((x & ((1 << k) - 1)) as _, k as _)?; 26 | Ok(()) 27 | } 28 | 29 | pub fn decode_value(k: u32, source: &mut Bitstream) -> Result { 30 | let mut high_bits = 0; 31 | while source.read_bits(1)? == 0 { 32 | high_bits += 1; 33 | } 34 | let x = (high_bits << k) | source.read_bits(k as _)? as u32; 35 | Ok((x as i32 >> 1) ^ ((x << 31) as i32 >> 31)) 36 | } 37 | 38 | pub fn k(a: u16, b: u16, c: u16, d: u16) -> u32 { 39 | let activity_level = 40 | (d as i32 - b as i32).abs() + (b as i32 - c as i32).abs() + (c as i32 - a as i32).abs(); 41 | let mut k = 0; 42 | while (3 << k) < activity_level { 43 | k += 1; 44 | } 45 | k 46 | } 47 | 48 | impl frame::Codec for Codec { 49 | fn encode, W: Write>(plane: &Plane, dest: W) -> Result<()> { 50 | let mut bitstream = BitstreamWriter::new(dest); 51 | let data = plane.data.as_ref(); 52 | 53 | let mut b = 0; 54 | for row in 0..plane.height { 55 | let mut a = 0; 56 | let mut c = 0; 57 | for col in 0..plane.width { 58 | let x = data[row * plane.row_stride + col * plane.sample_stride]; 59 | let d = if row > 0 && col + 1 < plane.width { 60 | data[(row - 1) * plane.row_stride + (col + 1) * plane.sample_stride] 61 | } else { 62 | 0 63 | }; 64 | 65 | let prediction = fixed_prediction(a, b, c); 66 | let prediction_residual = x as i32 - prediction; 67 | 68 | encode_value(k(a, b, c, d), prediction_residual, &mut bitstream)?; 69 | 70 | c = b; 71 | b = d; 72 | a = x; 73 | } 74 | b = data[row * plane.row_stride]; 75 | } 76 | 77 | bitstream.flush() 78 | } 79 | 80 | fn decode, R: Read>(source: R, plane: &mut Plane) -> Result<()> { 81 | let mut bitstream = Bitstream::new(source); 82 | let data = plane.data.as_mut(); 83 | 84 | let mut b = 0; 85 | for row in 0..plane.height { 86 | let mut a = 0; 87 | let mut c = 0; 88 | for col in 0..plane.width { 89 | let d = if row > 0 && col + 1 < plane.width { 90 | data[(row - 1) * plane.row_stride + (col + 1) * plane.sample_stride] 91 | } else { 92 | 0 93 | }; 94 | 95 | let prediction = fixed_prediction(a, b, c); 96 | let prediction_residual = decode_value(k(a, b, c, d), &mut bitstream)?; 97 | 98 | let x = (prediction + prediction_residual) as u16; 99 | data[row * plane.row_stride + col * plane.sample_stride] = x; 100 | 101 | c = b; 102 | b = d; 103 | a = x; 104 | } 105 | b = data[row * plane.row_stride]; 106 | } 107 | 108 | Ok(()) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::{super::frame::RGB48Frame, *}; 115 | 116 | #[test] 117 | fn test_encode_decode_value() { 118 | let mut buf = Vec::new(); 119 | for k in 0..10 { 120 | for &x in [-38368, -10, -1, 0, 1, 2, 3, 4, 5, 6, 38368, 38369].iter() { 121 | buf.clear(); 122 | { 123 | let mut dest = BitstreamWriter::new(&mut buf); 124 | encode_value(k, x, &mut dest).unwrap(); 125 | dest.flush().unwrap(); 126 | } 127 | let mut bitstream = Bitstream::new(&*buf); 128 | let decoded = decode_value(k, &mut bitstream).unwrap(); 129 | assert_eq!( 130 | x, decoded, 131 | "k = {}, x = {}, roundtripped = {}", 132 | k, x, decoded 133 | ); 134 | } 135 | } 136 | } 137 | 138 | #[test] 139 | fn test_codec_12131() { 140 | let frame = RGB48Frame::open("src/testdata/tears_of_steel_12130.tif").unwrap(); 141 | assert_eq!(frame.data.len(), 4096 * 1714 * 3); // 42,123,264 bytes uncompressed 142 | 143 | let mut encoded = Vec::new(); 144 | frame.encode::(&mut encoded).unwrap(); 145 | assert_eq!(encoded.len(), 25526583); 146 | 147 | let decoded = RGB48Frame::decode::(&*encoded, frame.width, frame.height).unwrap(); 148 | assert_eq!(frame == decoded, true); 149 | } 150 | 151 | #[test] 152 | fn test_codec_12209() { 153 | let frame = RGB48Frame::open("src/testdata/tears_of_steel_12209.tif").unwrap(); 154 | assert_eq!(frame.data.len(), 4096 * 1714 * 3); // 42,123,264 bytes uncompressed 155 | 156 | let mut encoded = Vec::new(); 157 | frame.encode::(&mut encoded).unwrap(); 158 | assert_eq!(encoded.len(), 28270586); 159 | 160 | let decoded = RGB48Frame::decode::(&*encoded, frame.width, frame.height).unwrap(); 161 | assert_eq!(frame == decoded, true); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/frame.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{self, Read, Write}, 3 | path::Path, 4 | }; 5 | use thiserror::Error; 6 | 7 | pub struct Plane { 8 | pub data: T, 9 | pub width: usize, 10 | pub height: usize, 11 | pub sample_stride: usize, 12 | pub row_stride: usize, 13 | } 14 | 15 | impl> Plane { 16 | pub fn sample(&self, col: usize, row: usize) -> u16 { 17 | self.data.as_ref()[row * self.row_stride + col * self.sample_stride] 18 | } 19 | } 20 | 21 | pub trait Codec { 22 | fn encode, W: Write>(plane: &Plane, dest: W) -> io::Result<()>; 23 | fn decode, R: Read>(source: R, plane: &mut Plane) -> io::Result<()>; 24 | } 25 | 26 | #[derive(Error, Debug)] 27 | pub enum FrameOpenError { 28 | #[error(transparent)] 29 | IO(#[from] io::Error), 30 | #[error(transparent)] 31 | TiffError(#[from] tiff::TiffError), 32 | #[error("unsupported color type: {0:?}")] 33 | UnsupportedColorType(tiff::ColorType), 34 | #[error("unsupported sample type")] 35 | UnsupportedSampleType, 36 | } 37 | 38 | #[derive(PartialEq)] 39 | pub struct RGB48Frame { 40 | pub data: Vec, 41 | pub width: usize, 42 | pub height: usize, 43 | } 44 | 45 | impl RGB48Frame { 46 | pub fn open>(path: P) -> Result { 47 | let f = std::fs::File::open(path)?; 48 | let mut dec = 49 | tiff::decoder::Decoder::new(f)?.with_limits(tiff::decoder::Limits::unlimited()); 50 | let (width, height) = dec.dimensions()?; 51 | 52 | Ok(match dec.colortype()? { 53 | tiff::ColorType::RGB(16) => match dec.read_image()? { 54 | tiff::decoder::DecodingResult::U16(data) => RGB48Frame { 55 | data, 56 | width: width as _, 57 | height: height as _, 58 | }, 59 | _ => return Err(FrameOpenError::UnsupportedSampleType), 60 | }, 61 | color_type => return Err(FrameOpenError::UnsupportedColorType(color_type)), 62 | }) 63 | } 64 | 65 | pub fn planes(&self) -> Vec> { 66 | vec![ 67 | Plane { 68 | data: &self.data, 69 | width: self.width, 70 | height: self.height, 71 | row_stride: 3 * self.width, 72 | sample_stride: 3, 73 | }, 74 | Plane { 75 | data: &self.data[1..], 76 | width: self.width, 77 | height: self.height, 78 | row_stride: 3 * self.width, 79 | sample_stride: 3, 80 | }, 81 | Plane { 82 | data: &self.data[2..], 83 | width: self.width, 84 | height: self.height, 85 | row_stride: 3 * self.width, 86 | sample_stride: 3, 87 | }, 88 | ] 89 | } 90 | 91 | pub fn encode(&self, mut dest: W) -> io::Result<()> { 92 | for plane in self.planes() { 93 | C::encode(&plane, &mut dest)?; 94 | } 95 | Ok(()) 96 | } 97 | 98 | pub fn decode( 99 | mut source: R, 100 | width: usize, 101 | height: usize, 102 | ) -> io::Result { 103 | let mut ret = Self { 104 | data: vec![0; width * height * 3], 105 | width, 106 | height, 107 | }; 108 | for plane in 0..3 { 109 | C::decode( 110 | &mut source, 111 | &mut Plane { 112 | data: &mut ret.data[plane..], 113 | width: width, 114 | height: height, 115 | row_stride: 3 * width, 116 | sample_stride: 3, 117 | }, 118 | )?; 119 | } 120 | Ok(ret) 121 | } 122 | } 123 | 124 | #[cfg(test)] 125 | mod tests { 126 | use super::*; 127 | 128 | #[test] 129 | fn test_rgb48_frame_open() { 130 | RGB48Frame::open("src/testdata/tears_of_steel_12130.tif").unwrap(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bitstream; 2 | pub mod codec; 3 | pub mod frame; 4 | -------------------------------------------------------------------------------- /src/testdata/tears_of_steel_12130.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tempus-ex/hello-video-codec/3e9551c699311ea12ad7f2fce9562fbc990d524c/src/testdata/tears_of_steel_12130.tif -------------------------------------------------------------------------------- /src/testdata/tears_of_steel_12209.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tempus-ex/hello-video-codec/3e9551c699311ea12ad7f2fce9562fbc990d524c/src/testdata/tears_of_steel_12209.tif --------------------------------------------------------------------------------