use crate::error::{NetError, NetResult};
use crate::smpte2110::rtp::{RtpHeader, RtpPacket};
use bytes::{Buf, BufMut, BytesMut};
use std::collections::HashMap;
pub const RTP_PAYLOAD_TYPE_ANC: u8 = 100;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AncillaryDataType {
Undefined = 0x00,
ClosedCaptions = 0x61,
Timecode = 0x60,
AFD = 0x41,
BarData = 0x42,
SCTE104 = 0x43,
OP47 = 0x45,
MultiPacket = 0x62,
}
impl AncillaryDataType {
#[must_use]
pub fn from_did(did: u8) -> Self {
match did {
0x61 => Self::ClosedCaptions,
0x60 => Self::Timecode,
0x41 => Self::AFD,
0x42 => Self::BarData,
0x43 => Self::SCTE104,
0x45 => Self::OP47,
0x62 => Self::MultiPacket,
_ => Self::Undefined,
}
}
#[must_use]
pub const fn as_did(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AncillaryLocation {
VANC {
line_number: u16,
},
HANC {
line_number: u16,
offset: u16,
},
}
impl AncillaryLocation {
#[must_use]
pub const fn is_vanc(&self) -> bool {
matches!(self, Self::VANC { .. })
}
#[must_use]
pub const fn is_hanc(&self) -> bool {
matches!(self, Self::HANC { .. })
}
#[must_use]
pub const fn line_number(&self) -> u16 {
match self {
Self::VANC { line_number } | Self::HANC { line_number, .. } => *line_number,
}
}
}
#[derive(Debug, Clone)]
pub struct AncillaryData {
pub did: u8,
pub sdid: u8,
pub data_count: u8,
pub user_data: Vec<u8>,
pub checksum: u16,
pub location: AncillaryLocation,
}
impl AncillaryData {
#[must_use]
pub fn new(did: u8, sdid: u8, user_data: Vec<u8>, location: AncillaryLocation) -> Self {
let data_count = user_data.len() as u8;
let checksum = Self::calculate_checksum(did, sdid, data_count, &user_data);
Self {
did,
sdid,
data_count,
user_data,
checksum,
location,
}
}
fn calculate_checksum(did: u8, sdid: u8, data_count: u8, user_data: &[u8]) -> u16 {
let mut sum = u16::from(did) + u16::from(sdid) + u16::from(data_count);
for &byte in user_data {
sum += u16::from(byte);
}
sum & 0x1FF }
#[must_use]
pub fn validate_checksum(&self) -> bool {
let calculated =
Self::calculate_checksum(self.did, self.sdid, self.data_count, &self.user_data);
calculated == self.checksum
}
pub fn serialize(&self, buf: &mut BytesMut) {
buf.put_u16(0x0000);
buf.put_u16(0x03FF);
buf.put_u16(0x03FF);
buf.put_u16(u16::from(self.did));
buf.put_u16(u16::from(self.sdid));
buf.put_u16(u16::from(self.data_count));
for &byte in &self.user_data {
buf.put_u16(u16::from(byte));
}
buf.put_u16(self.checksum);
}
pub fn parse(data: &[u8], location: AncillaryLocation) -> NetResult<Self> {
if data.len() < 12 {
return Err(NetError::parse(0, "ANC data too short"));
}
let mut cursor = &data[..];
let adf0 = cursor.get_u16();
let adf1 = cursor.get_u16();
let adf2 = cursor.get_u16();
if adf0 != 0x0000 || adf1 != 0x03FF || adf2 != 0x03FF {
return Err(NetError::protocol("Invalid ANC ADF"));
}
let did = (cursor.get_u16() & 0xFF) as u8;
let sdid = (cursor.get_u16() & 0xFF) as u8;
let data_count = (cursor.get_u16() & 0xFF) as u8;
let mut user_data = Vec::with_capacity(data_count as usize);
for _ in 0..data_count {
if cursor.len() < 2 {
return Err(NetError::parse(0, "Insufficient data for UDW"));
}
user_data.push((cursor.get_u16() & 0xFF) as u8);
}
if cursor.len() < 2 {
return Err(NetError::parse(0, "Missing checksum"));
}
let checksum = cursor.get_u16() & 0x1FF;
let anc_data = Self {
did,
sdid,
data_count,
user_data,
checksum,
location,
};
if !anc_data.validate_checksum() {
return Err(NetError::protocol("ANC checksum mismatch"));
}
Ok(anc_data)
}
#[must_use]
pub fn data_type(&self) -> AncillaryDataType {
AncillaryDataType::from_did(self.did)
}
#[must_use]
pub fn is_cea608(&self) -> bool {
self.did == 0x61 && self.sdid == 0x01
}
#[must_use]
pub fn is_cea708(&self) -> bool {
self.did == 0x61 && self.sdid == 0x02
}
#[must_use]
pub fn is_timecode(&self) -> bool {
self.did == 0x60
}
}
#[derive(Debug, Clone)]
pub struct AncillaryConfig {
pub max_packets_per_frame: usize,
pub field_id: bool,
}
impl Default for AncillaryConfig {
fn default() -> Self {
Self {
max_packets_per_frame: 64,
field_id: false,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AncillaryHeaderExtension {
pub extended_sequence: u16,
pub field_id: bool,
pub line_number: u16,
pub horizontal_offset: u16,
}
impl AncillaryHeaderExtension {
#[must_use]
pub const fn new(field_id: bool, line_number: u16, horizontal_offset: u16) -> Self {
Self {
extended_sequence: 0,
field_id,
line_number,
horizontal_offset,
}
}
pub fn serialize(&self, buf: &mut BytesMut) {
buf.put_u16(self.extended_sequence);
let line_and_field = (self.line_number & 0x7FFF) | (if self.field_id { 0x8000 } else { 0 });
buf.put_u16(line_and_field);
buf.put_u16(self.horizontal_offset);
buf.put_u16(0); }
pub fn parse(data: &[u8]) -> NetResult<Self> {
if data.len() < 8 {
return Err(NetError::parse(0, "ANC header extension too short"));
}
let mut cursor = &data[..];
let extended_sequence = cursor.get_u16();
let line_and_field = cursor.get_u16();
let horizontal_offset = cursor.get_u16();
let _reserved = cursor.get_u16();
let field_id = (line_and_field & 0x8000) != 0;
let line_number = line_and_field & 0x7FFF;
Ok(Self {
extended_sequence,
field_id,
line_number,
horizontal_offset,
})
}
}
#[derive(Debug, Clone)]
pub struct AncillaryPacket {
pub header: RtpHeader,
pub anc_extension: AncillaryHeaderExtension,
pub anc_data: Vec<AncillaryData>,
}
impl AncillaryPacket {
#[must_use]
pub fn new(
header: RtpHeader,
anc_extension: AncillaryHeaderExtension,
anc_data: Vec<AncillaryData>,
) -> Self {
Self {
header,
anc_extension,
anc_data,
}
}
pub fn from_rtp(rtp_packet: &RtpPacket) -> NetResult<Self> {
let ext_data = rtp_packet
.header
.extension_data
.as_ref()
.ok_or_else(|| NetError::protocol("Missing ANC header extension"))?;
let anc_extension = AncillaryHeaderExtension::parse(&ext_data.data)?;
let mut anc_data = Vec::new();
let mut offset = 0;
while offset < rtp_packet.payload.len() {
let location = if anc_extension.horizontal_offset > 0 {
AncillaryLocation::HANC {
line_number: anc_extension.line_number,
offset: anc_extension.horizontal_offset,
}
} else {
AncillaryLocation::VANC {
line_number: anc_extension.line_number,
}
};
match AncillaryData::parse(&rtp_packet.payload[offset..], location) {
Ok(anc) => {
let anc_size = 6 + 2 * (3 + anc.data_count as usize + 1); offset += anc_size;
anc_data.push(anc);
}
Err(_) => break, }
}
Ok(Self {
header: rtp_packet.header.clone(),
anc_extension,
anc_data,
})
}
#[must_use]
pub fn to_rtp(&self) -> RtpPacket {
let mut payload = BytesMut::new();
for anc in &self.anc_data {
anc.serialize(&mut payload);
}
let mut ext_data = BytesMut::with_capacity(8);
self.anc_extension.serialize(&mut ext_data);
let mut header = self.header.clone();
header.extension = true;
header.extension_data = Some(crate::smpte2110::rtp::RtpHeaderExtension {
profile: 0x0200, data: ext_data.freeze(),
});
RtpPacket {
header,
payload: payload.freeze(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CEA608Data {
pub cc_data1: u8,
pub cc_data2: u8,
}
impl CEA608Data {
#[must_use]
pub const fn new(cc_data1: u8, cc_data2: u8) -> Self {
Self { cc_data1, cc_data2 }
}
#[must_use]
pub fn to_anc(&self, line_number: u16) -> AncillaryData {
let user_data = vec![self.cc_data1, self.cc_data2];
let location = AncillaryLocation::VANC { line_number };
AncillaryData::new(0x61, 0x01, user_data, location)
}
pub fn from_anc(anc: &AncillaryData) -> NetResult<Self> {
if !anc.is_cea608() {
return Err(NetError::protocol("Not a CEA-608 packet"));
}
if anc.user_data.len() != 2 {
return Err(NetError::protocol("Invalid CEA-608 data length"));
}
Ok(Self {
cc_data1: anc.user_data[0],
cc_data2: anc.user_data[1],
})
}
}
#[derive(Debug, Clone)]
pub struct CEA708Data {
pub cc_data: Vec<u8>,
}
impl CEA708Data {
#[must_use]
pub fn new(cc_data: Vec<u8>) -> Self {
Self { cc_data }
}
#[must_use]
pub fn to_anc(&self, line_number: u16) -> AncillaryData {
let location = AncillaryLocation::VANC { line_number };
AncillaryData::new(0x61, 0x02, self.cc_data.clone(), location)
}
pub fn from_anc(anc: &AncillaryData) -> NetResult<Self> {
if !anc.is_cea708() {
return Err(NetError::protocol("Not a CEA-708 packet"));
}
Ok(Self {
cc_data: anc.user_data.clone(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timecode {
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
pub frames: u8,
pub drop_frame: bool,
}
impl Timecode {
#[must_use]
pub const fn new(hours: u8, minutes: u8, seconds: u8, frames: u8, drop_frame: bool) -> Self {
Self {
hours,
minutes,
seconds,
frames,
drop_frame,
}
}
#[must_use]
pub fn to_anc(&self, line_number: u16) -> AncillaryData {
let mut user_data = vec![0u8; 8];
user_data[0] = ((self.frames / 10) << 4) | (self.frames % 10);
user_data[1] = ((self.seconds / 10) << 4) | (self.seconds % 10);
user_data[2] = ((self.minutes / 10) << 4) | (self.minutes % 10);
user_data[3] = ((self.hours / 10) << 4) | (self.hours % 10);
if self.drop_frame {
user_data[0] |= 0x40;
}
let location = AncillaryLocation::VANC { line_number };
AncillaryData::new(0x60, 0x60, user_data, location)
}
pub fn from_anc(anc: &AncillaryData) -> NetResult<Self> {
if !anc.is_timecode() {
return Err(NetError::protocol("Not a timecode packet"));
}
if anc.user_data.len() < 4 {
return Err(NetError::protocol("Invalid timecode data length"));
}
let frames_bcd = anc.user_data[0];
let seconds_bcd = anc.user_data[1];
let minutes_bcd = anc.user_data[2];
let hours_bcd = anc.user_data[3];
let frames = ((frames_bcd >> 4) & 0x03) * 10 + (frames_bcd & 0x0F);
let seconds = ((seconds_bcd >> 4) & 0x07) * 10 + (seconds_bcd & 0x0F);
let minutes = ((minutes_bcd >> 4) & 0x07) * 10 + (minutes_bcd & 0x0F);
let hours = ((hours_bcd >> 4) & 0x03) * 10 + (hours_bcd & 0x0F);
let drop_frame = (frames_bcd & 0x40) != 0;
Ok(Self {
hours,
minutes,
seconds,
frames,
drop_frame,
})
}
#[must_use]
pub fn format(&self) -> String {
let separator = if self.drop_frame { ';' } else { ':' };
format!(
"{:02}:{:02}:{:02}{}{:02}",
self.hours, self.minutes, self.seconds, separator, self.frames
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AFD {
pub afd_code: u8,
pub aspect_ratio: AspectRatio,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AspectRatio {
Ratio4_3,
Ratio16_9,
}
impl AFD {
#[must_use]
pub const fn new(afd_code: u8, aspect_ratio: AspectRatio) -> Self {
Self {
afd_code,
aspect_ratio,
}
}
#[must_use]
pub fn to_anc(&self, line_number: u16) -> AncillaryData {
let ar_code = match self.aspect_ratio {
AspectRatio::Ratio4_3 => 0,
AspectRatio::Ratio16_9 => 1,
};
let user_data = vec![
(ar_code << 7) | ((self.afd_code & 0x0F) << 3),
0, ];
let location = AncillaryLocation::VANC { line_number };
AncillaryData::new(0x41, 0x05, user_data, location)
}
}
pub struct AncillaryEncoder {
config: AncillaryConfig,
sequence_number: u16,
ssrc: u32,
}
impl AncillaryEncoder {
#[must_use]
pub fn new(config: AncillaryConfig, ssrc: u32) -> Self {
Self {
config,
sequence_number: rand::random(),
ssrc,
}
}
pub fn encode(
&mut self,
anc_data: Vec<AncillaryData>,
timestamp: u32,
) -> NetResult<AncillaryPacket> {
if anc_data.is_empty() {
return Err(NetError::protocol("No ancillary data to encode"));
}
let first_location = anc_data[0].location;
let line_number = first_location.line_number();
let horizontal_offset = match first_location {
AncillaryLocation::HANC { offset, .. } => offset,
AncillaryLocation::VANC { .. } => 0,
};
let anc_ext =
AncillaryHeaderExtension::new(self.config.field_id, line_number, horizontal_offset);
let header = RtpHeader {
padding: false,
extension: true,
csrc_count: 0,
marker: true,
payload_type: RTP_PAYLOAD_TYPE_ANC,
sequence_number: self.sequence_number,
timestamp,
ssrc: self.ssrc,
csrcs: Vec::new(),
extension_data: None,
};
self.sequence_number = self.sequence_number.wrapping_add(1);
Ok(AncillaryPacket::new(header, anc_ext, anc_data))
}
#[must_use]
pub const fn config(&self) -> &AncillaryConfig {
&self.config
}
}
pub struct AncillaryDecoder {
config: AncillaryConfig,
packet_buffer: HashMap<u32, Vec<AncillaryData>>,
}
impl AncillaryDecoder {
#[must_use]
pub fn new(config: AncillaryConfig) -> Self {
Self {
config,
packet_buffer: HashMap::new(),
}
}
pub fn process_rtp_packet(&mut self, rtp_packet: &RtpPacket) -> NetResult<()> {
let anc_packet = AncillaryPacket::from_rtp(rtp_packet)?;
let timestamp = anc_packet.header.timestamp;
self.packet_buffer
.entry(timestamp)
.or_insert_with(Vec::new)
.extend(anc_packet.anc_data);
Ok(())
}
pub fn get_anc_data(&mut self, timestamp: u32) -> Option<Vec<AncillaryData>> {
self.packet_buffer.remove(×tamp)
}
#[must_use]
pub const fn config(&self) -> &AncillaryConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ancillary_data_type() {
assert_eq!(AncillaryDataType::from_did(0x61).as_did(), 0x61);
assert_eq!(AncillaryDataType::Timecode.as_did(), 0x60);
}
#[test]
fn test_ancillary_data() {
let user_data = vec![0x12, 0x34, 0x56];
let location = AncillaryLocation::VANC { line_number: 10 };
let anc = AncillaryData::new(0x61, 0x01, user_data.clone(), location);
assert_eq!(anc.did, 0x61);
assert_eq!(anc.sdid, 0x01);
assert_eq!(anc.data_count, 3);
assert!(anc.validate_checksum());
}
#[test]
fn test_cea608_data() {
let cea608 = CEA608Data::new(0x80, 0x80);
let anc = cea608.to_anc(21);
assert!(anc.is_cea608());
let parsed = CEA608Data::from_anc(&anc).expect("should succeed in test");
assert_eq!(parsed.cc_data1, 0x80);
assert_eq!(parsed.cc_data2, 0x80);
}
#[test]
fn test_timecode() {
let tc = Timecode::new(10, 30, 45, 12, false);
assert_eq!(tc.format(), "10:30:45:12");
let tc_drop = Timecode::new(10, 30, 45, 12, true);
assert_eq!(tc_drop.format(), "10:30:45;12");
let anc = tc.to_anc(10);
assert!(anc.is_timecode());
let parsed = Timecode::from_anc(&anc).expect("should succeed in test");
assert_eq!(parsed, tc);
}
#[test]
fn test_afd() {
let afd = AFD::new(0x08, AspectRatio::Ratio16_9);
let anc = afd.to_anc(11);
assert_eq!(anc.did, 0x41);
assert_eq!(anc.sdid, 0x05);
}
#[test]
fn test_ancillary_encoder() {
let config = AncillaryConfig::default();
let mut encoder = AncillaryEncoder::new(config, 12345);
let user_data = vec![0x12, 0x34];
let location = AncillaryLocation::VANC { line_number: 10 };
let anc = AncillaryData::new(0x61, 0x01, user_data, location);
let packet = encoder
.encode(vec![anc], 1000)
.expect("should succeed in test");
assert_eq!(packet.header.payload_type, RTP_PAYLOAD_TYPE_ANC);
assert!(!packet.anc_data.is_empty());
}
}