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
76 changes: 69 additions & 7 deletions crates/compositor/src/linux_decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use anyhow::{bail, Context, Result};
use std::ffi::CString;
use std::ptr;

use crate::timeline_walk::NextFrameTime;

use crate::ffi::{
av_frame_alloc, av_frame_free, av_frame_move_ref, av_frame_unref, av_packet_alloc,
av_packet_free, av_packet_unref, av_read_frame, av_seek_frame, avcodec_alloc_context3,
Expand Down Expand Up @@ -58,6 +60,11 @@ pub struct SwDecoder {
frame: *mut AVFrame,
sent_eof: bool,
cur_pts: Option<i64>,
/// Buffer de lookahead pour `peek_next_time_sec` : symétrique de
/// `pipeline_macos::Decoder::peek_frame` — cf. là-bas pour la justification.
peek_frame: *mut AVFrame,
/// `true` si `peek_frame` porte une frame décodée en attente de `commit_peek`.
has_peek: bool,
}

/// Libère toutes les ressources ffmpeg. `Drop` ne peut pas faillir ; on
Expand All @@ -78,6 +85,9 @@ impl Drop for SwDecoder {
if !self.pkt.is_null() {
av_packet_free(&mut self.pkt);
}
if !self.peek_frame.is_null() {
av_frame_free(&mut self.peek_frame);
}
}
}
}
Expand Down Expand Up @@ -177,7 +187,8 @@ impl SwDecoder {
};
let pkt = av_packet_alloc();
let frame = av_frame_alloc();
if pkt.is_null() || frame.is_null() {
let peek_frame = av_frame_alloc();
if pkt.is_null() || frame.is_null() || peek_frame.is_null() {
avcodec_free_context(&mut dec);
avformat_close_input(&mut fmt);
bail!("av_packet_alloc/av_frame_alloc (pompage sequentiel)");
Expand All @@ -192,6 +203,8 @@ impl SwDecoder {
frame,
sent_eof: false,
cur_pts: None,
peek_frame,
has_peek: false,
})
}

Expand All @@ -201,21 +214,33 @@ impl SwDecoder {
/// seek PAS : le decodeur garde son etat, donc une lecture sequentielle coute
/// UN packet par frame au lieu d'un re-parcours de demi-GOP.
pub unsafe fn next_frame(&mut self) -> Result<*mut AVFrame> {
if self.has_peek {
return self.commit_peek();
}
if !self.receive_into(self.frame)? {
return Ok(ptr::null_mut());
}
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
Ok(self.frame)
}

/// Décode dans `into` (buffer courant ou de lookahead) jusqu'à obtenir une frame ou
/// l'EOF — cf. `pipeline_macos::Decoder::receive_into` pour la justification.
unsafe fn receive_into(&mut self, into: *mut AVFrame) -> Result<bool> {
loop {
let r = avcodec_receive_frame(self.dec, self.frame);
let r = avcodec_receive_frame(self.dec, into);
if r == 0 {
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
return Ok(self.frame);
return Ok(true);
}
if r == AVERROR_EOF {
return Ok(ptr::null_mut());
return Ok(false);
}
if r != AVERROR_EAGAIN {
bail!("avcodec_receive_frame: {r}");
}
if self.sent_eof {
return Ok(ptr::null_mut());
return Ok(false);
}
let rr = av_read_frame(self.fmt, self.pkt);
if rr < 0 {
Expand All @@ -240,6 +265,41 @@ impl SwDecoder {
}
}

/// Décode la prochaine frame dans le buffer de lookahead et renvoie son temps.
/// Cf. `pipeline_macos::Decoder::peek_next_time_sec`.
pub(crate) unsafe fn peek_next_time_sec(&mut self) -> Result<NextFrameTime> {
if !self.has_peek {
if !self.receive_into(self.peek_frame)? {
return Ok(NextFrameTime::Eof);
}
self.has_peek = true;
}
let pts = (*self.peek_frame).best_effort_timestamp;
// Sans pts ni time_base exploitables on ne PEUT pas dire si la frame est due :
// `Unknown`, et non `0.0` — qui passait pour « due » à tous les coups.
Ok(if pts == i64::MIN || self.stream_timebase <= 0.0 {
NextFrameTime::Unknown
} else {
NextFrameTime::At(pts as f64 * self.stream_timebase)
})
}

/// Promeut la frame de lookahead au rang de frame courante. Cf.
/// `pipeline_macos::Decoder::commit_peek`.
pub(crate) unsafe fn commit_peek(&mut self) -> Result<*mut AVFrame> {
// `bail!` et non `debug_assert!` : compilée en release, l'assertion disparaissait
// et l'échange promouvait un `AVFrame` jamais rempli, avec un
// `best_effort_timestamp` indéterminé, jusque dans le chemin de présentation.
if !self.has_peek {
bail!("commit_peek sans peek_next_time_sec préalable");
}
std::mem::swap(&mut self.frame, &mut self.peek_frame);
self.has_peek = false;
let pts = (*self.frame).best_effort_timestamp;
self.cur_pts = if pts == i64::MIN { None } else { Some(pts) };
Ok(self.frame)
}

/// Temps source (secondes) de la derniere frame rendue par `next_frame` /
/// `decode_at`, tire du pts REEL et non d'un compteur d'index.
pub fn cur_time_sec(&self) -> Option<f64> {
Expand All @@ -263,6 +323,8 @@ impl SwDecoder {
/// `AVERROR_INVALIDDATA` plutôt que de paniquer : la prochaine itération
/// lira le packet complet suivant.
pub unsafe fn decode_at(&mut self, frame_idx: u32) -> Result<*mut AVFrame> {
// Tout seek invalide un éventuel peek en attente — cf. pipeline_macos::Decoder::seek_to.
self.has_peek = false;
let fps = self.fps;
let target_ts = (frame_idx as f64 / fps) * 1_000_000.0; // AV_TIME_BASE = µs
// `AVSEEK_FLAG_BACKWARD` vaut 1, pas 4 — 4 est `AVSEEK_FLAG_ANY`. La constante
Expand Down
Loading
Loading