mod gpu_policy;
mod hls;
mod single_file;
pub use gpu_policy::{detect_gpu_pool, gpu_pool_for_policy, policy_gpu_indices, serial_gpu_for_policy};
pub use hls::run_multigpu_hls;
pub use single_file::{RungPackets, run_multigpu_single_file};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use bytes::Bytes;
use codec::frame::{ColorMetadata, PixelFormat, VideoCodec};
use container::cmaf::CmafTrackManifest;
use container::streaming::DemuxHeader;
use crate::decode_pump::{ClipSource, DecodePumpConfig};
use crate::gpu_pool::GpuPool;
use crate::progress::{ProgressSink, RungProgress, RungStatus};
use crate::spec::Rung;
pub(super) const QUEUE_CAPACITY: usize = 2;
pub(super) const FANOUT_CHANNEL_CAPACITY: usize = 4;
pub(super) const HELPER_POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(200);
pub(super) const PROGRESS_TICK: std::time::Duration = std::time::Duration::from_millis(500);
#[derive(Debug, Clone)]
pub struct RungManifest {
pub rung_index: usize,
pub width: u32,
pub height: u32,
pub label: String,
pub relative_dir: String,
pub manifest: CmafTrackManifest,
}
pub struct MultiGpuParams<'a> {
pub input: Bytes,
pub codec: VideoCodec,
pub rungs: &'a [Rung],
pub header: DemuxHeader,
pub source_color_metadata: ColorMetadata,
pub source_pixel_format: PixelFormat,
pub tonemap_to_sdr: bool,
pub output_color_metadata: ColorMetadata,
pub output_pixel_format: PixelFormat,
pub needs_downsample: bool,
pub filters: Arc<codec::filter::FilterChain>,
pub frame_rate: f64,
pub gpu_pool: Arc<GpuPool>,
pub gpu_indices: Vec<u32>,
pub decode_gpu: Option<u32>,
pub output_root: PathBuf,
pub timescale: u32,
pub per_frame_ticks: u32,
pub keyframe_interval: u32,
pub segment_target_ticks: u64,
pub total_input_frames: u64,
pub constant_qp: bool,
pub spliced_clips: Vec<ClipSource>,
}
impl MultiGpuParams<'_> {
pub(super) fn decode_gpu_for(&self, i: usize) -> Option<u32> {
if self.decode_gpu.is_some() {
return self.decode_gpu;
}
if self.gpu_indices.is_empty() {
return None;
}
Some(self.gpu_indices[i % self.gpu_indices.len()])
}
pub(super) fn clip_sources_for(&self, gpu: Option<u32>) -> Vec<ClipSource> {
if self.spliced_clips.is_empty() {
return vec![ClipSource {
cfg: DecodePumpConfig {
codec_name: self.header.codec.clone(),
info_for_decoder: self.header.info.clone(),
source_color_metadata: self.source_color_metadata,
source_pixel_format: self.source_pixel_format,
needs_downsample: self.needs_downsample,
tonemap_to_sdr: self.tonemap_to_sdr,
gpu_index: gpu,
filters: self.filters.clone(),
},
input: self.input.clone(),
start_frame: 0,
end_frame: None,
}];
}
self.spliced_clips
.iter()
.map(|c| ClipSource {
cfg: DecodePumpConfig { gpu_index: gpu, ..c.cfg.clone() },
input: c.input.clone(),
start_frame: c.start_frame,
end_frame: c.end_frame,
})
.collect()
}
}
#[derive(Clone)]
pub(super) struct WorkerCtx {
pub(super) codec: VideoCodec,
pub(super) frame_rate: f64,
pub(super) output_color_metadata: ColorMetadata,
pub(super) output_pixel_format: PixelFormat,
pub(super) timescale: u32,
pub(super) per_frame_ticks: u32,
pub(super) keyframe_interval: u32,
pub(super) segment_target_ticks: u64,
pub(super) output_root: PathBuf,
pub(super) constant_qp: bool,
}
pub(super) fn spawn_progress_reporter(
rungs: Vec<Rung>,
frames_encoded: Vec<Arc<AtomicU64>>,
finalized: Arc<Vec<AtomicBool>>,
total_input_frames: u64,
sink: Arc<dyn ProgressSink>,
stop: Arc<AtomicBool>,
) -> tokio::task::JoinHandle<()> {
tokio::spawn(async move {
loop {
if stop.load(Ordering::Acquire) {
break;
}
tokio::time::sleep(PROGRESS_TICK).await;
for (idx, rung) in rungs.iter().enumerate() {
if finalized[idx].load(Ordering::Acquire) {
continue;
}
let done = frames_encoded[idx].load(Ordering::Relaxed);
report(
sink.as_ref(),
idx,
rung,
RungStatus::Running,
done,
Some(total_input_frames),
0,
0,
None,
);
}
}
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn report(
sink: &dyn ProgressSink,
rung_index: usize,
rung: &Rung,
status: RungStatus,
frames_done: u64,
frames_total: Option<u64>,
segments: u32,
bytes_out: u64,
message: Option<String>,
) {
let percent = match status {
RungStatus::Completed => 100.0,
RungStatus::Pending => 0.0,
_ => match frames_total {
Some(t) if t > 0 => ((frames_done as f32 / t as f32) * 100.0).min(99.0),
_ => 1.0,
},
};
sink.on_rung(RungProgress {
rung_index,
label: rung.label.clone(),
width: rung.width,
height: rung.height,
status,
percent,
frames_done,
frames_total,
segments_written: segments,
bytes_out,
message,
});
}