1use anyhow::{Context, Result};
4use std::sync::Arc;
5use tokio::sync::mpsc;
6use codec::encode::{self, EncoderConfig};
7use codec::frame::ColorMetadata;
8use container::cmaf::{CmafVideoMuxer, CmafVideoMuxerOptions, SegmentInfo};
9use crate::cmaf_util::add_packet_with_segment_flush;
10use crate::frame_queue::{SegmentChunk, SegmentChunkQueue};
11use super::{EncoderWorkerConfig, WorkerOutput, InvariantCheck, validate_or_set_rung_invariant};
12
13#[allow(clippy::too_many_arguments)]
21pub fn run_encoder_worker_blocking(
22 cfg: EncoderWorkerConfig,
23 queue: Arc<SegmentChunkQueue>,
24 rt: tokio::runtime::Handle,
25 shared_frames_encoded: Arc<std::sync::atomic::AtomicU64>,
26 progress_tx: mpsc::Sender<u64>,
27) -> Result<WorkerOutput> {
28 let enc_config = super::build_enc_config(&cfg);
29 let encoder_color_metadata = cfg.output_color_metadata;
30
31 let mut segments_written: Vec<SegmentInfo> = Vec::new();
32 let mut init_segment_written = false;
33
34 tracing::debug!(rung_idx = cfg.rung_idx, gpu_index = ?cfg.gpu_index, "encoder worker started; awaiting first chunk");
35 loop {
36 let chunk = match rt.block_on(queue.pop()) {
37 Some(c) => c,
38 None => break,
39 };
40 tracing::debug!(rung_idx = cfg.rung_idx, segment = chunk.segment_idx, frames = chunk.frames.len(), "encoder worker popped chunk");
41 match encode_one_segment(
42 &cfg,
43 &enc_config,
44 encoder_color_metadata,
45 chunk,
46 &mut init_segment_written,
47 &shared_frames_encoded,
48 &progress_tx,
49 )? {
50 SegmentOutcome::Wrote {
51 info,
52 segment_idx,
53 frames,
54 } => {
55 let role = if segment_idx == 0 {
56 "primary"
57 } else {
58 "worker"
59 };
60 tracing::info!(
61 rung_idx = cfg.rung_idx,
62 gpu_index = ?cfg.gpu_index,
63 role,
64 segment = segment_idx,
65 frames_encoded = frames,
66 "rung segment flushed",
67 );
68 segments_written.push(info);
69 }
70 SegmentOutcome::RequeuedOnMismatch {
71 chunk: rejected,
72 diff,
73 } => {
74 tracing::warn!(
80 rung_idx = cfg.rung_idx,
81 gpu_index = ?cfg.gpu_index,
82 gpu_vendor = ?cfg.gpu_vendor,
83 rejected_segment = rejected.segment_idx,
84 diff = %diff,
85 "encoder worker: codec invariant mismatch on first packet — \
86 requeuing chunk for a matching-vendor worker and exiting",
87 );
88 let _ = queue.push_front(rejected);
89 break;
90 }
91 }
92 }
93
94 Ok(WorkerOutput {
95 gpu_index: cfg.gpu_index,
96 segments: segments_written,
97 })
98}
99
100enum SegmentOutcome {
104 Wrote {
105 info: SegmentInfo,
106 segment_idx: usize,
107 frames: usize,
108 },
109 RequeuedOnMismatch {
110 chunk: SegmentChunk,
111 diff: String,
112 },
113}
114
115fn encode_one_segment(
116 cfg: &EncoderWorkerConfig,
117 enc_config: &EncoderConfig,
118 encoder_color_metadata: ColorMetadata,
119 chunk: SegmentChunk,
120 init_segment_written: &mut bool,
121 shared_frames_encoded: &std::sync::atomic::AtomicU64,
122 progress_tx: &mpsc::Sender<u64>,
123) -> Result<SegmentOutcome> {
124 let write_init = chunk.segment_idx == 0 && !*init_segment_written;
125 let muxer_options = CmafVideoMuxerOptions {
126 first_segment_index: (chunk.segment_idx as u32) + 1,
127 first_segment_base_decode_time: chunk.segment_idx as u64 * cfg.segment_target_ticks,
128 write_init_segment: write_init,
129 };
130 let mut muxer = CmafVideoMuxer::new_with_codec_options(
131 &cfg.output_dir,
132 cfg.width,
133 cfg.height,
134 cfg.timescale,
135 encoder_color_metadata,
136 cfg.codec,
137 muxer_options,
138 )
139 .with_context(|| {
140 format!(
141 "creating CmafVideoMuxer for segment {} in {}",
142 chunk.segment_idx,
143 cfg.output_dir.display()
144 )
145 })?;
146
147 let mut encoder =
148 encode::select_encoder(enc_config.clone(), None).context("creating encoder for segment")?;
149
150 let mut pending_packets: Vec<codec::encode::EncodedPacket> = Vec::new();
155 let mut first_packet_decision: Option<bool> = None; let segment_idx = chunk.segment_idx;
158 let frame_count = chunk.frames.len();
159
160 for frame in &chunk.frames {
161 encoder
162 .send_frame(frame)
163 .context("encoder.send_frame in worker")?;
164 while let Some(packet) = encoder
165 .receive_packet()
166 .context("encoder.receive_packet in worker")?
167 {
168 if first_packet_decision.is_none() {
169 match validate_or_set_rung_invariant(
170 cfg.rung_idx,
171 cfg.gpu_vendor,
172 &cfg.rung_invariant,
173 &packet.data,
174 cfg.codec,
175 )? {
176 InvariantCheck::Matched | InvariantCheck::SetByThisWorker => {
177 first_packet_decision = Some(true);
178 }
179 InvariantCheck::Mismatched { diff } => {
180 return Ok(SegmentOutcome::RequeuedOnMismatch { chunk, diff });
187 }
188 }
189 pending_packets.push(packet);
190 continue;
191 }
192 if !pending_packets.is_empty() {
196 for held in pending_packets.drain(..) {
197 add_packet_with_segment_flush(
198 &mut muxer,
199 &held,
200 cfg.per_frame_ticks,
201 cfg.segment_target_ticks,
202 )
203 .context("CMAF segment-flush add (held)")?;
204 }
205 }
206 add_packet_with_segment_flush(
207 &mut muxer,
208 &packet,
209 cfg.per_frame_ticks,
210 cfg.segment_target_ticks,
211 )
212 .context("CMAF segment-flush add (worker)")?;
213 }
214 let n = shared_frames_encoded.fetch_add(1, std::sync::atomic::Ordering::AcqRel) + 1;
215 let _ = progress_tx.try_send(n);
216 }
217
218 if first_packet_decision == Some(true) && !pending_packets.is_empty() {
221 for held in pending_packets.drain(..) {
222 add_packet_with_segment_flush(
223 &mut muxer,
224 &held,
225 cfg.per_frame_ticks,
226 cfg.segment_target_ticks,
227 )
228 .context("CMAF segment-flush add (final-held)")?;
229 }
230 }
231
232 encoder.flush().context("encoder.flush in worker")?;
233 while let Some(packet) = encoder
234 .receive_packet()
235 .context("encoder.receive_packet after flush")?
236 {
237 add_packet_with_segment_flush(
238 &mut muxer,
239 &packet,
240 cfg.per_frame_ticks,
241 cfg.segment_target_ticks,
242 )
243 .context("CMAF segment-flush add post-flush (worker)")?;
244 }
245
246 let manifest = muxer
247 .finalize()
248 .context("finalize CmafVideoMuxer (per-segment worker)")?;
249
250 if write_init {
251 *init_segment_written = true;
252 }
253
254 let info = manifest
255 .segments
256 .last()
257 .ok_or_else(|| {
258 anyhow::anyhow!(
259 "encoder worker produced no segment for chunk idx {} (rung {}, gpu {:?}); \
260 frames in chunk = {}",
261 segment_idx,
262 cfg.rung_idx,
263 cfg.gpu_index,
264 frame_count,
265 )
266 })?
267 .clone();
268 Ok(SegmentOutcome::Wrote {
269 info,
270 segment_idx,
271 frames: frame_count,
272 })
273}