Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

h265 add docs and tests #371

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

8 changes: 7 additions & 1 deletion crates/h265/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,22 @@ name = "scuffle-h265"
version = "0.1.1"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "A pure Rust H.265 header decoder."
repository = "https://github.com/scufflecloud/scuffle"
authors = ["Scuffle <opensource@scuffle.cloud>"]
readme = "README.md"
documentation = "https://docs.rs/scuffle-h265"
description = "A pure Rust H.265 header decoder."
keywords = ["h265", "video", "codec"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] }

[dependencies]
bytes = "1.5"
byteorder = "1.5"
scuffle-expgolomb.workspace = true
scuffle-bytes-util.workspace = true
scuffle-workspace-hack.workspace = true

[dev-dependencies]
insta = "1.42"
163 changes: 161 additions & 2 deletions crates/h265/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,115 @@ use scuffle_bytes_util::{BitReader, BitWriter};
/// HEVC Decoder Configuration Record
/// ISO/IEC 14496-15:2022(E) - 8.3.2.1
pub struct HEVCDecoderConfigurationRecord {
/// The `configuration_version` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub configuration_version: u8,

/// The `general_profile_space` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub general_profile_space: u8,

/// The `general_tier_flag` as a bool. Matches the field as defined in ISO/IEC 23008-2.
pub general_tier_flag: bool,

/// The `general_profile_idc` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub general_profile_idc: u8,

/// The `general_profile_compatibility_flags` as a u32. Matches the field as defined in ISO/IEC 23008-2.
pub general_profile_compatibility_flags: u32,

/// The `general_constraint_indicator_flags` as a u64. Matches the field as defined in ISO/IEC 23008-2.
pub general_constraint_indicator_flags: u64,

/// The `general_level_idc` as a u32. Matches the field as defined in ISO/IEC 23008-2.
pub general_level_idc: u8,

/// The `min_spatial_segmentation_idc` as a u16. Matches the field as defined in ISO/IEC 23008-2.
pub min_spatial_segmentation_idc: u16,
pub parallelism_type: u8,

/// The `chroma_format_idc` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub chroma_format_idc: u8,

/// The `bit_depth_luma_minus8` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub bit_depth_luma_minus8: u8,

/// The `bit_depth_chroma_minus8` as a u8. Matches the field as defined in ISO/IEC 23008-2.
pub bit_depth_chroma_minus8: u8,

// TODO: nutype enum
/// The `parallelism_type` as a u8.
///
/// 0 means the stream supports mixed types of parallel decoding or otherwise.
///
/// 1 means the stream supports slice based parallel decoding.
///
/// 2 means the stream supports tile based parallel decoding.
///
/// 3 means the stream supports entropy coding sync based parallel decoding.
pub parallelism_type: u8,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should create a nutype-enum for this field which has the fields u are talking about but also supports unknowns.


// definitely shouldn't be a u16. prolly f64
/// The `avg_frame_rate` as a u16.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this field work for non-integer framerates

pub avg_frame_rate: u16,

/// The `constant_frame_rate` as a u8.
///
/// 0 means the stream might have a constant frame rate.
///
/// 1 means the stream has a constant framerate.
///
/// 2 means the representation of each temporal layer in the stream has a constant framerate.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise with the nutype-enum

pub constant_frame_rate: u8,

// make this a nutype enum
/// The `num_temporal_layers` as a u8. This is the count of tepmoral layers or `sub-layer`s as defined in ISO/IEC 23008-2.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should make nutype enums

///
/// 0 means the stream might be temporally scalable.
///
/// 1 means the stream is NOT temporally scalable.
///
/// 2 or more means the stream is temporally scalable, and the count of temporal layers is equal to this value.
pub num_temporal_layers: u8,

/// The `temporal_id_nested` as a bool.
///
/// 0 means means the opposite might not be true (refer to what 1 means for this flag).
///
/// 1 means all the activated SPS have `sps_temporal_id_nesting_flag` (as defined in ISC/IEC 23008-2) set to 1 and that temporal sub-layer up-switching to a higehr temporal layer can be done at any sample.
pub temporal_id_nested: bool,

/// The `length_size_minus_one` is the u8 length of the NALUnitLength minus one.
pub length_size_minus_one: u8,

/// The `arrays` is a vec of NaluArray.
/// Refer to the NaluArray struct in the NaluArray docs for more info.
pub arrays: Vec<NaluArray>,
}

