Skip to main content

alpha2_native/
mux.rs

1//! The transactional MP4 the GPU-resident and packet-copy exports mux into.
2//!
3//! Nothing here depends on where the video packets come from: a hardware
4//! session hands over what its encoder produced, a packet copy what it read
5//! from its source. This writes them beside an audio stream that is either
6//! AAC encoded on its own thread or copied packets. Every such export
7//! therefore shares one partial-file contract: only a successful
8//! [`Mp4Muxer::close`] renames the partial file into place, and anything else
9//! removes it.
10
11use std::collections::VecDeque;
12use std::fs;
13use std::path::{Path, PathBuf};
14
15use alpha2_ffmpeg_bridge::OutputFile;
16use alpha2_media::EncodeError;
17use rsmpeg::avcodec::{AVCodec, AVCodecContext, AVCodecParameters, AVPacket};
18use rsmpeg::avutil::{AVChannelLayout, AVRational, ra};
19use rsmpeg::ffi;
20
21use crate::aac::{AacInput, encoder_sample_rate};
22use crate::convert::{encode_adapter_error, encode_adapter_error_at};
23use crate::encode::{
24    AudioCommand, AudioEncodeThread, AudioEncoderConfig, EncodeStageTimings, QueuedPacket,
25    spawn_audio_encoder, timed,
26};
27use crate::partial::PartialOutput;
28
29/// The AAC stream an export encodes beside its video.
30#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31pub struct AacStreamConfig {
32    /// Project audio sample rate.
33    pub sample_rate: u32,
34    /// Channel count.
35    pub channels: u16,
36    /// Resolved bitrate in bits per second; the host resolves the level.
37    pub bit_rate: u32,
38}
39
40/// The video stream an [`Mp4Muxer`] carries.
41pub(crate) struct VideoStream {
42    /// Its codec parameters, sample description included.
43    pub(crate) parameters: AVCodecParameters,
44    /// The time base its packets arrive in.
45    pub(crate) time_base: AVRational,
46    /// The parameters were read from another file, whose container codec tag
47    /// is not necessarily MP4's.
48    pub(crate) copied: bool,
49}
50
51/// The audio stream an [`Mp4Muxer`] carries.
52pub(crate) enum AudioStream {
53    /// Encoded here, from audio blocks.
54    Encode(AacStreamConfig),
55    /// Packets copied from another file.
56    Copy {
57        /// Their codec parameters.
58        parameters: AVCodecParameters,
59        /// The time base they arrive in.
60        time_base: AVRational,
61    },
62}
63
64/// The audio half of a muxed export.
65enum MuxedAudioTrack {
66    Encoded(MuxedAudio),
67    Copied {
68        stream_index: i32,
69        source_time_base: AVRational,
70        stream_time_base: AVRational,
71    },
72}
73
74/// A transactional MP4 with one video stream and at most one audio stream.
75pub(crate) struct Mp4Muxer {
76    final_path: PathBuf,
77    partial_path: PathBuf,
78    /// Held until the partial file is renamed into place or removed.
79    _partial_lock: crate::partial::PartialLock,
80    format: Option<OutputFile>,
81    stream_index: i32,
82    /// The container's time base for the video stream; the encoder's until
83    /// a deferred header is written.
84    stream_time_base: AVRational,
85    complete: bool,
86    audio: Option<MuxedAudioTrack>,
87    audio_stream_index: i32,
88    /// Finished AAC packets collected from the audio thread, on their way to
89    /// the muxer. Before a deferred header they wait here.
90    audio_packets: VecDeque<QueuedPacket>,
91    /// The header [`Self::open_deferred`] holds back until the first video
92    /// packet; `None` once it is written, or for a header written at open.
93    pending_header: Option<PendingHeader>,
94    /// The parameter sets a deferred header was written with, which every
95    /// set the encoder repeats must equal; `None` for a header written at
96    /// open.
97    parameter_sets: Option<Vec<u8>>,
98    /// The time base the audio thread rescales into, and the audio stream's,
99    /// when the thread started before a deferred header fixed the latter.
100    audio_rescale: Option<(AVRational, AVRational)>,
101}
102
103/// An AAC stream encoded on its own thread.
104struct MuxedAudio {
105    thread: AudioEncodeThread,
106    input: AacInput,
107}
108
109impl Mp4Muxer {
110    /// Opens a transactional MP4 beside `path` for an encoder's stream.
111    ///
112    /// `video` is the opened encoder, whose parameters describe the stream;
113    /// it must have opened with global headers, because an MP4 carries the
114    /// parameter sets in its sample description rather than in-band.
115    ///
116    /// # Errors
117    /// As [`Self::open_streams`].
118    // Used by the Vulkan and VideoToolbox backends; the D3D12VA one opens [`Self::open_deferred`].
119    #[cfg_attr(
120        not(all(feature = "hwencode", any(target_os = "linux", target_os = "macos"))),
121        allow(dead_code)
122    )]
123    pub(crate) fn open(
124        path: &Path,
125        video: &AVCodecContext,
126        video_time_base: AVRational,
127        audio: Option<AacStreamConfig>,
128    ) -> Result<Self, EncodeError> {
129        Self::open_streams(
130            path,
131            VideoStream {
132                parameters: video.extract_codecpar(),
133                time_base: video_time_base,
134                copied: false,
135            },
136            audio.map(AudioStream::Encode),
137        )
138    }
139
140    /// Opens a transactional MP4 beside `path` and writes its header.
141    ///
142    /// # Errors
143    /// Returns an adapter failure for an existing output, a partial a running export holds, a
144    /// container without global headers, or a muxer that cannot be created.
145    pub(crate) fn open_streams(
146        path: &Path,
147        video: VideoStream,
148        audio: Option<AudioStream>,
149    ) -> Result<Self, EncodeError> {
150        let mut unwritten = Unwritten::open(path, video, audio)?;
151        unwritten
152            .format
153            .write_header(&mut None)
154            .map_err(encode_adapter_error)?;
155        let stream_time_base = unwritten.time_base(unwritten.stream_index);
156        let audio_stream_time_base = unwritten.time_base(unwritten.audio_stream_index);
157        unwritten.start(stream_time_base, audio_stream_time_base, None)
158    }
159
160    /// Opens a transactional MP4 beside `path` for an encoder that writes
161    /// its parameter sets in-band rather than as extradata, behind an
162    /// [`EncodedVideoFilter`](crate::bitstream::EncodedVideoFilter).
163    ///
164    /// The partial file and its lock exist from here on, as with
165    /// [`Self::open`], so a failure past this point is the export's.
166    /// The header waits for the first video packet, which brings the
167    /// parameter sets as `AV_PKT_DATA_NEW_EXTRADATA`: an MP4 writes them into
168    /// its sample description in the header, and the encoder has none
169    /// before it has encoded a picture. Until then, audio packets wait in
170    /// order, and nothing is written, so the interleave is decided exactly
171    /// as it is for [`Self::open`].
172    ///
173    /// # Errors
174    /// As [`Self::open_streams`], short of the header.
175    // Used by the D3D12VA backend, which exists on Windows only, and by the tests.
176    #[cfg_attr(
177        not(any(test, all(feature = "hwencode", target_os = "windows"))),
178        allow(dead_code)
179    )]
180    pub(crate) fn open_deferred(
181        path: &Path,
182        video: &AVCodecContext,
183        video_time_base: AVRational,
184        audio: Option<AacStreamConfig>,
185    ) -> Result<Self, EncodeError> {
186        let unwritten = Unwritten::open(
187            path,
188            VideoStream {
189                parameters: video.extract_codecpar(),
190                time_base: video_time_base,
191                copied: false,
192            },
193            audio.map(AudioStream::Encode),
194        )?;
195        // The audio thread must run before the header, which waits on the video; the container
196        // fixes the audio time base only in the header, so the thread rescales into its encoder's
197        // for now.
198        let audio_time_base = unwritten
199            .opened_audio
200            .as_ref()
201            .map(|opened| opened.encoder_time_base);
202        unwritten.start(
203            video_time_base,
204            audio_time_base.unwrap_or(video_time_base),
205            Some(PendingHeader { audio_time_base }),
206        )
207    }
208
209    /// Writes a deferred header, with the parameter sets `packet` brings as
210    /// its sample description, and takes them off the packet.
211    fn write_deferred_header(&mut self, packet: &mut AVPacket) -> Result<(), EncodeError> {
212        let Some(pending) = self.pending_header.as_ref() else {
213            return Ok(());
214        };
215        let parameter_sets =
216            alpha2_ffmpeg_bridge::take_packet_side_data(packet, ffi::AV_PKT_DATA_NEW_EXTRADATA)
217                .filter(|sets| !sets.is_empty())
218                .map(|sets| distinct_parameter_sets(&sets))
219                .ok_or_else(|| {
220                    EncodeError::Adapter(
221                        "the encoder's first packet carried no parameter sets for the MP4 header"
222                            .into(),
223                    )
224                })?;
225        let audio_time_base = pending.audio_time_base;
226        let format = self
227            .format
228            .as_mut()
229            .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?;
230        alpha2_ffmpeg_bridge::set_output_stream_extradata(
231            format,
232            usize::try_from(self.stream_index).unwrap_or(0),
233            &parameter_sets,
234        )
235        .map_err(|error| encode_adapter_error_at("storing the parameter sets", error))?;
236        crate::encode::beat_encode_call();
237        format
238            .write_header(&mut None)
239            .map_err(encode_adapter_error)?;
240        self.stream_time_base =
241            format.streams()[usize::try_from(self.stream_index).unwrap_or(0)].time_base;
242        if let (Some(from), Some(MuxedAudioTrack::Encoded(_))) = (audio_time_base, &self.audio) {
243            let to =
244                format.streams()[usize::try_from(self.audio_stream_index).unwrap_or(0)].time_base;
245            self.audio_rescale = Some((from, to));
246        }
247        self.pending_header = None;
248        self.parameter_sets = Some(parameter_sets);
249        Ok(())
250    }
251
252    /// Writes one copied audio packet, in the time base its stream was
253    /// opened with.
254    ///
255    /// # Errors
256    /// Returns an adapter failure when this output copies no audio, the
257    /// muxer is closed, or the write fails.
258    pub(crate) fn write_copied_audio(&mut self, packet: &mut AVPacket) -> Result<(), EncodeError> {
259        let Some(MuxedAudioTrack::Copied {
260            stream_index,
261            source_time_base,
262            stream_time_base,
263        }) = self.audio
264        else {
265            return Err(EncodeError::Adapter("this output copies no audio".into()));
266        };
267        packet.set_stream_index(stream_index);
268        packet.rescale_ts(source_time_base, stream_time_base);
269        self.format
270            .as_mut()
271            .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?
272            .interleaved_write_frame(packet)
273            .map_err(|error| encode_adapter_error_at("MP4 packet write failed", error))
274    }
275
276    /// Writes one video packet the encoder produced, in `encoder_time_base`.
277    ///
278    /// Behind a deferred header, the first packet's parameter sets become the
279    /// sample description and the header is written before it. Every later
280    /// packet that repeats them has them taken off, since they are already
281    /// in the header.
282    ///
283    /// # Errors
284    /// Returns an adapter failure when the muxer is closed or the write fails,
285    /// and behind a deferred header when the first packet brings no parameter
286    /// sets or a later one brings different ones.
287    pub(crate) fn write_video(
288        &mut self,
289        packet: &mut AVPacket,
290        encoder_time_base: AVRational,
291    ) -> Result<(), EncodeError> {
292        if self.pending_header.is_some() {
293            self.write_deferred_header(packet)?;
294        } else if let Some(header) = self.parameter_sets.as_deref() {
295            let repeated =
296                alpha2_ffmpeg_bridge::take_packet_side_data(packet, ffi::AV_PKT_DATA_NEW_EXTRADATA);
297            // Parameter sets live outside the bitstream, so a picture coded against other sets
298            // would be decoded against the header's.
299            if repeated.is_some_and(|sets| distinct_parameter_sets(&sets) != header) {
300                return Err(EncodeError::Adapter(
301                    "the encoder changed its parameter sets mid-stream, which one MP4 sample \
302                     description cannot carry"
303                        .into(),
304                ));
305            }
306        }
307        packet.set_stream_index(self.stream_index);
308        // A hardware encoder's time base is the frame period.
309        fill_frame_duration(packet, 1);
310        packet.rescale_ts(encoder_time_base, self.stream_time_base);
311        crate::encode::beat_encode_call();
312        self.format
313            .as_mut()
314            .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?
315            .interleaved_write_frame(packet)
316            .map_err(|error| encode_adapter_error_at("MP4 packet write failed", error))
317    }
318
319    /// Buffers one audio block and hands the thread every whole AAC frame it
320    /// completes.
321    ///
322    /// # Errors
323    /// Returns an adapter failure for a format change, a non-monotonic block,
324    /// or a codec failure the thread has already reported.
325    pub(crate) fn write_audio(
326        &mut self,
327        block: &alpha2_media::AudioBlock,
328        stages: &mut EncodeStageTimings,
329    ) -> Result<(), EncodeError> {
330        let Some(MuxedAudioTrack::Encoded(state)) = self.audio.as_mut() else {
331            return Err(EncodeError::Adapter(
332                "this output encodes no audio stream".into(),
333            ));
334        };
335        let frames = state.input.push(block)?;
336        let covered = state.input.received_ticks();
337        for frame in frames {
338            state.thread.send(AudioCommand::Block(frame, covered))?;
339        }
340        // An audio-only stretch has no video packet to trigger a drain, so the queue would grow.
341        self.write_ready_audio(false, stages)
342    }
343
344    /// Hands the audio tail over, lets the thread flush its encoder, and
345    /// waits for it, so every audio packet exists before the muxer decides
346    /// the final interleave.
347    ///
348    /// # Errors
349    /// Returns the audio thread's failure.
350    pub(crate) fn finish_audio(&mut self) -> Result<(), EncodeError> {
351        let Some(MuxedAudioTrack::Encoded(state)) = self.audio.as_mut() else {
352            return Ok(());
353        };
354        let covered = state.input.received_ticks();
355        for frame in state.input.finish() {
356            state.thread.send(AudioCommand::Block(frame, covered))?;
357        }
358        state.thread.send(AudioCommand::Finish(covered))?;
359        state.thread.shutdown();
360        if let Some(error) = state.thread.lock().failure.clone() {
361            return Err(EncodeError::Adapter(error));
362        }
363        Ok(())
364    }
365
366    /// Collects what the audio thread has produced and writes it.
367    ///
368    /// `av_interleaved_write_frame` buffers both streams by DTS, so a separate
369    /// ordering queue would duplicate the muxer's work. `flushing` only reports
370    /// the thread's failure more eagerly; the thread has already been joined
371    /// by then, so its queue is complete either way.
372    ///
373    /// # Errors
374    /// Returns the audio thread's failure or a failed write.
375    pub(crate) fn write_ready_audio(
376        &mut self,
377        flushing: bool,
378        stages: &mut EncodeStageTimings,
379    ) -> Result<(), EncodeError> {
380        let Some(MuxedAudioTrack::Encoded(audio)) = self.audio.as_ref() else {
381            return Ok(());
382        };
383        {
384            let mut state = audio.thread.lock();
385            if let Some(error) = state.failure.clone() {
386                return Err(EncodeError::Adapter(error));
387            }
388            if flushing && !state.finished {
389                return Err(EncodeError::Adapter(
390                    "audio encoder stopped before it finished".into(),
391                ));
392            }
393            self.audio_packets.extend(state.packets.drain(..));
394        }
395        // Nothing can be written before the header; the packets wait in order.
396        if self.pending_header.is_some() {
397            return Ok(());
398        }
399        let rescale = self.audio_rescale;
400        while let Some(mut queued) = self.audio_packets.pop_front() {
401            if let Some((from, to)) = rescale {
402                queued.packet.rescale_ts(from, to);
403            }
404            let format = self
405                .format
406                .as_mut()
407                .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?;
408            timed(&mut stages.audio_mux_write, || {
409                format.interleaved_write_frame(&mut queued.packet)
410            })
411            .map_err(|error| encode_adapter_error_at("MP4 packet write failed", error))?;
412        }
413        Ok(())
414    }
415
416    /// The audio thread's own codec timings, merged into `stages`.
417    // Used by the hardware-encode backends.
418    #[cfg_attr(
419        not(all(
420            feature = "hwencode",
421            any(target_os = "linux", target_os = "macos", target_os = "windows")
422        )),
423        allow(dead_code)
424    )]
425    pub(crate) fn with_audio_timings(&self, stages: EncodeStageTimings) -> EncodeStageTimings {
426        let Some(MuxedAudioTrack::Encoded(audio)) = self.audio.as_ref() else {
427            return stages;
428        };
429        let thread = audio.thread.lock().stages;
430        EncodeStageTimings {
431            audio_send_frame: thread.audio_send_frame,
432            audio_receive_packet: thread.audio_receive_packet,
433            ..stages
434        }
435    }
436
437    /// Writes the trailer and renames the partial output into place.
438    ///
439    /// # Errors
440    /// Returns an adapter failure when the trailer or the rename fails, or
441    /// when a deferred header was never written because no video packet
442    /// arrived; the partial file then goes with the muxer.
443    pub(crate) fn close(&mut self) -> Result<(), EncodeError> {
444        if self.pending_header.is_some() {
445            return Err(EncodeError::Adapter(
446                "the encoder produced no parameter sets: no video packet reached the MP4 header"
447                    .into(),
448            ));
449        }
450        crate::encode::beat_encode_call();
451        self.format
452            .as_mut()
453            .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?
454            .write_trailer()
455            .map_err(encode_adapter_error)?;
456        // A write error reported only at close fails the export here, before the rename.
457        if let Some(output) = self.format.take() {
458            crate::encode::beat_encode_call();
459            output
460                .close()
461                .map_err(|error| encode_adapter_error_at("closing the output", error))?;
462        }
463        crate::partial::publish(&self.partial_path, &self.final_path)?;
464        self.complete = true;
465        Ok(())
466    }
467
468    /// Discards a partial output without writing a trailer: the muxer is
469    /// dropped where it stands and the partial file is removed, so nothing
470    /// half-written is left behind under the caller's name.
471    pub(crate) fn abort(&mut self) {
472        // Stop the audio thread before the muxer goes, so nothing is queued against a closed
473        // output.
474        if let Some(MuxedAudioTrack::Encoded(state)) = self.audio.as_mut() {
475            state.thread.shutdown();
476        }
477        if !self.complete {
478            drop(self.format.take());
479            let _ = fs::remove_file(&self.partial_path);
480        }
481    }
482}
483
484impl Drop for Mp4Muxer {
485    fn drop(&mut self) {
486        // A failed export leaves no half-written file, as the software sinks do.
487        self.abort();
488    }
489}
490
491/// A transactional MP4 whose streams exist and whose header is not written
492/// yet.
493struct Unwritten {
494    final_path: PathBuf,
495    /// Declared before `partial`, so a failed header closes the file before
496    /// the partial is removed: Windows removes no file that is still open.
497    format: OutputFile,
498    partial: PartialOutput,
499    stream_index: i32,
500    audio_stream_index: i32,
501    opened_audio: Option<OpenedAac>,
502    copied_audio: Option<AVRational>,
503}
504
505impl Unwritten {
506    /// Claims the partial file beside `path`, opens the muxer on it, and
507    /// adds the streams.
508    ///
509    /// # Errors
510    /// As [`Mp4Muxer::open_streams`], short of the header.
511    fn open(
512        path: &Path,
513        video: VideoStream,
514        audio: Option<AudioStream>,
515    ) -> Result<Self, EncodeError> {
516        let final_path = path.to_owned();
517        let partial = PartialOutput::claim(&final_path)?;
518        let mut format = partial.open_muxer()?;
519        let global_header =
520            format.oformat().flags & i32::try_from(ffi::AVFMT_GLOBALHEADER).unwrap_or(0) != 0;
521        // Parameter sets exist only in the sample description; a container without one would get a
522        // stream no decoder can start.
523        if !global_header {
524            return Err(EncodeError::Adapter(
525                "these exports write only to containers with global headers, such as MP4".into(),
526            ));
527        }
528        let stream_index;
529        {
530            let mut stream = format.new_stream();
531            stream.set_time_base(video.time_base);
532            let (codec_id, tag) = (video.parameters.codec_id, video.parameters.codec_tag);
533            stream.set_codecpar(video.parameters);
534            // Another container's tag need not be MP4's; HEVC is `hvc1` unless a copy says
535            // otherwise.
536            alpha2_ffmpeg_bridge::set_stream_codec_tag(
537                &mut stream,
538                crate::convert::mp4_codec_tag(codec_id, tag, video.copied),
539            );
540            stream_index = stream.index;
541        }
542        let mut opened_audio = None;
543        let mut copied_audio = None;
544        let mut audio_stream_index = 0_i32;
545        match audio {
546            Some(AudioStream::Encode(config)) => {
547                let opened = open_aac(config, global_header)?;
548                let mut stream = format.new_stream();
549                stream.set_time_base(opened.encoder_time_base);
550                stream.set_codecpar(opened.encoder.extract_codecpar());
551                audio_stream_index = stream.index;
552                opened_audio = Some(opened);
553            }
554            Some(AudioStream::Copy {
555                parameters,
556                time_base,
557            }) => {
558                let mut stream = format.new_stream();
559                stream.set_time_base(time_base);
560                let (codec_id, tag) = (parameters.codec_id, parameters.codec_tag);
561                stream.set_codecpar(parameters);
562                alpha2_ffmpeg_bridge::set_stream_codec_tag(
563                    &mut stream,
564                    crate::convert::mp4_codec_tag(codec_id, tag, true),
565                );
566                audio_stream_index = stream.index;
567                copied_audio = Some(time_base);
568            }
569            None => {}
570        }
571        Ok(Self {
572            final_path,
573            format,
574            partial,
575            stream_index,
576            audio_stream_index,
577            opened_audio,
578            copied_audio,
579        })
580    }
581
582    /// The time base the container gave stream `index`.
583    fn time_base(&self, index: i32) -> AVRational {
584        self.format.streams()[usize::try_from(index).unwrap_or(0)].time_base
585    }
586
587    /// Starts the audio thread, rescaling into `audio_stream_time_base`, and
588    /// keeps the partial file as the muxer's.
589    ///
590    /// # Errors
591    /// Returns an adapter failure when the audio thread cannot start.
592    fn start(
593        self,
594        stream_time_base: AVRational,
595        audio_stream_time_base: AVRational,
596        pending_header: Option<PendingHeader>,
597    ) -> Result<Mp4Muxer, EncodeError> {
598        // Bound after `partial`, `format` is dropped before it when the thread cannot start.
599        let Self {
600            final_path,
601            partial,
602            format,
603            stream_index,
604            audio_stream_index,
605            opened_audio,
606            copied_audio,
607        } = self;
608        let partial_path = partial.path().to_owned();
609        // Started after the header: the stream index and container time base it rescales into exist
610        // only then. A deferred header is the exception ([`Mp4Muxer::open_deferred`]).
611        let audio = match (opened_audio, copied_audio) {
612            (Some(opened), _) => Some(MuxedAudioTrack::Encoded(MuxedAudio {
613                input: AacInput::new(
614                    opened.project_rate,
615                    opened.encoder_rate,
616                    opened.channels,
617                    opened.frame_size,
618                ),
619                thread: spawn_audio_encoder(AudioEncoderConfig {
620                    encoder: opened.encoder,
621                    stream_index: audio_stream_index,
622                    encoder_time_base: opened.encoder_time_base,
623                    stream_time_base: audio_stream_time_base,
624                    sample_rate: opened.encoder_rate,
625                })?,
626            })),
627            (None, Some(source_time_base)) => Some(MuxedAudioTrack::Copied {
628                stream_index: audio_stream_index,
629                source_time_base,
630                stream_time_base: audio_stream_time_base,
631            }),
632            (None, None) => None,
633        };
634        let partial_lock = partial.keep();
635        Ok(Mp4Muxer {
636            final_path,
637            partial_path,
638            _partial_lock: partial_lock,
639            format: Some(format),
640            stream_index,
641            stream_time_base,
642            complete: false,
643            audio,
644            audio_stream_index,
645            audio_packets: VecDeque::new(),
646            pending_header,
647            parameter_sets: None,
648            audio_rescale: None,
649        })
650    }
651}
652
653/// What a deferred header ([`Mp4Muxer::open_deferred`]) still needs.
654struct PendingHeader {
655    /// The time base the audio thread rescales into until the header fixes
656    /// the stream's, when there is an encoded audio stream.
657    audio_time_base: Option<AVRational>,
658}
659
660/// The AAC encoder a muxed hardware export opens, before it has a stream.
661struct OpenedAac {
662    encoder: AVCodecContext,
663    encoder_time_base: AVRational,
664    /// The rate the audio arrives at.
665    project_rate: u32,
666    /// The rate it is encoded at ([`encoder_sample_rate`]).
667    encoder_rate: u32,
668    channels: usize,
669    frame_size: usize,
670}
671
672/// Opens the AAC encoder for a muxed hardware export.
673fn open_aac(config: AacStreamConfig, global_header: bool) -> Result<OpenedAac, EncodeError> {
674    let AacStreamConfig {
675        sample_rate,
676        channels,
677        bit_rate,
678    } = config;
679    let codec = AVCodec::find_encoder(ffi::AV_CODEC_ID_AAC)
680        .ok_or_else(|| EncodeError::Adapter("aac encoder is unavailable".into()))?;
681    let encoder_rate = encoder_sample_rate(&codec, sample_rate);
682    let rate = i32::try_from(encoder_rate)
683        .map_err(|_| EncodeError::Adapter("audio sample rate is out of range".into()))?;
684    let encoder_time_base = ra(1, rate);
685    let mut encoder = AVCodecContext::new(&codec);
686    encoder.set_sample_rate(rate);
687    encoder.set_sample_fmt(ffi::AV_SAMPLE_FMT_FLTP);
688    encoder.set_ch_layout(AVChannelLayout::from_nb_channels(i32::from(channels)).into_inner());
689    encoder.set_time_base(encoder_time_base);
690    encoder.set_bit_rate(i64::from(bit_rate));
691    if global_header {
692        encoder.set_flags(
693            encoder.flags | i32::try_from(ffi::AV_CODEC_FLAG_GLOBAL_HEADER).unwrap_or(0),
694        );
695    }
696    encoder.open(None).map_err(encode_adapter_error)?;
697    let frame_size = usize::try_from(encoder.frame_size)
698        .ok()
699        .filter(|size| *size > 0)
700        .ok_or_else(|| EncodeError::Adapter("AAC encoder has no fixed frame size".into()))?;
701    Ok(OpenedAac {
702        encoder,
703        encoder_time_base,
704        project_rate: sample_rate,
705        encoder_rate,
706        channels: usize::from(channels),
707        frame_size,
708    })
709}
710
711/// Parameter sets as a sample description takes them: for Annex B (H.264
712/// and HEVC), each distinct NAL unit once, in the order it first appears,
713/// behind a four-byte start code; anything else (an AV1 sequence header) as
714/// it is.
715///
716/// An encoder may repeat a parameter set within one access unit, and
717/// `extract_extradata` hands over every copy, so the same sets can arrive
718/// as different bytes from one IDR picture to the next. Comparing them as
719/// sets of units tells a repeat from a change.
720fn distinct_parameter_sets(sets: &[u8]) -> Vec<u8> {
721    if !(sets.starts_with(&[0, 0, 1]) || sets.starts_with(&[0, 0, 0, 1])) {
722        return sets.to_vec();
723    }
724    let mut starts = Vec::new();
725    let mut at = 0;
726    while at + 3 <= sets.len() {
727        if sets[at..at + 3] == [0, 0, 1] {
728            starts.push(at + 3);
729            at += 3;
730        } else {
731            at += 1;
732        }
733    }
734    let mut units: Vec<&[u8]> = Vec::new();
735    for (index, &start) in starts.iter().enumerate() {
736        let end = starts.get(index + 1).map_or(sets.len(), |next| next - 3);
737        // A four-byte start code's leading zero, or trailing padding, belongs to no unit.
738        let mut unit = &sets[start..end];
739        while let [rest @ .., 0] = unit {
740            unit = rest;
741        }
742        if !unit.is_empty() && !units.contains(&unit) {
743            units.push(unit);
744        }
745    }
746    units
747        .into_iter()
748        .flat_map(|unit| [&[0, 0, 0, 1][..], unit])
749        .flatten()
750        .copied()
751        .collect()
752}
753
754/// Gives a video packet with no duration one frame: `frame_units` of its
755/// time base, which is 1 where the time base is the frame period and the
756/// frame length rounded to the unit where an encoder could not hold that
757/// (MPEG-4 Part 2 at 119.88 fps).
758///
759/// `FFmpeg` clears a frame's duration unless the encoder was opened with
760/// `AV_CODEC_FLAG_FRAME_DURATION`, the encoders copy that zero into their
761/// packets, and the MP4 muxer takes the last sample's duration from the last
762/// packet: without this, the last frame lasts no time and the track is a
763/// frame short.
764pub(crate) fn fill_frame_duration(packet: &mut AVPacket, frame_units: i64) {
765    if packet.duration == 0 {
766        packet.set_duration(frame_units);
767    }
768}
769
770/// How many units of `time_base` one frame at `rate` lasts, to the nearest
771/// unit and at least one.
772pub(crate) fn frame_units(rate: alpha2_time::FrameRate, time_base: AVRational) -> i64 {
773    rsmpeg::avutil::av_rescale_q(
774        1,
775        rsmpeg::avutil::ra(
776            i32::try_from(rate.denominator()).unwrap_or(1),
777            i32::try_from(rate.numerator()).unwrap_or(i32::MAX),
778        ),
779        time_base,
780    )
781    .max(1)
782}