pub struct TimestampOrigin { /* private fields */ }Expand description
Shifts a packet stream so that its timeline starts at zero, taking the first packet it sees as the origin.
§What it is for
A branch attached to a Tee that is already
running inherits whatever timeline the source is up to. A
D3d11VideoCompositor stamps its output with a tick counter that starts
when the compositor did, so a recording branch attached ten minutes in
receives its first frame stamped ten minutes — and a muxer, which has no
reason to disbelieve it, writes a file whose first sample is ten minutes
from the start. Players show the leading gap as empty, and a thumbnailer
asking for an early frame finds nothing there.
Nothing upstream can fix that for the branch: the compositor’s timeline is correct, and it is shared with every other branch. What is wrong is only the assumption that this branch’s timeline is the same one. Putting this between the encoder and the muxer makes the file start where the recording did.
§Where it goes
After the encoder, on packets, rather than in front of it on frames.
Frames travel as pooled references whose slots the producer reuses, so
re-stamping one means holding its pool slot for as long as the copy is
downstream — a packet carries no such tie. Placed here it also shifts
dts alongside pts, which is the pair a muxer actually reads.
§What the origin is
The first packet’s pts — where the recording begins to be seen, which
is what has to land at zero.
With a reordering encoder that is not its dts: the first packet is
decoded before it is displayed, so dts runs a reorder delay behind. Take
dts as the origin and the shifted stream starts at zero for decoding but
a frame or two above it for presentation — and a muxer writes that as
content genuinely beginning that far in. Alone in a file nothing notices.
Beside an audio track, which has no reordering and so starts at zero, the
picture is simply late by the delay: about 50 ms for H.264 with two
B-frames at 60 fps, and on the side of the sync window a listener notices
first.
So dts is allowed to go negative for those first packets, which is what
FFmpeg’s own encoders hand a muxer and what an mp4 muxer’s edit list
exists to absorb. Checked against ffmpeg -c:v h264_nvenc -c:a aac, whose
output starts pts 0.000 / dts -0.050 on the video track and 0.000 on
the audio one, with both streams reporting a start_time of zero.
§One stream at a time
Each of these establishes its own origin from its own first packet, so two
of them on the two tracks of one file agree only to within whatever
separates those first packets. That has been measured rather than left as
a worry: a flash and a beep recorded together through obs-rs, on four
runs, landed −1.7, +5.0, +8.3 and +16.7 ms apart — scatter the width of
one video frame, which is the measurement’s own resolution rather than a
systematic offset.
So a shared origin is still not here, and now for a reason: it would be a different type carrying a handle, and nothing has yet been shown to need it.
§Cost
One packet copy each, to leave the Arc this was handed untouched — a
sibling branch off the same Tee must not see this branch’s timeline.
The same copy crate::elements::FileMuxer already makes to rescale.
Implementations§
Trait Implementations§
Source§impl Element for TimestampOrigin
impl Element for TimestampOrigin
Source§fn name(&self) -> Arc<str> ⓘ
fn name(&self) -> Arc<str> ⓘ
crate::bus::BusEvent stores names as
Arc<str> for exactly this reason: a hot path like
crate::queue::Queue posting BusEvent::Dropped once per
overflowed buffer shouldn’t pay for a fresh heap allocation every
time it wants to report which element it is.Source§fn element_type(&self) -> ElementType
fn element_type(&self) -> ElementType
ElementType.Source§fn pp_log(&self) -> &PpLog
fn pp_log(&self) -> &PpLog
crate::bus::Bus::post — same
id/name as Element::name, just already wrapped as the
crate::pp_log::PpLog its pp_info!/pp_warn!/pp_error! macros need. A
stored private field, not built fresh per call, for the same reason
name() returns a cheap Arc<str> clone instead of a fresh String
— see its own docs.Source§fn pp_log_mut(&mut self) -> &mut PpLog
fn pp_log_mut(&mut self) -> &mut PpLog
Element::pp_log reads — used by
crate::pipeline::ChainBuilder to stamp the owning
crate::pipeline::Pipeline’s id onto every element that
passes through it, via element_pp_log. Not meant to be called
from anywhere else.Source§fn graph_id(&self) -> Option<ElementId>
fn graph_id(&self) -> Option<ElementId>
ChainBuilder and keep the default None implementation.Source§fn attach_context(&mut self, _context: &Arc<Context>)
fn attach_context(&mut self, _context: &Arc<Context>)
Element::pp_log_mut hands it the pipeline’s
identity: the clock, the playback clock and the bus are the
pipeline’s to give, not the caller’s to choose. Read moreSource§impl Sink for TimestampOrigin
impl Sink for TimestampOrigin
Source§fn input_contract(&self) -> InputContract
fn input_contract(&self) -> InputContract
Encoded media of either kind. It reads timestamps and nothing else, so which medium the packets carry does not matter — but frames are not what it handles, and a chain that wired them here would be wrong before it ever ran.
Source§fn consume(&mut self, buf: MediaBuffer) -> Result<()>
fn consume(&mut self, buf: MediaBuffer) -> Result<()>
Source§fn control(&mut self, msg: ControlMsg) -> Result<()>
fn control(&mut self, msg: ControlMsg) -> Result<()>
ControlMsg (pause/resume/stop) and, for anything
with a downstream of its own, forwards it on — same shape as
consume, just a separate channel from MediaBuffer so it can
reach every element (not just ones that already know how to
interpret a data buffer) and, at a crate::queue::Queue, jump
ahead of whatever data is backed up instead of waiting behind it.
No default: every Sink has to consciously decide what this means
for it, rather than silently dropping it.Source§fn ready_consume(&mut self) -> bool
fn ready_consume(&mut self) -> bool
Self::consume can make progress now. Read more