// turn into nutype enum
#[derive(Debug, Clone, PartialEq)]
/// Nalu Array Structure
/// ISO/IEC 14496-15:2022(E) - 8.3.2.1
pub struct NaluArray {
/// The `array_completeness` is a flag set to 1 when all NAL units are in the array and none are in the stream. It is set to 0 if otherwise.
pub array_completeness: bool,
/// The `nal_unit_type` is the type of the NAL units in the `nalus` vec, as defined in ISO/IEC 23008-2.
/// Refer to the `NaluType` enum for more info.
pub nal_unit_type: NaluType,
/// `nalus` is a vec of NAL units. Each of these will contain either a VPS, PPS, SPS, or an unknown u8 as specified in ISO/IEC 23008-2.
/// Refer to the `NaluType` enum for more info.
pub nalus: Vec<Bytes>,
}

#[derive(Debug, Clone, PartialEq, Copy)]
/// Nalu Type
/// The Nalu Type.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a nutype enum

/// ISO/IEC 23008-2:2020(E) - 7.4.2.2 (Table 7-1)
pub enum NaluType {
/// The Video Parameter Set.
Vps,
/// The Picture Parameter Set.
Pps,
/// The Sequence Parameter Set.
Sps,
/// An unknown u8. This is the default value if the NaluType is set to something other than VPS, PPS, or SPS.
Unknown(u8),
}

Expand All @@ -72,6 +145,8 @@ impl From<NaluType> for u8 {
}

impl HEVCDecoderConfigurationRecord {
/// Demuxes an HEVCDecoderConfigurationRecord from a byte stream.
/// Returns a demuxed HEVCDecoderConfigurationRecord.
pub fn demux(data: &mut io::Cursor<Bytes>) -> io::Result<Self> {
let mut bit_reader = BitReader::new(data);

Expand Down Expand Up @@ -154,6 +229,7 @@ impl HEVCDecoderConfigurationRecord {
})
}

/// Returns the total byte size of the HEVCDecoderConfigurationRecord.
pub fn size(&self) -> u64 {
1 // configuration_version
+ 1 // general_profile_space, general_tier_flag, general_profile_idc
Expand All @@ -178,6 +254,8 @@ impl HEVCDecoderConfigurationRecord {
}).sum::<u64>()
}

/// Muxes the HEVCDecoderConfigurationRecord into a byte stream.
/// Returns a muxed byte stream.
pub fn mux<T: io::Write>(&self, writer: &mut T) -> io::Result<()> {
let mut bit_writer = BitWriter::new(writer);

Expand Down Expand Up @@ -230,3 +308,84 @@ impl HEVCDecoderConfigurationRecord {
Ok(())
}
}

