mirror of
https://github.com/CCExtractor/ccextractor.git
synced 2026-02-04 05:44:53 +00:00
[FEAT] Add bitstream module in lib_ccxr (#1649)
* feat: Add bitstream module * run code formatters * Run cargo clippy --fix * Run cargo fmt --all * refactor: remove rust pointer from C struct * feat: Add bitstream module * run code formatters * Run cargo clippy --fix * Run cargo fmt --all * refactor: remove rust pointer from C struct * Added Bitstream to libccxr_exports * Minor Formatting Issue * Bitstream: Removed redundant CType * bitstream: recommended changes for is_byte_aligned * bitstream: recommended changes for long comments * bitstream: comment fix * bitstream: removed redundant comparism comments --------- Co-authored-by: Deepnarayan Sett <depnra1@gmail.com> Co-authored-by: Deepnarayan Sett <71217129+steel-bucket@users.noreply.github.com>
This commit is contained in:
@@ -3,6 +3,21 @@
|
||||
// Hold functions to read streams on a bit or byte oriented basis
|
||||
// plus some data related helper functions.
|
||||
|
||||
#ifndef DISABLE_RUST
|
||||
extern uint64_t ccxr_next_bits(struct bitstream *bs, uint32_t bnum);
|
||||
extern uint64_t ccxr_read_bits(struct bitstream *bs, uint32_t bnum);
|
||||
extern int ccxr_skip_bits(struct bitstream *bs, uint32_t bnum);
|
||||
extern int ccxr_is_byte_aligned(struct bitstream *bs);
|
||||
extern void ccxr_make_byte_aligned(struct bitstream *bs);
|
||||
extern const uint8_t *ccxr_next_bytes(struct bitstream *bs, size_t bynum);
|
||||
extern const uint8_t *ccxr_read_bytes(struct bitstream *bs, size_t bynum);
|
||||
extern uint64_t ccxr_read_exp_golomb_unsigned(struct bitstream *bs);
|
||||
extern int64_t ccxr_read_exp_golomb(struct bitstream *bs);
|
||||
extern uint8_t ccxr_reverse8(uint8_t data);
|
||||
extern uint64_t ccxr_bitstream_get_num(struct bitstream *bs, unsigned bytes, int advance);
|
||||
extern int64_t ccxr_read_int(struct bitstream *bs, unsigned bnum);
|
||||
#endif
|
||||
|
||||
// Guidelines for all bitsream functions:
|
||||
// * No function shall advance the pointer past the end marker
|
||||
// * If bitstream.bitsleft < 0 do not attempt any read access,
|
||||
@@ -35,6 +50,9 @@ int init_bitstream(struct bitstream *bstr, unsigned char *start, unsigned char *
|
||||
// there are not enough bits left in the bitstream.
|
||||
uint64_t next_bits(struct bitstream *bstr, unsigned bnum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_next_bits(bstr, bnum);
|
||||
#else
|
||||
uint64_t res = 0;
|
||||
|
||||
if (bnum > 64)
|
||||
@@ -99,12 +117,16 @@ uint64_t next_bits(struct bitstream *bstr, unsigned bnum)
|
||||
bstr->_i_pos = vpos;
|
||||
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Read bnum bits from bitstream bstr with the most significant
|
||||
// bit read first. A 64 bit unsigned integer is returned.
|
||||
uint64_t read_bits(struct bitstream *bstr, unsigned bnum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_read_bits(bstr, bnum);
|
||||
#else
|
||||
uint64_t res = next_bits(bstr, bnum);
|
||||
|
||||
// Special case for reading zero bits. Also abort when not enough
|
||||
@@ -117,6 +139,7 @@ uint64_t read_bits(struct bitstream *bstr, unsigned bnum)
|
||||
bstr->pos = bstr->_i_pos;
|
||||
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// This function will advance the bitstream by bnum bits, if possible.
|
||||
@@ -124,6 +147,9 @@ uint64_t read_bits(struct bitstream *bstr, unsigned bnum)
|
||||
// Return TRUE when successful, otherwise FALSE
|
||||
int skip_bits(struct bitstream *bstr, unsigned bnum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_skip_bits(bstr, bnum);
|
||||
#else
|
||||
// Sanity check
|
||||
if (bstr->end - bstr->pos < 0)
|
||||
fatal(CCX_COMMON_EXIT_BUG_BUG, "In skip_bits: bitstream length cannot be negative!");
|
||||
@@ -140,7 +166,7 @@ int skip_bits(struct bitstream *bstr, unsigned bnum)
|
||||
if (bstr->bitsleft < 0)
|
||||
return 0;
|
||||
|
||||
// Special case for reading zero bits. Return zero
|
||||
// Special case for reading zero bits. Return one == success
|
||||
if (bnum == 0)
|
||||
return 1;
|
||||
|
||||
@@ -153,6 +179,7 @@ int skip_bits(struct bitstream *bstr, unsigned bnum)
|
||||
bstr->pos += 1;
|
||||
}
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return TRUE if the current position in the bitstream is on a byte
|
||||
@@ -160,6 +187,9 @@ int skip_bits(struct bitstream *bstr, unsigned bnum)
|
||||
// a byte, otherwise return FALSE
|
||||
int is_byte_aligned(struct bitstream *bstr)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_is_byte_aligned(bstr);
|
||||
#else
|
||||
// Sanity check
|
||||
if (bstr->end - bstr->pos < 0)
|
||||
fatal(CCX_COMMON_EXIT_BUG_BUG, "In is_byte_aligned: bitstream length can not be negative!");
|
||||
@@ -175,11 +205,15 @@ int is_byte_aligned(struct bitstream *bstr)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Move bitstream to next byte border. Adjust bitsleft.
|
||||
void make_byte_aligned(struct bitstream *bstr)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
ccxr_make_byte_aligned(bstr);
|
||||
#else
|
||||
// Sanity check
|
||||
if (bstr->end - bstr->pos < 0)
|
||||
fatal(CCX_COMMON_EXIT_BUG_BUG, "In make_byte_aligned: bitstream length can not be negative!");
|
||||
@@ -208,6 +242,7 @@ void make_byte_aligned(struct bitstream *bstr)
|
||||
bstr->bitsleft = 0LL + 8 * (bstr->end - bstr->pos - 1) + bstr->bpos;
|
||||
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return pointer to first of bynum bytes from the bitstream if the
|
||||
@@ -217,6 +252,9 @@ void make_byte_aligned(struct bitstream *bstr)
|
||||
// This function does not advance the bitstream pointer.
|
||||
unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return (unsigned char *)ccxr_next_bytes(bstr, bynum);
|
||||
#else
|
||||
// Sanity check
|
||||
if (bstr->end - bstr->pos < 0)
|
||||
fatal(CCX_COMMON_EXIT_BUG_BUG, "In next_bytes: bitstream length can not be negative!");
|
||||
@@ -238,6 +276,7 @@ unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
bstr->_i_pos = bstr->pos + bynum;
|
||||
|
||||
return bstr->pos;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return pointer to first of bynum bytes from the bitstream if the
|
||||
@@ -247,6 +286,9 @@ unsigned char *next_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
// This function does advance the bitstream pointer.
|
||||
unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return (unsigned char *)ccxr_read_bytes(bstr, bynum);
|
||||
#else
|
||||
unsigned char *res = next_bytes(bstr, bynum);
|
||||
|
||||
// Advance the bitstream when a read was possible
|
||||
@@ -256,6 +298,7 @@ unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
bstr->pos = bstr->_i_pos;
|
||||
}
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return an integer number with "bytes" precision from the current
|
||||
@@ -266,6 +309,9 @@ unsigned char *read_bytes(struct bitstream *bstr, unsigned bynum)
|
||||
// little-endian and big-endian CPUs.
|
||||
uint64_t bitstream_get_num(struct bitstream *bstr, unsigned bytes, int advance)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_bitstream_get_num(bstr, bytes, advance);
|
||||
#else
|
||||
void *bpos;
|
||||
uint64_t rval = 0;
|
||||
|
||||
@@ -296,11 +342,15 @@ uint64_t bitstream_get_num(struct bitstream *bstr, unsigned bytes, int advance)
|
||||
rval = (rval << 8) + uc;
|
||||
}
|
||||
return rval;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Read unsigned Exp-Golomb code from bitstream
|
||||
uint64_t read_exp_golomb_unsigned(struct bitstream *bstr)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_read_exp_golomb_unsigned(bstr);
|
||||
#else
|
||||
uint64_t res = 0;
|
||||
int zeros = 0;
|
||||
|
||||
@@ -310,11 +360,15 @@ uint64_t read_exp_golomb_unsigned(struct bitstream *bstr)
|
||||
res = (0x01 << zeros) - 1 + read_bits(bstr, zeros);
|
||||
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Read signed Exp-Golomb code from bitstream
|
||||
int64_t read_exp_golomb(struct bitstream *bstr)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_read_exp_golomb(bstr);
|
||||
#else
|
||||
int64_t res = 0;
|
||||
|
||||
res = read_exp_golomb_unsigned(bstr);
|
||||
@@ -325,6 +379,7 @@ int64_t read_exp_golomb(struct bitstream *bstr)
|
||||
res = (res / 2 + (res % 2 ? 1 : 0)) * (res % 2 ? 1 : -1);
|
||||
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Read unsigned integer with bnum bits length. Basically an
|
||||
@@ -337,6 +392,9 @@ uint64_t read_int_unsigned(struct bitstream *bstr, unsigned bnum)
|
||||
// Read signed integer with bnum bits length.
|
||||
int64_t read_int(struct bitstream *bstr, unsigned bnum)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_read_int(bstr, bnum);
|
||||
#else
|
||||
uint64_t res = read_bits(bstr, bnum);
|
||||
|
||||
// Special case for reading zero bits. Return zero
|
||||
@@ -344,11 +402,15 @@ int64_t read_int(struct bitstream *bstr, unsigned bnum)
|
||||
return 0;
|
||||
|
||||
return (0xFFFFFFFFFFFFFFFFULL << bnum) | res;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Return the value with the bit order reversed.
|
||||
uint8_t reverse8(uint8_t data)
|
||||
{
|
||||
#ifndef DISABLE_RUST
|
||||
return ccxr_reverse8(data);
|
||||
#else
|
||||
uint8_t res = 0;
|
||||
|
||||
for (int k = 0; k < 8; k++)
|
||||
@@ -358,4 +420,5 @@ uint8_t reverse8(uint8_t data)
|
||||
}
|
||||
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef _BITSTREAM_
|
||||
#define _BITSTREAM_
|
||||
|
||||
|
||||
// The structure holds the current position in the bitstream.
|
||||
// pos points to the current byte position and bpos counts the
|
||||
// bits left unread at the current byte pos. No bit read means
|
||||
|
||||
@@ -27,6 +27,7 @@ fn main() {
|
||||
".*(?i)_?dtvcc_.*",
|
||||
"encoder_ctx",
|
||||
"lib_cc_decode",
|
||||
"bitstream",
|
||||
"cc_subtitle",
|
||||
"ccx_output_format",
|
||||
"ccx_boundary_time",
|
||||
|
||||
460
src/rust/lib_ccxr/src/common/bitstream.rs
Normal file
460
src/rust/lib_ccxr/src/common/bitstream.rs
Normal file
@@ -0,0 +1,460 @@
|
||||
use crate::fatal;
|
||||
use crate::util::log::ExitCause;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum BitstreamError {
|
||||
#[error("Bitstream has negative length")]
|
||||
NegativeLength,
|
||||
#[error("Illegal bit position value: {0}")]
|
||||
IllegalBitPosition(u8),
|
||||
#[error("Argument is greater than maximum bit number (64): {0}")]
|
||||
TooManyBits(u32),
|
||||
#[error("Data is insufficient for operation")]
|
||||
InsufficientData,
|
||||
}
|
||||
|
||||
pub struct BitStreamRust<'a> {
|
||||
pub data: &'a [u8],
|
||||
pub pos: usize,
|
||||
pub bpos: u8,
|
||||
pub bits_left: i64,
|
||||
pub error: bool,
|
||||
pub _i_pos: usize,
|
||||
pub _i_bpos: u8,
|
||||
}
|
||||
|
||||
impl<'a> BitStreamRust<'a> {
|
||||
/// Create a new bitstream. Empty data is allowed (bits_left = 0).
|
||||
pub fn new(data: &'a [u8]) -> Result<Self, BitstreamError> {
|
||||
if data.is_empty() {
|
||||
return Err(BitstreamError::NegativeLength);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
data,
|
||||
pos: 0,
|
||||
bpos: 8,
|
||||
bits_left: (data.len() as i64) * 8,
|
||||
error: false,
|
||||
_i_pos: 0,
|
||||
_i_bpos: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Peek at next `bnum` bits without advancing. MSB first.
|
||||
pub fn next_bits(&mut self, bnum: u32) -> Result<u64, BitstreamError> {
|
||||
if bnum > 64 {
|
||||
fatal!(cause = ExitCause::Bug; "In next_bits: Argument is greater than the maximum bit number i.e. 64: {}!", bnum);
|
||||
}
|
||||
|
||||
if self.pos > self.data.len() {
|
||||
fatal!(cause = ExitCause::Bug; "In next_bits: Bitstream can not have negative length!");
|
||||
}
|
||||
|
||||
// Keep a negative bitstream.bitsleft, but correct it.
|
||||
if self.bits_left <= 0 {
|
||||
self.bits_left -= bnum as i64;
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Calculate the remaining number of bits in bitstream after reading.
|
||||
self.bits_left =
|
||||
(self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64 - bnum as i64;
|
||||
if self.bits_left < 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Special case for reading zero bits. Return zero
|
||||
if bnum == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut vbit = self.bpos as i32;
|
||||
let mut vpos = self.pos;
|
||||
let mut res = 0u64;
|
||||
let mut remaining_bits = bnum;
|
||||
|
||||
if !(1..=8).contains(&vbit) {
|
||||
fatal!(cause = ExitCause::Bug; "In next_bits: Illegal bit position value {}!", vbit);
|
||||
}
|
||||
|
||||
loop {
|
||||
if vpos >= self.data.len() {
|
||||
// We should not get here ...
|
||||
fatal!(cause = ExitCause::Bug; "In next_bits: Trying to read after end of data ...");
|
||||
}
|
||||
|
||||
res |= if self.data[vpos] & (0x01 << (vbit - 1)) != 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
vbit -= 1;
|
||||
remaining_bits -= 1;
|
||||
|
||||
if vbit == 0 {
|
||||
vpos += 1;
|
||||
vbit = 8;
|
||||
}
|
||||
|
||||
if remaining_bits != 0 {
|
||||
res <<= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remember the bitstream position
|
||||
self._i_bpos = vbit as u8;
|
||||
self._i_pos = vpos;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
/// Read and commit `bnum` bits. On underflow or zero, returns 0.
|
||||
pub fn read_bits(&mut self, bnum: u32) -> Result<u64, BitstreamError> {
|
||||
let res = self.next_bits(bnum)?;
|
||||
|
||||
if bnum == 0 || self.bits_left < 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
self.bpos = self._i_bpos;
|
||||
self.pos = self._i_pos;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
/// Skip `bnum` bits, advancing the position.
|
||||
pub fn skip_bits(&mut self, bnum: u32) -> Result<bool, BitstreamError> {
|
||||
if self.pos > self.data.len() {
|
||||
fatal!(cause = ExitCause::Bug; "In skip_bits: bitstream length cannot be negative!");
|
||||
}
|
||||
|
||||
// Keep a negative bitstream.bitsleft, but correct it.
|
||||
if self.bits_left < 0 {
|
||||
self.bits_left -= bnum as i64;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Calculate the remaining number of bits in bitstream after reading.
|
||||
self.bits_left =
|
||||
(self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64 - bnum as i64;
|
||||
if self.bits_left < 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Special case for reading zero bits. Return true
|
||||
if bnum == 0 {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// Handle the bit position arithmetic more carefully
|
||||
let mut new_bpos = self.bpos as i32 - (bnum % 8) as i32;
|
||||
let mut new_pos = self.pos + (bnum / 8) as usize;
|
||||
|
||||
if new_bpos < 1 {
|
||||
new_bpos += 8;
|
||||
new_pos += 1;
|
||||
}
|
||||
|
||||
self.bpos = new_bpos as u8;
|
||||
self.pos = new_pos;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Check alignment: true if on next-byte boundary.
|
||||
pub fn is_byte_aligned(&self) -> Result<bool, BitstreamError> {
|
||||
if self.pos > self.data.len() {
|
||||
fatal!(cause = ExitCause::Bug; "In is_byte_aligned: bitstream length can not be negative!");
|
||||
}
|
||||
let vbit = self.bpos as i32;
|
||||
if vbit == 0 || vbit > 8 {
|
||||
fatal!(cause = ExitCause::Bug; "In is_byte_aligned: Illegal bit position value {}!", vbit);
|
||||
}
|
||||
Ok(vbit == 8)
|
||||
}
|
||||
|
||||
/// Align to next byte boundary (commit state).
|
||||
pub fn make_byte_aligned(&mut self) -> Result<(), BitstreamError> {
|
||||
if self.pos > self.data.len() {
|
||||
fatal!(cause = ExitCause::Bug; "In make_byte_aligned: bitstream length can not be negative!");
|
||||
}
|
||||
|
||||
let vbit = self.bpos as i32;
|
||||
|
||||
if vbit == 0 || vbit > 8 {
|
||||
fatal!(cause = ExitCause::Bug; "In make_byte_aligned: Illegal bit position value {}!", vbit);
|
||||
}
|
||||
|
||||
// Keep a negative bstr->bitsleft, but correct it.
|
||||
if self.bits_left < 0 {
|
||||
self.bits_left = (self.bits_left - 7) / 8 * 8;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.bpos != 8 {
|
||||
self.bpos = 8;
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
// Reset, in case a next_???() function was used before
|
||||
self.bits_left = 8 * (self.data.len() as i64 - self.pos as i64 - 1) + self.bpos as i64;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Peek at next `bynum` bytes without advancing.
|
||||
/// Errors if not aligned or insufficient data.
|
||||
pub fn next_bytes(&mut self, bynum: usize) -> Result<&'a [u8], BitstreamError> {
|
||||
if self.pos > self.data.len() {
|
||||
fatal!(cause = ExitCause::Bug; "In next_bytes: bitstream length can not be negative!");
|
||||
}
|
||||
|
||||
// Keep a negative bstr->bitsleft, but correct it.
|
||||
if self.bits_left < 0 {
|
||||
self.bits_left -= (bynum * 8) as i64;
|
||||
return Err(BitstreamError::InsufficientData);
|
||||
}
|
||||
|
||||
self.bits_left = (self.data.len() as i64 - self.pos as i64 - 1) * 8 + self.bpos as i64
|
||||
- (bynum * 8) as i64;
|
||||
|
||||
if !self.is_byte_aligned()? || self.bits_left < 0 || bynum < 1 {
|
||||
return Err(BitstreamError::InsufficientData);
|
||||
}
|
||||
|
||||
// Remember the bitstream position
|
||||
self._i_bpos = 8;
|
||||
self._i_pos = self.pos + bynum;
|
||||
|
||||
// Return slice of the requested bytes
|
||||
if self.pos + bynum <= self.data.len() {
|
||||
Ok(&self.data[self.pos..self.pos + bynum])
|
||||
} else {
|
||||
Err(BitstreamError::InsufficientData)
|
||||
}
|
||||
}
|
||||
/// Read and commit `bynum` bytes.
|
||||
pub fn read_bytes(&mut self, bynum: usize) -> Result<&'a [u8], BitstreamError> {
|
||||
let res = self.next_bytes(bynum)?;
|
||||
|
||||
// Advance the bitstream when a read was possible
|
||||
self.bpos = self._i_bpos;
|
||||
self.pos = self._i_pos;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
/// Return an integer number with "bytes" precision from the current bitstream position.
|
||||
/// Allowed "bytes" values are 1,2,4,8.
|
||||
/// This function advances the bitstream pointer when "advance" is true.
|
||||
/// Numbers come MSB (most significant first).
|
||||
pub fn bitstream_get_num(
|
||||
&mut self,
|
||||
bytes: usize,
|
||||
advance: bool,
|
||||
) -> Result<u64, BitstreamError> {
|
||||
let bpos = if advance {
|
||||
self.read_bytes(bytes)?
|
||||
} else {
|
||||
self.next_bytes(bytes)?
|
||||
};
|
||||
|
||||
match bytes {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
fatal!(cause = ExitCause::Bug; "In bitstream_get_num: Illegal precision value [{}]!", bytes);
|
||||
}
|
||||
}
|
||||
|
||||
let mut rval = 0u64;
|
||||
for i in 0..bytes {
|
||||
// Read backwards - C: unsigned char *ucpos = ((unsigned char *)bpos) + bytes - i - 1;
|
||||
let uc = bpos[bytes - i - 1];
|
||||
rval = (rval << 8) + uc as u64;
|
||||
}
|
||||
|
||||
Ok(rval)
|
||||
}
|
||||
|
||||
/// Read unsigned Exp-Golomb code from bitstream
|
||||
pub fn read_exp_golomb_unsigned(&mut self) -> Result<u64, BitstreamError> {
|
||||
let mut zeros = 0;
|
||||
|
||||
// Count leading zeros
|
||||
while self.read_bits(1)? == 0 && self.bits_left >= 0 {
|
||||
zeros += 1;
|
||||
}
|
||||
|
||||
// Read the remaining bits
|
||||
let remaining_bits = self.read_bits(zeros)?;
|
||||
let res = ((1u64 << zeros) - 1) + remaining_bits;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Read signed Exp-Golomb code from bitstream
|
||||
pub fn read_exp_golomb(&mut self) -> Result<i64, BitstreamError> {
|
||||
let res = self.read_exp_golomb_unsigned()? as i64;
|
||||
|
||||
// The following function might truncate when res+1 overflows
|
||||
// res = (res+1)/2 * (res % 2 ? 1 : -1);
|
||||
// Use this:
|
||||
// C: res = (res / 2 + (res % 2 ? 1 : 0)) * (res % 2 ? 1 : -1);
|
||||
let result =
|
||||
(res / 2 + if res % 2 != 0 { 1 } else { 0 }) * if res % 2 != 0 { 1 } else { -1 };
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
/// Read signed integer with bnum bits length.
|
||||
pub fn read_int(&mut self, bnum: u32) -> Result<i64, BitstreamError> {
|
||||
let res = self.read_bits(bnum)?;
|
||||
|
||||
// Special case for reading zero bits. Return zero
|
||||
if bnum == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// C: return (0xFFFFFFFFFFFFFFFFULL << bnum) | res;
|
||||
// Sign extend by filling upper bits with 1s
|
||||
let result = (0xFFFFFFFFFFFFFFFFu64 << bnum) | res;
|
||||
|
||||
Ok(result as i64)
|
||||
}
|
||||
|
||||
/// Return the value with the bit order reversed.
|
||||
pub fn reverse8(data: u8) -> u8 {
|
||||
let mut res = 0u8;
|
||||
|
||||
for k in 0..8 {
|
||||
res <<= 1;
|
||||
res |= if data & (0x01 << k) != 0 { 1 } else { 0 };
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_bitstream_creation() {
|
||||
let data = vec![0xFF, 0x00];
|
||||
let bs = BitStreamRust::new(&data);
|
||||
assert!(bs.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bits() {
|
||||
let data = vec![0b10101010];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
assert_eq!(bs.read_bits(1).unwrap(), 1);
|
||||
assert_eq!(bs.read_bits(1).unwrap(), 0);
|
||||
assert_eq!(bs.read_bits(1).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_byte_alignment() {
|
||||
let data = vec![0xFF];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
assert!(bs.is_byte_aligned().unwrap());
|
||||
bs.read_bits(1).unwrap();
|
||||
assert!(!bs.is_byte_aligned().unwrap());
|
||||
bs.make_byte_aligned().unwrap();
|
||||
assert!(bs.is_byte_aligned().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_bit_reads() {
|
||||
// Test data: 0xFF, 0x00 = 11111111 00000000
|
||||
let data = [0xFF, 0x00];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
assert_eq!(bs.next_bits(4).unwrap(), 0xF); // 1111
|
||||
assert_eq!(bs.next_bits(4).unwrap(), 0xF); // 1111
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cross_byte_boundary() {
|
||||
// Test data: 0xF0, 0x0F = 11110000 00001111
|
||||
let data = [0xF0, 0x0F];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
// Read 6 bits crossing byte boundary: 111100 (should be 0x3C = 60)
|
||||
assert_eq!(bs.next_bits(6).unwrap(), 0x3C);
|
||||
|
||||
// Read remaining 10 bits: 0000001111 (should be 0x0F = 15)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_reads() {
|
||||
// Test reading up to 64 bits
|
||||
let data = [0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
// Read all 64 bits at once
|
||||
let result = bs.next_bits(64).unwrap();
|
||||
let expected = 0xFFEEDDCCBBAA9988u64;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_bits_read() {
|
||||
let data = [0xAA];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
// Reading 0 bits should return 0 and not advance position
|
||||
assert_eq!(bs.next_bits(0).unwrap(), 0);
|
||||
assert_eq!(bs._i_pos, 0);
|
||||
|
||||
// Next read should still work normally
|
||||
assert_eq!(bs.next_bits(1).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insufficient_data() {
|
||||
let data = [0xAA]; // Only 8 bits available
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
// Try to read more bits than available
|
||||
let result = bs.next_bits(16).unwrap();
|
||||
assert_eq!(result, 0); // Should return 0 when not enough bits
|
||||
assert!(bs.bits_left < 0); // bits_left should be negative
|
||||
|
||||
// Subsequent reads should also return 0
|
||||
assert_eq!(bs.next_bits(8).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_bits_left_behavior() {
|
||||
let data = [0xFF];
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
// Exhaust all bits
|
||||
bs.next_bits(8).unwrap();
|
||||
|
||||
// Now bits_left should be 0
|
||||
assert_eq!(bs.bits_left, 0);
|
||||
|
||||
// Try to read more - should return 0 and make bits_left negative
|
||||
assert_eq!(bs.next_bits(4).unwrap(), 0);
|
||||
assert_eq!(bs.bits_left, -4);
|
||||
|
||||
// Another read should make it more negative
|
||||
assert_eq!(bs.next_bits(2).unwrap(), 0);
|
||||
assert_eq!(bs.bits_left, -6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bits_left_calculation() {
|
||||
let data = [0xFF, 0xFF, 0xFF]; // 24 bits total
|
||||
let mut bs = BitStreamRust::new(&data).unwrap();
|
||||
|
||||
assert_eq!(bs.bits_left, 24);
|
||||
|
||||
bs.next_bits(5).unwrap();
|
||||
assert_eq!(bs.bits_left, 19);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,10 @@
|
||||
//! | `cdp_section_type` | [`CdpSectionType`] |
|
||||
//! | `language[NB_LANGUAGE]` | [`Language`] |
|
||||
|
||||
mod bitstream;
|
||||
mod constants;
|
||||
mod options;
|
||||
|
||||
pub use bitstream::*;
|
||||
pub use constants::*;
|
||||
pub use options::*;
|
||||
|
||||
852
src/rust/src/libccxr_exports/bitstream.rs
Normal file
852
src/rust/src/libccxr_exports/bitstream.rs
Normal file
@@ -0,0 +1,852 @@
|
||||
use crate::bindings::bitstream;
|
||||
use lib_ccxr::common::BitStreamRust;
|
||||
use lib_ccxr::info;
|
||||
use std::os::raw::{c_int, c_uchar};
|
||||
|
||||
/// Copies the state from a Rust `BitStreamRust` to a C `bitstream` struct.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it:
|
||||
/// - Dereferences a raw pointer (`bitstream_ptr`)
|
||||
/// - Performs pointer arithmetic and assumes valid memory layout
|
||||
unsafe fn copy_bitstream_from_rust_to_c(
|
||||
bitstream_ptr: *mut bitstream,
|
||||
rust_bitstream: &BitStreamRust,
|
||||
) {
|
||||
// Handle empty slice case
|
||||
if rust_bitstream.data.is_empty() {
|
||||
(*bitstream_ptr).pos = std::ptr::null_mut();
|
||||
(*bitstream_ptr).bpos = 8;
|
||||
(*bitstream_ptr).end = std::ptr::null_mut();
|
||||
(*bitstream_ptr).bitsleft = 0;
|
||||
(*bitstream_ptr).error = c_int::from(rust_bitstream.error);
|
||||
(*bitstream_ptr)._i_pos = std::ptr::null_mut();
|
||||
(*bitstream_ptr)._i_bpos = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the original pos (which is the base of our slice)
|
||||
let base_ptr = rust_bitstream.data.as_ptr() as *mut c_uchar;
|
||||
let end_ptr = base_ptr.add(rust_bitstream.data.len());
|
||||
|
||||
// Current position should be base + rust pos
|
||||
let current_pos_ptr = base_ptr.add(rust_bitstream.pos);
|
||||
|
||||
// Internal position should be base + _i_pos
|
||||
let i_pos_ptr = if rust_bitstream._i_pos <= rust_bitstream.data.len() {
|
||||
base_ptr.add(rust_bitstream._i_pos)
|
||||
} else {
|
||||
base_ptr
|
||||
};
|
||||
|
||||
(*bitstream_ptr).pos = current_pos_ptr;
|
||||
(*bitstream_ptr).bpos = rust_bitstream.bpos as i32;
|
||||
(*bitstream_ptr).end = end_ptr;
|
||||
(*bitstream_ptr).bitsleft = rust_bitstream.bits_left;
|
||||
(*bitstream_ptr).error = c_int::from(rust_bitstream.error);
|
||||
(*bitstream_ptr)._i_pos = i_pos_ptr;
|
||||
(*bitstream_ptr)._i_bpos = rust_bitstream._i_bpos as i32;
|
||||
}
|
||||
|
||||
/// Converts a C `bitstream` struct to a Rust `BitStreamRust` with static lifetime.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it Dereferences a raw pointer (`value`) without null checking and
|
||||
/// Performs pointer arithmetic and offset calculations assuming valid pointer relationships
|
||||
unsafe fn copy_bitstream_c_to_rust(value: *mut bitstream) -> BitStreamRust<'static> {
|
||||
// We need to work with the original buffer, not just from current position
|
||||
let mut slice: &[u8] = &[];
|
||||
let mut current_pos_in_slice = 0;
|
||||
let mut i_pos_in_slice = 0;
|
||||
|
||||
if !(*value).pos.is_null() && !(*value).end.is_null() {
|
||||
// Calculate total buffer length from pos to end
|
||||
let total_len = (*value).end.offset_from((*value).pos) as usize;
|
||||
|
||||
if total_len > 0 {
|
||||
// Create slice from current position to end
|
||||
slice = std::slice::from_raw_parts((*value).pos, total_len);
|
||||
// Current position in this slice is 0 (since slice starts from current pos)
|
||||
current_pos_in_slice = 0;
|
||||
|
||||
// Calculate _i_pos relative to the slice start (which is current pos)
|
||||
if !(*value)._i_pos.is_null() {
|
||||
let i_offset = (*value)._i_pos.offset_from((*value).pos);
|
||||
i_pos_in_slice = i_offset.max(0) as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitStreamRust {
|
||||
data: slice,
|
||||
pos: current_pos_in_slice,
|
||||
bpos: (*value).bpos as u8,
|
||||
bits_left: (*value).bitsleft,
|
||||
error: (*value).error != 0,
|
||||
_i_pos: i_pos_in_slice,
|
||||
_i_bpos: (*value)._i_bpos as u8,
|
||||
}
|
||||
}
|
||||
/// Updates only the internal state of a C bitstream without modifying current position pointers.
|
||||
/// Used by functions like `ccxr_next_bits` that peek at data without advancing the main position.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it:
|
||||
/// This function is unsafe because it Dereferences a raw pointer (`value`) without null checking and
|
||||
/// Performs pointer arithmetic and offset calculations assuming valid pointer relationships
|
||||
unsafe fn copy_internal_state_from_rust_to_c(
|
||||
bitstream_ptr: *mut bitstream,
|
||||
rust_bitstream: &BitStreamRust,
|
||||
) {
|
||||
// Only update internal positions and bits_left, NOT current position
|
||||
(*bitstream_ptr).bitsleft = rust_bitstream.bits_left;
|
||||
(*bitstream_ptr).error = c_int::from(rust_bitstream.error);
|
||||
(*bitstream_ptr)._i_bpos = rust_bitstream._i_bpos as i32;
|
||||
|
||||
// Handle _i_pos
|
||||
if rust_bitstream.data.is_empty() {
|
||||
(*bitstream_ptr)._i_pos = std::ptr::null_mut();
|
||||
} else {
|
||||
let base_ptr = rust_bitstream.data.as_ptr() as *mut c_uchar;
|
||||
let i_pos_ptr = if rust_bitstream._i_pos <= rust_bitstream.data.len() {
|
||||
base_ptr.add(rust_bitstream._i_pos)
|
||||
} else {
|
||||
base_ptr // Fallback to start if out of bounds
|
||||
};
|
||||
(*bitstream_ptr)._i_pos = i_pos_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a bitstream created by Rust allocation functions.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it Drops the `Box` created from a raw pointer (`bs`) without null checking.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_free_bitstream(bs: *mut BitStreamRust<'static>) {
|
||||
if !bs.is_null() {
|
||||
drop(Box::from_raw(bs));
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bits from a bitstream without advancing the position (peek operation).
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_internal_state_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_next_bits(bs: *mut bitstream, bnum: u32) -> u64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let val = match rust_bs.next_bits(bnum) {
|
||||
Ok(val) => val,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
// Only copy back internal state, NOT current position
|
||||
copy_internal_state_from_rust_to_c(bs, &rust_bs);
|
||||
val
|
||||
}
|
||||
|
||||
/// Read bits from a bitstream and advance the position.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_read_bits(bs: *mut bitstream, bnum: u32) -> u64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let val = match rust_bs.read_bits(bnum) {
|
||||
Ok(val) => val,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
val
|
||||
}
|
||||
|
||||
/// Skip a number of bits in the bitstream.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_skip_bits(bs: *mut bitstream, bnum: u32) -> i32 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let val = match rust_bs.skip_bits(bnum) {
|
||||
Ok(val) => val,
|
||||
Err(_) => return 0,
|
||||
};
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
if val {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the bitstream is byte aligned.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_is_byte_aligned(bs: *mut bitstream) -> i32 {
|
||||
let rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
match rust_bs.is_byte_aligned() {
|
||||
Ok(val) => {
|
||||
if val {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Align the bitstream to the next byte boundary.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_make_byte_aligned(bs: *mut bitstream) {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
if rust_bs.make_byte_aligned().is_ok() {
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
} else {
|
||||
info!("Bitstream : Failed to make bitstream byte aligned");
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a pointer to the next bytes in the bitstream without advancing the position.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_internal_state_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_next_bytes(bs: *mut bitstream, bynum: usize) -> *const u8 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
match rust_bs.next_bytes(bynum) {
|
||||
Ok(slice) => {
|
||||
// Copy back internal state only (like next_bits does)
|
||||
copy_internal_state_from_rust_to_c(bs, &rust_bs);
|
||||
slice.as_ptr()
|
||||
}
|
||||
Err(_) => std::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes from the bitstream and advance the position.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_read_bytes(bs: *mut bitstream, bynum: usize) -> *const u8 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
match rust_bs.read_bytes(bynum) {
|
||||
Ok(slice) => {
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
slice.as_ptr()
|
||||
}
|
||||
Err(_) => std::ptr::null(),
|
||||
}
|
||||
}
|
||||
/// Read a multi-byte number from the bitstream with optional position advancement.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_bitstream_get_num(
|
||||
bs: *mut bitstream,
|
||||
bytes: usize,
|
||||
advance: i32,
|
||||
) -> u64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let result = rust_bs.bitstream_get_num(bytes, advance != 0).unwrap_or(0);
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
result
|
||||
}
|
||||
|
||||
/// Read an unsigned Exp-Golomb code from the bitstream.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_read_exp_golomb_unsigned(bs: *mut bitstream) -> u64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let result = rust_bs.read_exp_golomb_unsigned().unwrap_or(0);
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
result
|
||||
}
|
||||
|
||||
/// Read a signed Exp-Golomb code from the bitstream.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_read_exp_golomb(bs: *mut bitstream) -> i64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let result = rust_bs.read_exp_golomb().unwrap_or(0);
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
result
|
||||
}
|
||||
/// Read a signed integer value from the specified number of bits.
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is unsafe because it calls unsafe functions `copy_bitstream_c_to_rust` and `copy_bitstream_from_rust_to_c`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_read_int(bs: *mut bitstream, bnum: u32) -> i64 {
|
||||
let mut rust_bs = copy_bitstream_c_to_rust(bs);
|
||||
let result = rust_bs.read_int(bnum).unwrap_or(0);
|
||||
copy_bitstream_from_rust_to_c(bs, &rust_bs);
|
||||
result
|
||||
}
|
||||
|
||||
/// Reverse the bits in a byte (bit 0 becomes bit 7, etc.).
|
||||
///
|
||||
/// # Safety
|
||||
/// This function is marked unsafe only because it's part of the FFI interface.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn ccxr_reverse8(data: u8) -> u8 {
|
||||
BitStreamRust::reverse8(data)
|
||||
}
|
||||
|
||||
mod tests {
|
||||
// FFI binding tests
|
||||
#[test]
|
||||
fn test_ffi_next_bits() {
|
||||
let data = vec![0b10101010];
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: data.as_ptr() as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { data.as_ptr().add(data.len()) } as *mut u8,
|
||||
bitsleft: (data.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: data.as_ptr() as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
assert_eq!(unsafe { super::ccxr_next_bits(&mut c_bs, 1) }, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_read_bits() {
|
||||
let data = vec![0b10101010];
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: data.as_ptr() as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { data.as_ptr().add(data.len()) } as *mut u8,
|
||||
bitsleft: (data.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: data.as_ptr() as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
assert_eq!(unsafe { super::ccxr_read_bits(&mut c_bs, 3) }, 0b101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_byte_alignment() {
|
||||
let data = vec![0xFF];
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: data.as_ptr() as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { data.as_ptr().add(data.len()) } as *mut u8,
|
||||
bitsleft: (data.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: data.as_ptr() as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
unsafe { super::ccxr_is_byte_aligned(&mut c_bs as *mut crate::bindings::bitstream) },
|
||||
1
|
||||
);
|
||||
unsafe { super::ccxr_read_bits(&mut c_bs, 1) };
|
||||
assert_eq!(
|
||||
unsafe { super::ccxr_is_byte_aligned(&mut c_bs as *mut crate::bindings::bitstream) },
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_read_bytes() {
|
||||
static DATA: [u8; 3] = [0xAA, 0xBB, 0xCC];
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: DATA.as_ptr() as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { DATA.as_ptr().add(DATA.len()) } as *mut u8,
|
||||
bitsleft: (DATA.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: DATA.as_ptr() as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let ptr = super::ccxr_read_bytes(&mut c_bs, 2);
|
||||
assert!(!ptr.is_null());
|
||||
let b1 = *ptr;
|
||||
let b2 = *ptr.add(1);
|
||||
assert_eq!([b1, b2], [0xAA, 0xBB]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_exp_golomb() {
|
||||
let data = vec![0b10000000];
|
||||
let data_ptr = data.as_ptr();
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: data_ptr as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { data_ptr.add(data.len()) } as *mut u8,
|
||||
bitsleft: (data.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: data_ptr as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
unsafe { super::ccxr_read_exp_golomb_unsigned(&mut c_bs) },
|
||||
0
|
||||
);
|
||||
drop(data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_reverse8() {
|
||||
assert_eq!(unsafe { super::ccxr_reverse8(0b10101010) }, 0b01010101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ffi_state_updates() {
|
||||
let data = vec![0xAA, 0xBB];
|
||||
let mut c_bs = crate::bindings::bitstream {
|
||||
pos: data.as_ptr() as *mut u8,
|
||||
bpos: 8,
|
||||
end: unsafe { data.as_ptr().add(data.len()) } as *mut u8,
|
||||
bitsleft: (data.len() as i64) * 8,
|
||||
error: 0,
|
||||
_i_pos: data.as_ptr() as *mut u8,
|
||||
_i_bpos: 8,
|
||||
};
|
||||
|
||||
unsafe { super::ccxr_read_bits(&mut c_bs, 4) };
|
||||
assert_eq!(c_bs.bpos, 4);
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod bitstream_copying_tests {
|
||||
use super::*;
|
||||
use std::ptr;
|
||||
|
||||
// Test helper function to create a test buffer
|
||||
fn create_test_buffer(size: usize) -> Vec<u8> {
|
||||
(0..size).map(|i| (i % 256) as u8).collect()
|
||||
}
|
||||
|
||||
// Test helper to verify pointer arithmetic safety
|
||||
unsafe fn verify_pointer_bounds(c_stream: &bitstream) -> bool {
|
||||
!c_stream.pos.is_null() && !c_stream.end.is_null() && c_stream.pos <= c_stream.end
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_to_c_basic_conversion() {
|
||||
let buffer = create_test_buffer(100);
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 3,
|
||||
bits_left: 789,
|
||||
error: false,
|
||||
_i_pos: 10,
|
||||
_i_bpos: 5,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &*c_s;
|
||||
// Verify basic field conversions
|
||||
assert_eq!(c_stream.bpos, 3);
|
||||
assert_eq!(c_stream.bitsleft, 789);
|
||||
assert_eq!(c_stream.error, 0);
|
||||
assert_eq!(c_stream._i_bpos, 5);
|
||||
|
||||
// Verify pointer arithmetic
|
||||
assert!(verify_pointer_bounds(&c_stream));
|
||||
assert_eq!(c_stream.end.offset_from(c_stream.pos), 100);
|
||||
assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 10);
|
||||
|
||||
// Verify data integrity
|
||||
let reconstructed_slice = std::slice::from_raw_parts(c_stream.pos, 100);
|
||||
assert_eq!(reconstructed_slice, &buffer[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_c_to_rust_basic_conversion() {
|
||||
let mut buffer = create_test_buffer(50);
|
||||
let buffer_ptr = buffer.as_mut_ptr();
|
||||
|
||||
unsafe {
|
||||
let mut c_stream = bitstream {
|
||||
pos: buffer_ptr,
|
||||
bpos: 7,
|
||||
end: buffer_ptr.add(50),
|
||||
bitsleft: 400,
|
||||
error: 1,
|
||||
_i_pos: buffer_ptr.add(15),
|
||||
_i_bpos: 2,
|
||||
};
|
||||
|
||||
let rust_stream = copy_bitstream_c_to_rust(&mut c_stream as *mut bitstream);
|
||||
|
||||
// Verify basic field conversions
|
||||
assert_eq!(rust_stream.bpos, 7);
|
||||
assert_eq!(rust_stream.bits_left, 400);
|
||||
assert_eq!(rust_stream.error, true);
|
||||
assert_eq!(rust_stream._i_pos, 15);
|
||||
assert_eq!(rust_stream._i_bpos, 2);
|
||||
|
||||
// Verify slice reconstruction
|
||||
assert_eq!(rust_stream.data.len(), 50);
|
||||
assert_eq!(rust_stream.data, &buffer[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_round_trip_conversion() {
|
||||
let buffer = create_test_buffer(75);
|
||||
let original = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 4,
|
||||
bits_left: 600,
|
||||
error: true,
|
||||
_i_pos: 25,
|
||||
_i_bpos: 1,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &original);
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
|
||||
// Verify all fields match
|
||||
assert_eq!(reconstructed.bpos, original.bpos);
|
||||
assert_eq!(reconstructed.bits_left, original.bits_left);
|
||||
assert_eq!(reconstructed.error, original.error);
|
||||
assert_eq!(reconstructed._i_pos, original._i_pos);
|
||||
assert_eq!(reconstructed._i_bpos, original._i_bpos);
|
||||
assert_eq!(reconstructed.data, original.data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_buffer() {
|
||||
let buffer: &[u8] = &[];
|
||||
let rust_stream = BitStreamRust {
|
||||
data: buffer,
|
||||
pos: 0,
|
||||
bpos: 0,
|
||||
bits_left: 0,
|
||||
error: false,
|
||||
_i_pos: 0,
|
||||
_i_bpos: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
|
||||
// Verify empty buffer handling
|
||||
assert_eq!(c_stream.end.offset_from(c_stream.pos), 0);
|
||||
assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 0);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed.data.len(), 0);
|
||||
assert_eq!(reconstructed._i_pos, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_byte_buffer() {
|
||||
let buffer = vec![0x42u8];
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 7,
|
||||
bits_left: 1,
|
||||
error: false,
|
||||
_i_pos: 0,
|
||||
_i_bpos: 3,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
assert_eq!(c_stream.end.offset_from(c_stream.pos), 1);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed.data.len(), 1);
|
||||
assert_eq!(reconstructed.data[0], 0x42);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_buffer() {
|
||||
let buffer = create_test_buffer(10000);
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 2,
|
||||
bits_left: 80000,
|
||||
error: false,
|
||||
_i_pos: 5000,
|
||||
_i_bpos: 6,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
assert_eq!(c_stream.end.offset_from(c_stream.pos), 10000);
|
||||
assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), 5000);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed.data.len(), 10000);
|
||||
assert_eq!(reconstructed._i_pos, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_values() {
|
||||
let buffer = create_test_buffer(256);
|
||||
|
||||
// Test with maximum values
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 7, // Max value for 3 bits
|
||||
bits_left: i64::MAX,
|
||||
error: true,
|
||||
_i_pos: 255, // Last valid index
|
||||
_i_bpos: 7,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
|
||||
assert_eq!(reconstructed.bpos, 7);
|
||||
assert_eq!(reconstructed.bits_left, i64::MAX);
|
||||
assert_eq!(reconstructed.error, true);
|
||||
assert_eq!(reconstructed._i_pos, 255);
|
||||
assert_eq!(reconstructed._i_bpos, 7);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_values() {
|
||||
let buffer = create_test_buffer(50);
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 0,
|
||||
bits_left: -100, // Negative bits_left
|
||||
error: false, // Negative error code
|
||||
_i_pos: 0,
|
||||
_i_bpos: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
|
||||
assert_eq!(reconstructed.bits_left, -100);
|
||||
assert_eq!(reconstructed.error, false);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_null_i_pos_handling() {
|
||||
let mut buffer = create_test_buffer(30);
|
||||
let buffer_ptr = buffer.as_mut_ptr();
|
||||
|
||||
unsafe {
|
||||
let mut c_stream = bitstream {
|
||||
pos: buffer_ptr,
|
||||
bpos: 0,
|
||||
end: buffer_ptr.add(30),
|
||||
bitsleft: 240,
|
||||
error: 0,
|
||||
_i_pos: ptr::null_mut(), // Null _i_pos
|
||||
_i_bpos: 0,
|
||||
};
|
||||
|
||||
let rust_stream = copy_bitstream_c_to_rust(&mut c_stream as *mut bitstream);
|
||||
|
||||
// Should default to 0 when _i_pos is null
|
||||
assert_eq!(rust_stream._i_pos, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_different_buffer_positions() {
|
||||
let buffer = create_test_buffer(100);
|
||||
|
||||
// Test different _i_pos values
|
||||
for i_pos in [0, 1, 50, 99] {
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 0,
|
||||
bits_left: 800,
|
||||
error: false,
|
||||
_i_pos: i_pos,
|
||||
_i_bpos: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
assert_eq!(c_stream._i_pos.offset_from(c_stream.pos), i_pos as isize);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed._i_pos, i_pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_integrity_after_conversion() {
|
||||
// Create a buffer with specific pattern
|
||||
let mut buffer = Vec::new();
|
||||
for i in 0..256 {
|
||||
buffer.push(i as u8);
|
||||
}
|
||||
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 3,
|
||||
bits_left: 2048,
|
||||
error: false,
|
||||
_i_pos: 128,
|
||||
_i_bpos: 4,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
|
||||
// Verify we can read the data correctly through C pointers
|
||||
for i in 0..256 {
|
||||
let byte_val = *c_stream.pos.add(i);
|
||||
assert_eq!(byte_val, i as u8);
|
||||
}
|
||||
|
||||
// Verify internal position pointer
|
||||
let internal_byte = *c_stream._i_pos;
|
||||
assert_eq!(internal_byte, 128);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed.data.len(), 256);
|
||||
for (i, &byte) in reconstructed.data.iter().enumerate() {
|
||||
assert_eq!(byte, i as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_conversions() {
|
||||
let buffer = create_test_buffer(64);
|
||||
let mut rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 1,
|
||||
bits_left: 512,
|
||||
error: false,
|
||||
_i_pos: 32,
|
||||
_i_bpos: 2,
|
||||
};
|
||||
|
||||
// Test multiple round-trip conversions
|
||||
for _ in 0..5 {
|
||||
rust_stream.error = true;
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let new_rust_stream = copy_bitstream_c_to_rust(c_s);
|
||||
|
||||
assert_eq!(new_rust_stream.error, true);
|
||||
assert_eq!(new_rust_stream.data.len(), 64);
|
||||
assert_eq!(new_rust_stream._i_pos, 32);
|
||||
|
||||
rust_stream = new_rust_stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bit_position_values() {
|
||||
let buffer = create_test_buffer(10);
|
||||
|
||||
// Test all valid bit positions (0-7)
|
||||
for bpos in 0..8 {
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: bpos as u8,
|
||||
bits_left: 80,
|
||||
error: false,
|
||||
_i_pos: 5,
|
||||
_i_bpos: (7 - bpos) as u8,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
assert_eq!(c_stream.bpos, bpos);
|
||||
assert_eq!(c_stream._i_bpos, 7 - bpos);
|
||||
|
||||
let reconstructed = copy_bitstream_c_to_rust(c_s);
|
||||
assert_eq!(reconstructed.bpos, bpos as u8);
|
||||
assert_eq!(reconstructed._i_bpos, (7 - bpos) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_memory_safety() {
|
||||
let buffer = create_test_buffer(1000);
|
||||
let rust_stream = BitStreamRust {
|
||||
data: &buffer,
|
||||
pos: 0,
|
||||
bpos: 0,
|
||||
bits_left: 8000,
|
||||
error: false,
|
||||
_i_pos: 500,
|
||||
_i_bpos: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let c_s = Box::into_raw(Box::new(bitstream::default()));
|
||||
copy_bitstream_from_rust_to_c(c_s, &rust_stream);
|
||||
let c_stream = &mut *c_s;
|
||||
|
||||
// Verify all pointers are within bounds
|
||||
assert!(verify_pointer_bounds(&c_stream));
|
||||
|
||||
// Verify we can safely access the boundaries
|
||||
let first_byte = *c_stream.pos;
|
||||
let last_byte = *c_stream.end.sub(1);
|
||||
let internal_byte = *c_stream._i_pos;
|
||||
|
||||
// These should not panic and should match our buffer
|
||||
assert_eq!(first_byte, 0);
|
||||
assert_eq!(last_byte, (999 % 256) as u8);
|
||||
assert_eq!(internal_byte, (500 % 256) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Provides C-FFI functions that are direct equivalent of functions available in C.
|
||||
|
||||
pub mod bitstream;
|
||||
pub mod time;
|
||||
|
||||
use crate::ccx_options;
|
||||
use lib_ccxr::util::log::*;
|
||||
use lib_ccxr::util::{bits::*, levenshtein::*};
|
||||
|
||||
@@ -11,3 +11,4 @@
|
||||
#include "../lib_ccx/hardsubx.h"
|
||||
#include "../lib_ccx/utility.h"
|
||||
#include "../lib_ccx/ccx_encoders_helpers.h"
|
||||
#include "../lib_ccx/cc_bitstream.h"
|
||||
|
||||
Reference in New Issue
Block a user