use core::fmt;
use std::net::Ipv4Addr;
use crate::wire::{
DeviceUid, MxrSignalType, V2IP_AUDIO_DEFAULT_CHANNELS, V2IP_AUDIO_DEFAULT_SAMPLE_RATE,
V2IP_DSCP_MAX, V2IP_DSCP_SET,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StreamKind {
#[default]
Video,
Audio,
Anc,
Arc,
}
impl fmt::Display for StreamKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Video => "video",
Self::Audio => "audio",
Self::Anc => "anc",
Self::Arc => "arc",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct V2ipStreamSource {
pub kind: StreamKind,
pub ip: Ipv4Addr,
pub port: u16,
}
impl Default for V2ipStreamSource {
fn default() -> Self {
Self {
kind: StreamKind::default(),
ip: Ipv4Addr::UNSPECIFIED,
port: 0,
}
}
}
impl V2ipStreamSource {
pub const fn is_valid(&self) -> bool {
self.ip.is_multicast() && self.port != 0
}
}
impl fmt::Display for V2ipStreamSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}={}:{}", self.kind, self.ip, self.port)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipStreamSources {
pub uid: DeviceUid,
pub video: V2ipStreamSource,
pub audio: V2ipStreamSource,
pub anc: V2ipStreamSource,
pub arc: Option<V2ipStreamSource>,
}
impl fmt::Display for V2ipStreamSources {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"video:{} audio:{} anc:{}",
self.video, self.audio, self.anc
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct V2ipRouteTarget {
pub ip: Ipv4Addr,
pub port: u16,
}
impl Default for V2ipRouteTarget {
fn default() -> Self {
Self {
ip: Ipv4Addr::UNSPECIFIED,
port: 0,
}
}
}
impl V2ipRouteTarget {
pub const fn new(ip: Ipv4Addr) -> Self {
Self { ip, port: 0 }
}
pub(crate) const fn port_or(self, standard: u16) -> u16 {
if self.port == 0 {
standard
} else {
self.port
}
}
}
impl fmt::Display for V2ipRouteTarget {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.ip, self.port)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipRoute {
pub video: V2ipRouteTarget,
pub audio: V2ipRouteTarget,
pub anc: V2ipRouteTarget,
}
impl V2ipRoute {
pub fn of(sources: &V2ipStreamSources) -> Self {
let target = |s: &V2ipStreamSource| V2ipRouteTarget {
ip: s.ip,
port: s.port,
};
Self {
video: target(&sources.video),
audio: target(&sources.audio),
anc: target(&sources.anc),
}
}
}
impl fmt::Display for V2ipRoute {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"video:{} audio:{} anc:{}",
self.video, self.audio, self.anc
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipAudioFormat {
pub sample_rate: u32,
pub channels: u8,
}
impl V2ipAudioFormat {
pub const STANDARD: Self = Self {
sample_rate: V2IP_AUDIO_DEFAULT_SAMPLE_RATE,
channels: V2IP_AUDIO_DEFAULT_CHANNELS,
};
pub(crate) fn wire(&self) -> [u8; 8] {
let r = self.sample_rate.to_le_bytes();
[r[0], r[1], r[2], r[3], self.channels, 0, 0, 0]
}
}
impl fmt::Display for V2ipAudioFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}Hz/{}ch", self.sample_rate, self.channels)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipScalingSettings {
pub mode: MxrSignalType,
pub refresh: u16,
pub flags: u8,
}
pub const SCALING_FLAG_MODE_VALID: u8 = 1 << 0;
pub const SCALING_FLAG_OPTIONS_VALID: u8 = 1 << 1;
pub const SCALING_FLAG_AUTO_SCALING: u8 = 1 << 7;
pub const SCALING_FLAGS_DEFINED: u8 =
SCALING_FLAG_MODE_VALID | SCALING_FLAG_OPTIONS_VALID | SCALING_FLAG_AUTO_SCALING;
impl V2ipScalingSettings {
#[must_use]
pub fn merge(self, previous: Self) -> Self {
let mut out = previous;
if self.flags & SCALING_FLAG_MODE_VALID != 0 {
out.mode = self.mode;
out.refresh = self.refresh;
out.flags |= SCALING_FLAG_MODE_VALID;
}
if self.flags & SCALING_FLAG_OPTIONS_VALID != 0 {
out.flags &= !SCALING_FLAG_AUTO_SCALING;
out.flags |= SCALING_FLAG_OPTIONS_VALID;
out.flags |= self.flags & SCALING_FLAG_AUTO_SCALING;
}
out
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipDscpConfig {
pub video: Option<u8>,
pub audio: Option<u8>,
pub anc: Option<u8>,
}
impl V2ipDscpConfig {
pub const fn is_complete(&self) -> bool {
self.video.is_some() && self.audio.is_some() && self.anc.is_some()
}
}
impl fmt::Display for V2ipDscpConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.video, self.audio, self.anc) {
(Some(v), Some(a), Some(n)) => write!(f, "video:{v} audio:{a} anc:{n}"),
_ => f.write_str("no marking"),
}
}
}
pub(crate) fn parse_dscp(raw: u8) -> Option<u8> {
(raw & V2IP_DSCP_SET != 0).then_some(raw & V2IP_DSCP_MAX)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DeviceV2ipDetails {
pub video: V2ipStreamSource,
pub audio: V2ipStreamSource,
pub anc: V2ipStreamSource,
pub arc: V2ipStreamSource,
pub tx_rate: Option<u8>,
pub dscp: V2ipDscpConfig,
pub scaling: V2ipScalingSettings,
}
impl DeviceV2ipDetails {
pub const fn source_is_valid(&self) -> bool {
self.video.is_valid() && self.anc.is_valid()
}
#[must_use]
pub fn merge(mut self, previous: Option<Self>) -> Self {
let Some(previous) = previous else {
return self;
};
if !self.source_is_valid() {
self.video = previous.video;
self.audio = previous.audio;
self.anc = previous.anc;
}
if !self.arc.is_valid() {
self.arc = previous.arc;
}
if self.tx_rate.is_none() {
self.tx_rate = previous.tx_rate;
}
if self.dscp.video.is_none() {
self.dscp = previous.dscp;
}
self.scaling = self.scaling.merge(previous.scaling);
self
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DeviceV2ipSink {
pub addresses: V2ipStreamSources,
pub audio_fmt: Option<V2ipAudioFormat>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipTxStats {
pub video: u32,
pub audio: u32,
pub anc: u32,
pub stream_down: u32,
pub overflow: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct V2ipDecoderState(u8);
impl V2ipDecoderState {
pub const UNKNOWN: Self = Self(0);
pub const HEALTHY: Self = Self(1);
pub const BAD: Self = Self(2);
pub const STARTING: Self = Self(3);
pub const fn from_wire(value: u8) -> Self {
Self(value)
}
pub const fn to_wire(self) -> u8 {
self.0
}
pub const fn is_settled(self) -> bool {
matches!(self, Self::HEALTHY | Self::BAD)
}
}
impl fmt::Display for V2ipDecoderState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::UNKNOWN => f.write_str("Unknown"),
Self::HEALTHY => f.write_str("Healthy"),
Self::BAD => f.write_str("Bad"),
Self::STARTING => f.write_str("Starting"),
Self(v) => write!(f, "state {v}"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipRxStats {
pub video_total: u32,
pub video_dropped: u32,
pub video_seq_errors: u32,
pub wdt_timeout: u32,
pub audio_total: u32,
pub audio_dropped: u32,
pub audio_seq_errors: u32,
pub anc_total: u32,
pub anc_dropped: u32,
pub anc_seq_errors: u32,
pub decoder_state: V2ipDecoderState,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct V2ipDecoderReason(u8);
impl V2ipDecoderReason {
pub const OK: Self = Self(0);
pub const NO_PACKETS: Self = Self(1);
pub const PACKETS_DEGRADED: Self = Self(2);
pub const NO_FORMAT: Self = Self(3);
pub const FORMAT_MISMATCH: Self = Self(4);
pub const FORMAT_REJECTED: Self = Self(5);
pub const DECODER_BLOCKED: Self = Self(6);
pub const SWITCH_PENDING: Self = Self(7);
pub const PTP_UNLOCKED: Self = Self(8);
pub const TX_BRIDGE_UNLOCKED: Self = Self(9);
pub const IDLE: Self = Self(10);
pub const fn from_wire(value: u8) -> Self {
Self(value)
}
pub const fn to_wire(self) -> u8 {
self.0
}
}
impl fmt::Display for V2ipDecoderReason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::OK => f.write_str("ok"),
Self::NO_PACKETS => f.write_str("no packets"),
Self::PACKETS_DEGRADED => f.write_str("packets degraded"),
Self::NO_FORMAT => f.write_str("no format recovered"),
Self::FORMAT_MISMATCH => f.write_str("format mismatch"),
Self::FORMAT_REJECTED => f.write_str("format rejected"),
Self::DECODER_BLOCKED => f.write_str("decoder blocked"),
Self::SWITCH_PENDING => f.write_str("switch pending"),
Self::PTP_UNLOCKED => f.write_str("PTP unlocked"),
Self::TX_BRIDGE_UNLOCKED => f.write_str("TX bridge unlocked"),
Self::IDLE => f.write_str("idle"),
Self(v) => write!(f, "reason {v}"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct V2ipDecoderFormat(u16);
impl V2ipDecoderFormat {
pub const RGB: Self = Self(0);
pub const YCBCR_444: Self = Self(1);
pub const YCBCR_422: Self = Self(2);
pub const YCBCR_420: Self = Self(3);
pub const UNNAMED: Self = Self(255);
pub const fn from_wire(value: u16) -> Self {
Self(value)
}
pub const fn to_wire(self) -> u16 {
self.0
}
}
impl fmt::Display for V2ipDecoderFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Self::RGB => f.write_str("RGB"),
Self::YCBCR_444 => f.write_str("YCbCr 4:4:4"),
Self::YCBCR_422 => f.write_str("YCbCr 4:2:2"),
Self::YCBCR_420 => f.write_str("YCbCr 4:2:0"),
Self::UNNAMED => f.write_str("unnamed"),
Self(v) => write!(f, "format {v}"),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipDecoderReport {
pub reason: V2ipDecoderReason,
pub blocking: bool,
pub width: u16,
pub height: u16,
pub format: V2ipDecoderFormat,
pub updates: u16,
pub flags: u32,
pub blocked_count: u32,
}
impl V2ipDecoderReport {
pub const fn has_geometry(&self) -> bool {
self.width != 0 && self.height != 0
}
pub const fn has_cause(&self, reason: V2ipDecoderReason) -> bool {
let bit = reason.to_wire();
bit > 0 && bit < u32::BITS as u8 && self.flags & (1 << bit) != 0
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum V2ipDecoderDetail {
#[default]
Absent,
NeverAnswered,
Answered(V2ipDecoderReport),
}
impl V2ipDecoderDetail {
pub const fn reading(self) -> Option<V2ipDecoderReport> {
match self {
Self::Answered(report) => Some(report),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct V2ipDeviceStats {
pub tx: V2ipTxStats,
pub tx_per_minute: V2ipTxStats,
pub rx: V2ipRxStats,
pub rx_per_minute: V2ipRxStats,
pub decoder: V2ipDecoderDetail,
}