#[cfg(test)]
#[cfg_attr(all(test, coverage_nightly), coverage(off))]
mod tests {
use std::io;

use bytes::Bytes;

use crate::{ColorConfig, HEVCDecoderConfigurationRecord, NaluType, Sps};

#[test]
fn test_config_demux() {
// h265 config
let data = Bytes::from(b"\x01\x01@\0\0\0\x90\0\0\0\0\0\x99\xf0\0\xfc\xfd\xf8\xf8\0\0\x0f\x03 \0\x01\0\x18@\x01\x0c\x01\xff\xff\x01@\0\0\x03\0\x90\0\0\x03\0\0\x03\0\x99\x95@\x90!\0\x01\0=B\x01\x01\x01@\0\0\x03\0\x90\0\0\x03\0\0\x03\0\x99\xa0\x01@ \x05\xa1e\x95R\x90\x84d_\xf8\xc0Z\x80\x80\x80\x82\0\0\x03\0\x02\0\0\x03\x01 \xc0\x0b\xbc\xa2\0\x02bX\0\x011-\x08\"\0\x01\0\x07D\x01\xc0\x93|\x0c\xc9".to_vec());

let config = HEVCDecoderConfigurationRecord::demux(&mut io::Cursor::new(data)).unwrap();

assert_eq!(config.configuration_version, 1);
assert_eq!(config.general_profile_space, 0);
assert!(!config.general_tier_flag);
assert_eq!(config.general_profile_idc, 1);
assert_eq!(config.general_profile_compatibility_flags, 64);
assert_eq!(config.general_constraint_indicator_flags, 144);
assert_eq!(config.general_level_idc, 153);
assert_eq!(config.min_spatial_segmentation_idc, 0);
assert_eq!(config.parallelism_type, 0);
assert_eq!(config.chroma_format_idc, 1);
assert_eq!(config.bit_depth_luma_minus8, 0);
assert_eq!(config.bit_depth_chroma_minus8, 0);
assert_eq!(config.avg_frame_rate, 0);
assert_eq!(config.constant_frame_rate, 0);
assert_eq!(config.num_temporal_layers, 1);
assert!(config.temporal_id_nested);
assert_eq!(config.length_size_minus_one, 3);
assert_eq!(config.arrays.len(), 3);

let vps = &config.arrays[0];
assert!(!vps.array_completeness);
assert_eq!(vps.nal_unit_type, NaluType::Vps);
assert_eq!(vps.nalus.len(), 1);

let sps = &config.arrays[1];
assert!(!sps.array_completeness);
assert_eq!(sps.nal_unit_type, NaluType::Sps);
assert_eq!(sps.nalus.len(), 1);
let sps = Sps::parse(sps.nalus[0].clone()).unwrap();
assert_eq!(
sps,
Sps {
color_config: Some(ColorConfig {
full_range: false,
color_primaries: 1,
matrix_coefficients: 1,
transfer_characteristics: 1,
}),
frame_rate: 144.0,
width: 2560,
height: 1440,
}
);

let pps = &config.arrays[2];
assert!(!pps.array_completeness);
assert_eq!(pps.nal_unit_type, NaluType::Pps);
assert_eq!(pps.nalus.len(), 1);
}

#[test]
fn test_config_mux() {
let data = Bytes::from(b"\x01\x01@\0\0\0\x90\0\0\0\0\0\x99\xf0\0\xfc\xfd\xf8\xf8\0\0\x0f\x03 \0\x01\0\x18@\x01\x0c\x01\xff\xff\x01@\0\0\x03\0\x90\0\0\x03\0\0\x03\0\x99\x95@\x90!\0\x01\0=B\x01\x01\x01@\0\0\x03\0\x90\0\0\x03\0\0\x03\0\x99\xa0\x01@ \x05\xa1e\x95R\x90\x84d_\xf8\xc0Z\x80\x80\x80\x82\0\0\x03\0\x02\0\0\x03\x01 \xc0\x0b\xbc\xa2\0\x02bX\0\x011-\x08\"\0\x01\0\x07D\x01\xc0\x93|\x0c\xc9".to_vec());

let config = HEVCDecoderConfigurationRecord::demux(&mut io::Cursor::new(data.clone())).unwrap();

assert_eq!(config.size(), data.len() as u64);

let mut buf = Vec::new();
config.mux(&mut buf).unwrap();

assert_eq!(buf, data.to_vec());
}
}
40 changes: 37 additions & 3 deletions crates/h265/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
//! A pure Rust implementation of the H.265 encoder and decoder.
//!
//! This crate is designed to provide a simple and safe interface to encode and decode H.265 headers.
//!
//! ## Why do we need this?
//!
//! This crate aims to provides a simple and safe interface for h265.
//!
//! ## How is this different from other h265 crates?
//!
//! The other main h265 crate is TODO.
//!
//! ## Notable features
//!
//! This crate is a completely safe implementation of H265 encoding and decoding, which means there is no unsafe code!
//!
//! ## Examples
//!
//! TODO
//!
//! ## Status
//!
//! This crate is currently under development and is not yet stable.
//!
//! Unit tests are not yet fully implemented. Use at your own risk.
//!
//! ## License
//!
//! This project is licensed under the [MIT](./LICENSE.MIT) or [Apache-2.0](./LICENSE.Apache-2.0) license.
//! You can choose between one of them if you use this work.
//!
//! `SPDX-License-Identifier: MIT OR Apache-2.0`
#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]
#![deny(unsafe_code)]

mod config;
mod sps;

pub use self::config::{HEVCDecoderConfigurationRecord, NaluArray, NaluType};
pub use self::sps::{ColorConfig, Sps};

#[cfg(test)]
mod tests;
Loading
Loading