use crate::{SubtitleError, SubtitleResult};
use std::collections::HashMap;
pub struct DvbDecoder {
pages: HashMap<u16, PageComposition>,
regions: HashMap<u8, RegionComposition>,
cluts: HashMap<u8, Clut>,
objects: HashMap<u16, ObjectData>,
display_definition: Option<DisplayDefinition>,
subtitles: Vec<DvbSubtitle>,
}
#[derive(Clone, Debug)]
pub struct DvbSubtitle {
pub start_time: i64,
pub end_time: i64,
pub page_id: u16,
pub regions: Vec<RegionDisplay>,
}
#[derive(Clone, Debug)]
pub struct RegionDisplay {
pub region_id: u8,
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
pub bitmap: Vec<u8>,
}
#[derive(Clone, Debug)]
struct PageComposition {
page_id: u16,
page_timeout: u8,
page_version: u8,
page_state: u8,
regions: Vec<RegionReference>,
}
#[derive(Clone, Debug)]
struct RegionReference {
region_id: u8,
horizontal_address: u16,
vertical_address: u16,
}
#[derive(Clone, Debug)]
struct RegionComposition {
region_id: u8,
region_version: u8,
region_width: u16,
region_height: u16,
region_depth: u8,
clut_id: u8,
objects: Vec<ObjectReference>,
}
#[derive(Clone, Debug)]
struct ObjectReference {
object_id: u16,
object_type: u8,
horizontal_position: u16,
vertical_position: u16,
foreground_pixel_code: u8,
background_pixel_code: u8,
}
#[derive(Clone, Debug)]
struct Clut {
clut_id: u8,
clut_version: u8,
entries: HashMap<u8, ClutEntry>,
}
#[derive(Clone, Copy, Debug)]
struct ClutEntry {
r: u8,
g: u8,
b: u8,
t: u8, }
impl ClutEntry {
const TRANSPARENT: Self = Self {
r: 0,
g: 0,
b: 0,
t: 0,
};
}
#[derive(Clone, Debug)]
struct ObjectData {
object_id: u16,
object_version: u8,
coding_method: u8,
non_modifying_color_flag: bool,
top_field_data: Vec<u8>,
bottom_field_data: Vec<u8>,
}
#[derive(Clone, Debug)]
struct DisplayDefinition {
width: u16,
height: u16,
}
fn decode_2bit_rle(data: &[u8], width: u16) -> Vec<u8> {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let mut bit_offset: usize = 0;
let total_bits = data.len() * 8;
let read_bits = |offset: usize, count: usize| -> Option<u8> {
if offset + count > total_bits {
return None;
}
let mut val: u8 = 0;
for i in 0..count {
let byte_idx = (offset + i) / 8;
let bit_idx = 7 - ((offset + i) % 8);
val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1);
}
Some(val)
};
loop {
let Some(code) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
if code != 0 {
pixels.push(code);
} else {
let Some(sub) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
match sub {
0b00 => {
break;
}
0b01 => {
pixels.push(0);
pixels.push(0);
}
0b10 => {
let Some(run_bits) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let run = run_bits as usize + 3;
for _ in 0..run {
pixels.push(0);
}
}
0b11 => {
let Some(colour) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let Some(run_bits) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let run = run_bits as usize + 4;
for _ in 0..run {
pixels.push(colour);
}
}
_ => unreachable!(),
}
}
}
pixels
}
fn decode_4bit_rle(data: &[u8], width: u16) -> Vec<u8> {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let mut i = 0; let mut high: bool = true;
let next_nibble = |idx: &mut usize, high_flag: &mut bool| -> Option<u8> {
if *idx >= data.len() {
return None;
}
let nibble = if *high_flag {
(data[*idx] >> 4) & 0x0F
} else {
let n = data[*idx] & 0x0F;
*idx += 1;
n
};
*high_flag = !*high_flag;
Some(nibble)
};
loop {
let Some(n1) = next_nibble(&mut i, &mut high) else {
break;
};
if n1 != 0 {
pixels.push(n1);
} else {
let Some(n2) = next_nibble(&mut i, &mut high) else {
break;
};
if n2 == 0 {
break;
} else if (n2 & 0x08) == 0 {
let run = n2 as usize + 2;
for _ in 0..run {
pixels.push(0);
}
} else {
let run = (n2 & 0x07) as usize + 4;
let Some(colour) = next_nibble(&mut i, &mut high) else {
break;
};
for _ in 0..run {
pixels.push(colour);
}
}
}
}
pixels
}
fn decode_8bit_rle(data: &[u8], width: u16) -> Vec<u8> {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let mut i = 0;
while i < data.len() {
let b = data[i];
i += 1;
if b != 0 {
pixels.push(b);
} else {
if i >= data.len() {
break;
}
let b2 = data[i];
i += 1;
if b2 == 0 {
break;
} else if b2 <= 0x7F {
for _ in 0..b2 {
pixels.push(0);
}
} else {
let run = (b2 as usize).saturating_sub(0x80).saturating_add(3);
if i >= data.len() {
break;
}
let colour = data[i];
i += 1;
for _ in 0..run {
pixels.push(colour);
}
}
}
}
pixels
}
impl DvbDecoder {
#[must_use]
pub fn new() -> Self {
Self {
pages: HashMap::new(),
regions: HashMap::new(),
cluts: HashMap::new(),
objects: HashMap::new(),
display_definition: None,
subtitles: Vec::new(),
}
}
pub fn decode_segment(&mut self, data: &[u8], timestamp_ms: i64) -> SubtitleResult<()> {
if data.len() < 6 {
return Err(SubtitleError::ParseError(
"DVB segment too short".to_string(),
));
}
let sync_byte = data[0];
if sync_byte != 0x0F {
return Err(SubtitleError::ParseError(
"Invalid DVB sync byte".to_string(),
));
}
let segment_type = data[1];
let page_id = u16::from_be_bytes([data[2], data[3]]);
let segment_length = u16::from_be_bytes([data[4], data[5]]) as usize;
if data.len() < 6 + segment_length {
return Err(SubtitleError::ParseError(
"DVB segment data too short".to_string(),
));
}
let segment_data = &data[6..6 + segment_length];
match segment_type {
0x10 => self.decode_page_composition(page_id, segment_data)?,
0x11 => self.decode_region_composition(segment_data)?,
0x12 => self.decode_clut_definition(segment_data)?,
0x13 => self.decode_object_data(segment_data)?,
0x14 => self.decode_display_definition(segment_data)?,
0x80 => {
self.render_page(page_id, timestamp_ms)?;
}
_ => {
}
}
Ok(())
}
fn decode_page_composition(&mut self, page_id: u16, data: &[u8]) -> SubtitleResult<()> {
if data.len() < 2 {
return Ok(());
}
let page_timeout = data[0];
let page_version_state = data[1];
let page_version = (page_version_state >> 4) & 0x0F;
let page_state = page_version_state & 0x03;
let mut regions = Vec::new();
let mut pos = 2;
while pos + 6 <= data.len() {
let region_id = data[pos];
let horizontal_address = u16::from_be_bytes([data[pos + 2], data[pos + 3]]);
let vertical_address = u16::from_be_bytes([data[pos + 4], data[pos + 5]]);
regions.push(RegionReference {
region_id,
horizontal_address,
vertical_address,
});
pos += 6;
}
let page = PageComposition {
page_id,
page_timeout,
page_version,
page_state,
regions,
};
self.pages.insert(page_id, page);
Ok(())
}
fn decode_region_composition(&mut self, data: &[u8]) -> SubtitleResult<()> {
if data.len() < 10 {
return Ok(());
}
let region_id = data[0];
let region_version = (data[1] >> 4) & 0x0F;
let _fill_flag = (data[1] & 0x08) != 0;
let region_width = u16::from_be_bytes([data[2], data[3]]);
let region_height = u16::from_be_bytes([data[4], data[5]]);
let _region_level_of_compatibility = (data[6] >> 5) & 0x07;
let region_depth = (data[6] >> 2) & 0x07;
let clut_id = data[7];
let mut objects = Vec::new();
let mut pos = 10;
while pos + 6 <= data.len() {
let object_id = u16::from_be_bytes([data[pos], data[pos + 1]]);
let object_type = (data[pos + 2] >> 6) & 0x03;
let _object_provider_flag = (data[pos + 2] >> 4) & 0x03;
let horizontal_position = u16::from_be_bytes([data[pos + 2] & 0x0F, data[pos + 3]]);
let vertical_position = u16::from_be_bytes([data[pos + 4] & 0x0F, data[pos + 5]]);
let (foreground_pixel_code, background_pixel_code) =
if object_type == 0x01 || object_type == 0x02 {
if pos + 8 <= data.len() {
(data[pos + 6], data[pos + 7])
} else {
(0, 0)
}
} else {
(0, 0)
};
objects.push(ObjectReference {
object_id,
object_type,
horizontal_position,
vertical_position,
foreground_pixel_code,
background_pixel_code,
});
pos += if object_type == 0x01 || object_type == 0x02 {
8
} else {
6
};
}
let region = RegionComposition {
region_id,
region_version,
region_width,
region_height,
region_depth,
clut_id,
objects,
};
self.regions.insert(region_id, region);
Ok(())
}
fn decode_clut_definition(&mut self, data: &[u8]) -> SubtitleResult<()> {
if data.len() < 2 {
return Ok(());
}
let clut_id = data[0];
let clut_version = (data[1] >> 4) & 0x0F;
let mut entries = HashMap::new();
let mut pos = 2;
while pos + 2 <= data.len() {
let clut_entry_id = data[pos];
let clut_entry_flags = data[pos + 1];
let _entry_2bit = (clut_entry_flags & 0x80) != 0;
let _entry_4bit = (clut_entry_flags & 0x40) != 0;
let _entry_8bit = (clut_entry_flags & 0x20) != 0;
let full_range_flag = (clut_entry_flags & 0x01) != 0;
let (y, cr, cb, t, advance) = if full_range_flag {
if pos + 6 > data.len() {
break;
}
(
data[pos + 2],
data[pos + 3],
data[pos + 4],
data[pos + 5],
6,
)
} else {
if pos + 4 > data.len() {
break;
}
let b0 = data[pos + 2];
let b1 = data[pos + 3];
let y_val = b0 & 0xFC; let cr_val = ((b0 & 0x03) << 6) | ((b1 & 0xC0) >> 2); let cb_val = (b1 & 0x3C) << 2; let t_val = (b1 & 0x03) * 85; (y_val, cr_val, cb_val, t_val, 4)
};
let (r, g, b) = Self::ycrcb_to_rgb(y, cr, cb);
let alpha = 255 - t;
entries.insert(clut_entry_id, ClutEntry { r, g, b, t: alpha });
pos += advance;
}
let clut = Clut {
clut_id,
clut_version,
entries,
};
self.cluts.insert(clut_id, clut);
Ok(())
}
fn decode_object_data(&mut self, data: &[u8]) -> SubtitleResult<()> {
if data.len() < 3 {
return Ok(());
}
let object_id = u16::from_be_bytes([data[0], data[1]]);
let object_version = (data[2] >> 4) & 0x0F;
let coding_method = (data[2] >> 2) & 0x03;
let non_modifying_color_flag = (data[2] & 0x02) != 0;
let (top_field_data, bottom_field_data) = if coding_method == 0x00 && data.len() >= 7 {
let top_addr = u16::from_be_bytes([data[3], data[4]]) as usize;
let bot_addr = u16::from_be_bytes([data[5], data[6]]) as usize;
let top_end = if bot_addr > top_addr {
bot_addr
} else {
data.len()
};
let bot_end = data.len();
let top = if top_addr < data.len() {
data[top_addr..top_end.min(data.len())].to_vec()
} else {
Vec::new()
};
let bot = if bot_addr < data.len() && bot_addr != top_addr {
data[bot_addr..bot_end].to_vec()
} else {
Vec::new()
};
(top, bot)
} else {
(data[3..].to_vec(), Vec::new())
};
let object = ObjectData {
object_id,
object_version,
coding_method,
non_modifying_color_flag,
top_field_data,
bottom_field_data,
};
self.objects.insert(object_id, object);
Ok(())
}
fn decode_display_definition(&mut self, data: &[u8]) -> SubtitleResult<()> {
if data.len() < 5 {
return Ok(());
}
let width = u16::from_be_bytes([data[1], data[2]]);
let height = u16::from_be_bytes([data[3], data[4]]);
self.display_definition = Some(DisplayDefinition { width, height });
Ok(())
}
fn render_page(&mut self, page_id: u16, timestamp_ms: i64) -> SubtitleResult<()> {
let page = match self.pages.get(&page_id).cloned() {
Some(p) => p,
None => return Ok(()),
};
let mut regions_display = Vec::new();
for region_ref in &page.regions {
let region = match self.regions.get(®ion_ref.region_id).cloned() {
Some(r) => r,
None => continue,
};
let width = region.region_width;
let height = region.region_height;
let pixel_count = (width as usize) * (height as usize);
let mut bitmap = vec![0u8; pixel_count * 4];
let empty_clut = Clut {
clut_id: 0,
clut_version: 0,
entries: HashMap::new(),
};
let clut = self.cluts.get(®ion.clut_id).unwrap_or(&empty_clut);
for obj_ref in ®ion.objects {
if let Some(obj) = self.objects.get(&obj_ref.object_id).cloned() {
let pixel_indices = Self::decode_object_pixels(
&obj,
region_ref
.horizontal_address
.saturating_add(obj_ref.horizontal_position),
region_ref
.vertical_address
.saturating_add(obj_ref.vertical_position),
width,
height,
region.region_depth,
);
let obj_x = obj_ref.horizontal_position as usize;
let obj_y = obj_ref.vertical_position as usize;
for (row_idx, row_pixels) in pixel_indices.iter().enumerate() {
let dst_y = obj_y + row_idx;
if dst_y >= height as usize {
break;
}
for (col_idx, &idx) in row_pixels.iter().enumerate() {
let dst_x = obj_x + col_idx;
if dst_x >= width as usize {
break;
}
let pixel_pos = (dst_y * width as usize + dst_x) * 4;
let entry = clut
.entries
.get(&idx)
.copied()
.unwrap_or(ClutEntry::TRANSPARENT);
bitmap[pixel_pos] = entry.r;
bitmap[pixel_pos + 1] = entry.g;
bitmap[pixel_pos + 2] = entry.b;
bitmap[pixel_pos + 3] = entry.t;
}
}
}
}
regions_display.push(RegionDisplay {
region_id: region.region_id,
x: region_ref.horizontal_address,
y: region_ref.vertical_address,
width,
height,
bitmap,
});
}
let timeout_ms = i64::from(page.page_timeout) * 1000;
let end_time = if timeout_ms > 0 {
timestamp_ms + timeout_ms
} else {
timestamp_ms + 5000 };
self.subtitles.push(DvbSubtitle {
start_time: timestamp_ms,
end_time,
page_id,
regions: regions_display,
});
Ok(())
}
fn decode_object_pixels(
obj: &ObjectData,
_x: u16,
_y: u16,
region_width: u16,
region_height: u16,
region_depth: u8,
) -> Vec<Vec<u8>> {
if obj.coding_method != 0x00 {
return Vec::new();
}
let mut rows: Vec<Vec<u8>> = Vec::new();
let bpp = match region_depth {
1 => 2,
2 => 4,
_ => 8,
};
let top_rows = Self::parse_field_lines(&obj.top_field_data, bpp, region_width);
let bot_rows = Self::parse_field_lines(&obj.bottom_field_data, bpp, region_width);
let max_field_rows = top_rows.len().max(bot_rows.len());
for i in 0..max_field_rows {
if let Some(row) = top_rows.get(i) {
rows.push(row.clone());
}
if let Some(row) = bot_rows.get(i) {
rows.push(row.clone());
}
}
rows.truncate(region_height as usize);
rows
}
fn parse_field_lines(data: &[u8], bpp: u8, width: u16) -> Vec<Vec<u8>> {
let mut rows: Vec<Vec<u8>> = Vec::new();
match bpp {
2 => {
let mut offset = 0;
let total_bits = data.len() * 8;
loop {
if offset >= total_bits {
break;
}
let (row_pixels, consumed_bits) = decode_2bit_rle_row(data, offset, width);
if consumed_bits == 0 {
break;
}
rows.push(row_pixels);
offset += consumed_bits;
}
}
4 => {
let mut pos = 0;
let mut high = true;
loop {
if pos >= data.len() {
break;
}
let (row_pixels, new_pos, new_high) =
decode_4bit_rle_row(data, pos, high, width);
if new_pos == pos && new_high == high {
break;
}
rows.push(row_pixels);
pos = new_pos;
high = new_high;
}
}
_ => {
let mut pos = 0;
loop {
if pos >= data.len() {
break;
}
let (row_pixels, new_pos) = decode_8bit_rle_row(data, pos, width);
if new_pos == pos {
break;
}
rows.push(row_pixels);
pos = new_pos;
}
}
}
rows
}
fn ycrcb_to_rgb(y: u8, cr: u8, cb: u8) -> (u8, u8, u8) {
let y_f = f32::from(y);
let cr_f = f32::from(cr) - 128.0;
let cb_f = f32::from(cb) - 128.0;
let r = (y_f + 1.402 * cr_f).clamp(0.0, 255.0) as u8;
let g = (y_f - 0.344136 * cb_f - 0.714136 * cr_f).clamp(0.0, 255.0) as u8;
let b = (y_f + 1.772 * cb_f).clamp(0.0, 255.0) as u8;
(r, g, b)
}
#[must_use]
pub fn take_subtitles(&mut self) -> Vec<DvbSubtitle> {
std::mem::take(&mut self.subtitles)
}
#[must_use]
pub fn finalize(self) -> Vec<DvbSubtitle> {
self.subtitles
}
}
impl Default for DvbDecoder {
fn default() -> Self {
Self::new()
}
}
fn decode_2bit_rle_row(data: &[u8], start_bit: usize, width: u16) -> (Vec<u8>, usize) {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let total_bits = data.len() * 8;
let mut bit_offset = start_bit;
let read_bits = |offset: usize, count: usize| -> Option<u8> {
if offset + count > total_bits {
return None;
}
let mut val: u8 = 0;
for i in 0..count {
let byte_idx = (offset + i) / 8;
let bit_idx = 7 - ((offset + i) % 8);
val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1);
}
Some(val)
};
loop {
let Some(code) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
if code != 0 {
pixels.push(code);
} else {
let Some(sub) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
match sub {
0b00 => break, 0b01 => {
pixels.push(0);
pixels.push(0);
}
0b10 => {
let Some(run_bits) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let run = run_bits as usize + 3;
for _ in 0..run {
pixels.push(0);
}
}
0b11 => {
let Some(colour) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let Some(run_bits) = read_bits(bit_offset, 2) else {
break;
};
bit_offset += 2;
let run = run_bits as usize + 4;
for _ in 0..run {
pixels.push(colour);
}
}
_ => unreachable!(),
}
}
}
let consumed = bit_offset - start_bit;
(pixels, consumed)
}
fn decode_4bit_rle_row(
data: &[u8],
start_pos: usize,
start_high: bool,
width: u16,
) -> (Vec<u8>, usize, bool) {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let mut pos = start_pos;
let mut high = start_high;
let next_nibble = |p: &mut usize, h: &mut bool| -> Option<u8> {
if *p >= data.len() {
return None;
}
let nibble = if *h {
(data[*p] >> 4) & 0x0F
} else {
let n = data[*p] & 0x0F;
*p += 1;
n
};
*h = !*h;
Some(nibble)
};
loop {
let Some(n1) = next_nibble(&mut pos, &mut high) else {
break;
};
if n1 != 0 {
pixels.push(n1);
} else {
let Some(n2) = next_nibble(&mut pos, &mut high) else {
break;
};
if n2 == 0 {
if !high {
pos += 1;
high = true;
}
break;
} else if (n2 & 0x08) == 0 {
let run = n2 as usize + 2;
for _ in 0..run {
pixels.push(0);
}
} else {
let run = (n2 & 0x07) as usize + 4;
let Some(colour) = next_nibble(&mut pos, &mut high) else {
break;
};
for _ in 0..run {
pixels.push(colour);
}
}
}
}
(pixels, pos, high)
}
fn decode_8bit_rle_row(data: &[u8], start_pos: usize, width: u16) -> (Vec<u8>, usize) {
let mut pixels: Vec<u8> = Vec::with_capacity(width as usize);
let mut i = start_pos;
while i < data.len() {
let b = data[i];
i += 1;
if b != 0 {
pixels.push(b);
} else {
if i >= data.len() {
break;
}
let b2 = data[i];
i += 1;
if b2 == 0 {
break; } else if b2 <= 0x7F {
for _ in 0..b2 {
pixels.push(0);
}
} else {
let run = (b2 as usize).saturating_sub(0x80).saturating_add(3);
if i >= data.len() {
break;
}
let colour = data[i];
i += 1;
for _ in 0..run {
pixels.push(colour);
}
}
}
}
(pixels, i)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ycrcb_conversion_white() {
let (r, g, b) = DvbDecoder::ycrcb_to_rgb(255, 128, 128);
assert_eq!(r, 255);
assert_eq!(g, 255);
assert_eq!(b, 255);
}
#[test]
fn test_ycrcb_conversion_black() {
let (r, g, b) = DvbDecoder::ycrcb_to_rgb(0, 128, 128);
assert_eq!(r, 0);
assert_eq!(g, 0);
assert_eq!(b, 0);
}
#[test]
fn test_decoder_creation() {
let decoder = DvbDecoder::new();
assert_eq!(decoder.pages.len(), 0);
assert_eq!(decoder.regions.len(), 0);
}
#[test]
fn test_8bit_rle_single_pixel() {
let data = [0x05]; let pixels = decode_8bit_rle(&data, 8);
assert_eq!(pixels, vec![5]);
}
#[test]
fn test_8bit_rle_transparent_run() {
let data = [0x00, 0x03];
let pixels = decode_8bit_rle(&data, 8);
assert_eq!(pixels, vec![0, 0, 0]);
}
#[test]
fn test_8bit_rle_colour_run() {
let data = [0x00, 0x84, 0x07];
let pixels = decode_8bit_rle(&data, 16);
assert_eq!(pixels.len(), 7);
assert!(pixels.iter().all(|&p| p == 7));
}
#[test]
fn test_8bit_rle_end_of_line() {
let data = [0x01, 0x00, 0x00, 0x02];
let pixels = decode_8bit_rle(&data, 8);
assert_eq!(pixels, vec![1]);
}
#[test]
fn test_4bit_rle_single_pixels() {
let (pixels, _p, _h) = decode_4bit_rle_row(&[0xAB], 0, true, 8);
assert_eq!(pixels, vec![10, 11]);
}
#[test]
fn test_4bit_rle_transparent_run() {
let (pixels, _, _) = decode_4bit_rle_row(&[0x03, 0x00], 0, true, 16);
assert_eq!(pixels, vec![0u8; 5]);
}
#[test]
fn test_4bit_rle_colour_run() {
let data = [0x09, 0x70, 0x00];
let (pixels, _, _) = decode_4bit_rle_row(&data, 0, true, 16);
assert_eq!(pixels.len(), 5);
assert!(pixels.iter().all(|&p| p == 7));
}
#[test]
fn test_2bit_rle_single_pixel() {
let data = [0x40]; let (pixels, consumed) = decode_2bit_rle_row(&data, 0, 8);
assert_eq!(pixels, vec![1]);
assert_eq!(consumed, 6); }
#[test]
fn test_2bit_rle_transparent_pair() {
let data = [0x10];
let (pixels, _) = decode_2bit_rle_row(&data, 0, 8);
assert_eq!(pixels, vec![0, 0]);
}
#[test]
fn test_decode_segment_invalid_sync() {
let mut dec = DvbDecoder::new();
let bad = [0xFF, 0x10, 0x00, 0x01, 0x00, 0x00];
let result = dec.decode_segment(&bad, 0);
assert!(result.is_err());
}
#[test]
fn test_decode_segment_page_composition() {
let mut dec = DvbDecoder::new();
let data = [0x0F, 0x10, 0x00, 0x01, 0x00, 0x02, 0x05, 0x10];
dec.decode_segment(&data, 1000).expect("decode");
assert!(dec.pages.contains_key(&1));
}
}