Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/compositor/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ pub fn averr(ret: i32, ctx: &str) -> anyhow::Result<()> {
Ok(())
}

/// Étiquette couleur d'un encodeur d'export, à poser avant `avcodec_open2` : l'encodeur
/// l'écrit dans le VUI, `avcodec_parameters_from_context` la recopie et le muxer MP4 en
/// fait l'atome `colr`. Le compositeur rend du sRGB, d'où primaires et transfert BT.709 ;
/// `matrix` est celle de la conversion RGB → YUV de la plateforme, en plage limitée.
///
/// Sans elle le fichier ne dit rien et chaque lecteur devine : ffmpeg suppose BT.601 et
/// décalait jusqu'à 22 niveaux les couleurs saturées d'un export macOS en BT.709.
pub unsafe fn tag_export_color(ctx: *mut AVCodecContext, matrix: AVColorSpace::Type) {
(*ctx).color_primaries = AVColorPrimaries::AVCOL_PRI_BT709;
(*ctx).color_trc = AVColorTransferCharacteristic::AVCOL_TRC_BT709;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
(*ctx).colorspace = matrix;
(*ctx).color_range = AVColorRange::AVCOL_RANGE_MPEG;
}

#[cfg(test)]
mod tests {
/// Les constantes Rust doivent valoir EXACTEMENT ce que les macros ffmpeg valent
Expand Down
36 changes: 36 additions & 0 deletions crates/compositor/src/pipeline_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ impl VideoEncoder {
Self::tune_openh264(ctx, name);
// MP4 : header global dans l'extradata (pas par-paquet).
(*ctx).flags |= AV_CODEC_FLAG_GLOBAL_HEADER as i32;
// `vk_shaders/yuv.wgsl` : BT.601 limité.
tag_export_color(ctx, AVColorSpace::AVCOL_SPC_SMPTE170M);
if let Err(e) = averr(avcodec_open2(ctx, enc, ptr::null_mut()), "avcodec_open2(enc)") {
avcodec_free_context(&mut ctx);
return Err(e);
Expand Down Expand Up @@ -1427,6 +1429,8 @@ impl VaapiEncoder {
// muxer recupere ce qu'il trouve — mais un lecteur qui se fie a
// `codecpar` seul aurait de quoi echouer.
(*me.ctx).flags |= AV_CODEC_FLAG_GLOBAL_HEADER as i32;
// La matrice des frames que `send_dmabuf` pose, celle de `yuv.wgsl`.
tag_export_color(me.ctx, AVColorSpace::AVCOL_SPC_SMPTE170M);
// Avec la copie, les surfaces de SORTIE du VPP, pas celles du mapping :
// c'est elles que l'encodeur recoit.
let out_frames = if me.graph.is_null() {
Expand Down Expand Up @@ -1983,6 +1987,8 @@ mod tests {
qmin: i32,
qmax: i32,
gop_size: i32,
/// Primaires, transfert, matrice, plage de `codecpar`, ce que le muxer écrit.
color: (i32, i32, i32, i32),
}

/// Encode `n` images 320x180 par le chemin RÉEL de l'export — `VideoEncoder::open`,
Expand All @@ -2007,12 +2013,22 @@ mod tests {
qmin: 0,
qmax: 0,
gop_size: 0,
color: (0, 0, 0, 0),
};
unsafe {
use crate::ffi::*;
out.qmin = (*enc.ctx).qmin;
out.qmax = (*enc.ctx).qmax;
out.gop_size = (*enc.ctx).gop_size;
let mut par = avcodec_parameters_alloc();
avcodec_parameters_from_context(par, enc.ctx);
out.color = (
(*par).color_primaries as i32,
(*par).color_trc as i32,
(*par).color_space as i32,
(*par).color_range as i32,
);
avcodec_parameters_free(&mut par);
let (ptr, len) = ((*enc.ctx).extradata, (*enc.ctx).extradata_size);
if !ptr.is_null() && len > 0 {
out.extradata = std::slice::from_raw_parts(ptr, len as usize).to_vec();
Expand Down Expand Up @@ -2087,6 +2103,26 @@ mod tests {
/// image clé pour tout le fichier — mesuré 1 image I sur 300 avant ce correctif.
/// Le MP4 reste lisible, mais tout seek redécode depuis le début et un paquet
/// abîmé emporte le reste. C'est le test qui aurait attrapé ça.
/// Le MP4 dit sa matrice : BT.601, celle de `yuv.wgsl`. Sans étiquette un
/// lecteur qui suppose BT.709 décale les couleurs saturées.
#[test]
fn l_export_h264_porte_son_etiquette_couleur() {
let Some(enc) = encode(10, 5) else {
eprintln!("libopenh264 indisponible — test sauté");
return;
};
use crate::ffi::*;
assert_eq!(
enc.color,
(
AVColorPrimaries::AVCOL_PRI_BT709 as i32,
AVColorTransferCharacteristic::AVCOL_TRC_BT709 as i32,
AVColorSpace::AVCOL_SPC_SMPTE170M as i32,
AVColorRange::AVCOL_RANGE_MPEG as i32,
)
);
}

#[test]
fn l_export_h264_emet_des_images_cles_periodiques() {
let Some(enc) = encode(10, 45) else {
Expand Down
2 changes: 2 additions & 0 deletions crates/compositor/src/pipeline_macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,8 @@ impl VideoEncoder {
(*ctx).framerate = crate::ffi::AVRational { num: fps, den: 1 };
(*ctx).bit_rate = bit_rate;
(*ctx).flags |= crate::ffi::AV_CODEC_FLAG_GLOBAL_HEADER as i32;
// `rgb2y` / `rgb2uv` de `shaders.metal` : BT.709 limité.
crate::ffi::tag_export_color(ctx, crate::ffi::AVColorSpace::AVCOL_SPC_BT709);

// VT : on attache le hw_frames_ctx. En pratique le call-site (run_composited_multi)
// nous passera un `hw_frames_ctx` pré-construit lié au même device VideoToolbox
Expand Down
36 changes: 36 additions & 0 deletions crates/compositor/src/pipeline_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1553,6 +1553,8 @@ impl VideoEncoder {
// d'ajouter n'émettent pas d'extradata sans ce drapeau, et personne ici n'a de machine
// pour constater le MP4 bancal qui en sortirait.
(*ctx).flags |= AV_CODEC_FLAG_GLOBAL_HEADER as i32;
// `rgb2y` / `rgb2uv` de `shaders.hlsl` : BT.709 limité.
crate::ffi::tag_export_color(ctx, AVColorSpace::AVCOL_SPC_BT709);
if pix_fmt == AVPixelFormat::AV_PIX_FMT_D3D11 {
(*ctx).hw_frames_ctx = av_buffer_ref(hw_frames);
}
Expand Down Expand Up @@ -2008,6 +2010,40 @@ mod tests {
}
}

/// Le MP4 dit sa matrice : BT.709, celle de `shaders.hlsl`. Sans étiquette ffmpeg
/// suppose BT.601 et décale les couleurs saturées. Sans pool D3D11 c'est un candidat
/// en mémoire système qui ouvre, par le même `try_open` que l'export.
#[test]
fn export_encoder_tags_its_colour() {
let opened = unsafe {
VideoEncoder::open(&ExportCodec::H264, 320, 180, 30, 1_000_000, ptr::null_mut())
};
let Ok(enc) = opened else {
eprintln!("aucun encodeur H.264 en mémoire système ici, test sauté");
return;
};
unsafe {
let mut par = avcodec_parameters_alloc();
avcodec_parameters_from_context(par, enc.ctx);
let color = (
(*par).color_primaries,
(*par).color_trc,
(*par).color_space,
(*par).color_range,
);
avcodec_parameters_free(&mut par);
assert_eq!(
color,
(
AVColorPrimaries::AVCOL_PRI_BT709,
AVColorTransferCharacteristic::AVCOL_TRC_BT709,
AVColorSpace::AVCOL_SPC_BT709,
AVColorRange::AVCOL_RANGE_MPEG,
)
);
}
}

#[test]
fn hardware_prerequisite_classification() {
use windows::Win32::Foundation::{E_INVALIDARG, E_OUTOFMEMORY, E_UNEXPECTED};
Expand Down
Loading