1use anyhow::{Result, anyhow};
4use std::sync::RwLock;
5use codec::frame::VideoCodec;
6use codec::pixel_format::{
7 Av1SequenceHeader, H264SpsInfo, HevcSpsInfo, parse_av1_sequence_header, parse_h264_sps,
8 parse_hevc_sps,
9};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum RungCodecInvariant {
36 Av1(Av1Invariant),
37 H26x(H26xInvariant),
40}
41
42impl RungCodecInvariant {
43 pub(super) fn describe_diff(&self, other: &Self) -> String {
45 if self == other {
46 return String::new();
47 }
48 match (self, other) {
49 (RungCodecInvariant::Av1(a), RungCodecInvariant::Av1(b)) => a.describe_diff(b),
50 _ => format!("rung={self:?}, this worker={other:?}"),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct H26xInvariant {
58 pub profile_idc: u8,
59 pub level_idc: u8,
60 pub chroma_format_idc: u8,
61 pub bit_depth_luma: u8,
62 pub bit_depth_chroma: u8,
63 pub width: u32,
64 pub height: u32,
65}
66
67impl H26xInvariant {
68 fn from_h264(sps: &H264SpsInfo) -> Self {
69 Self {
70 profile_idc: sps.profile_idc,
71 level_idc: sps.level_idc,
72 chroma_format_idc: sps.chroma_format_idc,
73 bit_depth_luma: sps.bit_depth_luma,
74 bit_depth_chroma: sps.bit_depth_chroma,
75 width: sps.width.unwrap_or(0),
76 height: sps.height.unwrap_or(0),
77 }
78 }
79
80 fn from_h265(sps: &HevcSpsInfo) -> Self {
81 Self {
82 profile_idc: sps.profile_idc,
83 level_idc: sps.level_idc,
84 chroma_format_idc: sps.chroma_format_idc,
85 bit_depth_luma: sps.bit_depth_luma,
86 bit_depth_chroma: sps.bit_depth_chroma,
87 width: sps.width.unwrap_or(0),
88 height: sps.height.unwrap_or(0),
89 }
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct Av1Invariant {
107 pub seq_profile: u8,
108 pub seq_level_idx_0: u8,
109 pub seq_tier_0: u8,
110 pub bit_depth: u8,
111 pub monochrome: bool,
112 pub chroma_subsampling_x: bool,
113 pub chroma_subsampling_y: bool,
114 pub color_primaries: u8,
115 pub transfer_characteristics: u8,
116 pub matrix_coefficients: u8,
117 pub color_range: bool,
118 pub max_frame_width_minus1: u32,
119 pub max_frame_height_minus1: u32,
120 pub still_picture: bool,
121}
122
123impl Av1Invariant {
124 pub fn from_sequence_header(sh: &Av1SequenceHeader) -> Self {
125 Self {
126 seq_profile: sh.seq_profile,
127 seq_level_idx_0: sh.seq_level_idx_0,
128 seq_tier_0: sh.seq_tier_0,
129 bit_depth: sh.bit_depth,
130 monochrome: sh.monochrome,
131 chroma_subsampling_x: sh.chroma_subsampling_x,
132 chroma_subsampling_y: sh.chroma_subsampling_y,
133 color_primaries: sh.color_primaries,
134 transfer_characteristics: sh.transfer_characteristics,
135 matrix_coefficients: sh.matrix_coefficients,
136 color_range: sh.color_range,
137 max_frame_width_minus1: sh.max_frame_width_minus1,
138 max_frame_height_minus1: sh.max_frame_height_minus1,
139 still_picture: sh.still_picture,
140 }
141 }
142
143 fn describe_diff(&self, other: &Self) -> String {
145 let mut diffs = Vec::new();
146 macro_rules! diff_field {
147 ($field:ident) => {
148 if self.$field != other.$field {
149 diffs.push(format!(
150 "{}: rung={:?}, this worker={:?}",
151 stringify!($field),
152 self.$field,
153 other.$field
154 ));
155 }
156 };
157 }
158 diff_field!(seq_profile);
159 diff_field!(seq_level_idx_0);
160 diff_field!(seq_tier_0);
161 diff_field!(bit_depth);
162 diff_field!(monochrome);
163 diff_field!(chroma_subsampling_x);
164 diff_field!(chroma_subsampling_y);
165 diff_field!(color_primaries);
166 diff_field!(transfer_characteristics);
167 diff_field!(matrix_coefficients);
168 diff_field!(color_range);
169 diff_field!(max_frame_width_minus1);
170 diff_field!(max_frame_height_minus1);
171 diff_field!(still_picture);
172 diffs.join("; ")
173 }
174}
175
176#[derive(Debug)]
182pub enum InvariantCheck {
183 SetByThisWorker,
185 Matched,
187 Mismatched { diff: String },
193}
194
195pub fn validate_or_set_rung_invariant(
202 rung_idx: usize,
203 gpu_vendor: Option<codec::gpu::GpuVendor>,
204 slot: &RwLock<Option<RungCodecInvariant>>,
205 first_packet: &[u8],
206 codec: VideoCodec,
207) -> Result<InvariantCheck> {
208 let observed = match codec {
211 VideoCodec::Av1 => {
212 let parsed = parse_av1_sequence_header(first_packet).ok_or_else(|| {
213 anyhow!(
214 "rung {} (vendor {:?}): could not parse AV1 sequence header from first \
215 encoded packet; encoder did not emit OBU_SEQUENCE_HEADER as required for \
216 segment alignment",
217 rung_idx,
218 gpu_vendor,
219 )
220 })?;
221 RungCodecInvariant::Av1(Av1Invariant::from_sequence_header(&parsed))
222 }
223 VideoCodec::H264 => {
224 let sps = parse_h264_sps(first_packet).ok_or_else(|| {
225 anyhow!(
226 "rung {} (vendor {:?}): could not parse H.264 SPS from first encoded packet; \
227 encoder did not emit an SPS NAL on the first IDR",
228 rung_idx,
229 gpu_vendor,
230 )
231 })?;
232 RungCodecInvariant::H26x(H26xInvariant::from_h264(&sps))
233 }
234 VideoCodec::H265 => {
235 let sps = parse_hevc_sps(first_packet).ok_or_else(|| {
236 anyhow!(
237 "rung {} (vendor {:?}): could not parse H.265 SPS from first encoded packet; \
238 encoder did not emit an SPS NAL on the first IRAP",
239 rung_idx,
240 gpu_vendor,
241 )
242 })?;
243 RungCodecInvariant::H26x(H26xInvariant::from_h265(&sps))
244 }
245 };
246
247 if let Some(existing) = &*slot.read().unwrap() {
249 if existing == &observed {
250 return Ok(InvariantCheck::Matched);
251 }
252 return Ok(InvariantCheck::Mismatched {
253 diff: existing.describe_diff(&observed),
254 });
255 }
256 let mut w = slot.write().unwrap();
259 match &*w {
260 Some(existing) if existing != &observed => Ok(InvariantCheck::Mismatched {
261 diff: existing.describe_diff(&observed),
262 }),
263 Some(_) => Ok(InvariantCheck::Matched),
264 None => {
265 tracing::info!(
266 rung_idx,
267 gpu_vendor = ?gpu_vendor,
268 ?codec,
269 invariant = ?observed,
270 "rung codec invariant captured from first worker"
271 );
272 *w = Some(observed);
273 Ok(InvariantCheck::SetByThisWorker)
274 }
275 }
276}