Skip to main content

alpha2_native/
encode.rs

1//! Muxed A/V and WAV encode sinks.
2
3use std::collections::VecDeque;
4use std::fs;
5use std::io::{BufWriter, Seek, SeekFrom, Write};
6use std::path::{Path, PathBuf};
7use std::sync::mpsc::{self, Receiver, SyncSender};
8use std::sync::{Arc, Mutex};
9use std::thread::{self, JoinHandle};
10use std::time::{Duration, Instant};
11
12use crate::audio_input::{AudioStreamConfig, EncoderAudioInput, open_audio_encoder};
13use crate::capabilities::{
14    AvEncodeConfig, NativeVideoEncoderSelection, VideoEncoderOpenRequest, open_video_encoder,
15};
16use crate::convert::{
17    codec_thread_budget, configure_rgba_to_yuv, encode_adapter_error, encode_adapter_error_at,
18    tick_base,
19};
20use crate::partial::PartialOutput;
21use alpha2_ffmpeg_bridge::OutputFile;
22use alpha2_ffmpeg_bridge::Yuv420pFrameBuffer;
23use alpha2_media::{
24    AudioBlock, AudioBlockSink, CancelToken, DecodedVideoFrame, EncodeError, EncodeEvent,
25    EncodeSink, MAX_AUDIO_CHANNELS, PixelFormat, SpeakerLayout,
26};
27use alpha2_time::MediaTime;
28use rsmpeg::avcodec::{AVCodecContext, AVPacket};
29use rsmpeg::avutil::{AVFrameWithImage, AVImage, AVRational, av_rescale_q};
30use rsmpeg::error::RsmpegError;
31use rsmpeg::ffi;
32use rsmpeg::swscale::SwsContext;
33
34/// Where one encode sink's wall time went.
35///
36/// The codec, the packet drain and the mux write move for different reasons:
37/// a codec change moves the first, a channel-depth change moves the second,
38/// and a container change moves the third. A single total such as
39/// `videoEncodeMs` cannot say which one did, so each is timed apart.
40#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
41pub struct EncodeStageTimings {
42    /// Waiting, before `avcodec_send_frame`, for the GPU to finish writing a
43    /// surface the encoder cannot wait for on its own. Zero on every path
44    /// whose encoder waits on the GPU itself or takes CPU frames.
45    pub video_input_ready_wait: Duration,
46    /// `avcodec_send_frame` for video: the codec itself.
47    pub video_send_frame: Duration,
48    /// `avcodec_receive_packet` for video.
49    pub video_receive_packet: Duration,
50    /// `av_interleaved_write_frame` for video packets.
51    pub video_mux_write: Duration,
52    /// `avcodec_send_frame` for audio.
53    pub audio_send_frame: Duration,
54    /// `avcodec_receive_packet` for audio.
55    pub audio_receive_packet: Duration,
56    /// `av_interleaved_write_frame` for audio packets.
57    pub audio_mux_write: Duration,
58}
59
60impl EncodeStageTimings {
61    /// What elapsed between `self` and a later snapshot.
62    #[must_use]
63    pub fn since(self, earlier: Self) -> Self {
64        Self {
65            video_input_ready_wait: self
66                .video_input_ready_wait
67                .saturating_sub(earlier.video_input_ready_wait),
68            video_send_frame: self
69                .video_send_frame
70                .saturating_sub(earlier.video_send_frame),
71            video_receive_packet: self
72                .video_receive_packet
73                .saturating_sub(earlier.video_receive_packet),
74            video_mux_write: self.video_mux_write.saturating_sub(earlier.video_mux_write),
75            audio_send_frame: self
76                .audio_send_frame
77                .saturating_sub(earlier.audio_send_frame),
78            audio_receive_packet: self
79                .audio_receive_packet
80                .saturating_sub(earlier.audio_receive_packet),
81            audio_mux_write: self.audio_mux_write.saturating_sub(earlier.audio_mux_write),
82        }
83    }
84}
85
86thread_local! {
87    /// What this thread calls before each encoder and muxer call; see
88    /// [`watch_encode_calls`].
89    static ENCODE_CALL_WATCH: std::cell::RefCell<Option<Box<dyn Fn()>>> =
90        const { std::cell::RefCell::new(None) };
91}
92
93/// Calls `beat` on this thread before every encoder and muxer call an encode
94/// sink makes on it, until the returned guard drops.
95///
96/// Some of those calls wait with no timeout: `h264_vulkan` waits on its
97/// execution fences with `UINT64_MAX`, and `write(2)` to a hard-mounted share
98/// that stopped answering is retried forever. Nothing can interrupt them, so
99/// the thread that drives a sink reports where it is instead, and its owner
100/// decides that a call which has not returned in time is stuck. A sink's calls
101/// all run on the thread that calls the sink, except the audio encoder's own
102/// thread, which the sink waits on in a call this beat already covers.
103#[must_use = "the watch ends when the guard drops"]
104pub fn watch_encode_calls(beat: impl Fn() + 'static) -> EncodeCallWatch {
105    let previous = ENCODE_CALL_WATCH.with(|watch| watch.replace(Some(Box::new(beat))));
106    EncodeCallWatch {
107        previous,
108        thread: std::marker::PhantomData,
109    }
110}
111
112/// Ends a [`watch_encode_calls`] watch, restoring the one before it.
113pub struct EncodeCallWatch {
114    previous: Option<Box<dyn Fn()>>,
115    /// The watch belongs to the thread that set it.
116    thread: std::marker::PhantomData<*const ()>,
117}
118
119impl Drop for EncodeCallWatch {
120    fn drop(&mut self) {
121        let previous = self.previous.take();
122        ENCODE_CALL_WATCH.with(|watch| {
123            watch.replace(previous);
124        });
125    }
126}
127
128/// An encoder or muxer call starts now on this thread.
129pub(crate) fn beat_encode_call() {
130    ENCODE_CALL_WATCH.with(|watch| {
131        if let Some(beat) = watch.borrow().as_ref() {
132            beat();
133        }
134    });
135}
136
137/// Runs `body`, adding its elapsed time to `total`. `body` is an encoder or
138/// muxer call, so it is watched ([`watch_encode_calls`]).
139pub(crate) fn timed<T>(total: &mut Duration, body: impl FnOnce() -> T) -> T {
140    beat_encode_call();
141    let started = Instant::now();
142    let value = body();
143    *total = total.saturating_add(started.elapsed());
144    value
145}
146
147/// Transactional IEEE-f32 WAV encoder used by native offline audio export.
148pub struct NativeWavEncodeSink {
149    pub(crate) final_path: PathBuf,
150    pub(crate) partial_path: PathBuf,
151    /// Held until the partial file is renamed into place or removed.
152    pub(crate) _partial_lock: crate::partial::PartialLock,
153    pub(crate) file: Option<BufWriter<fs::File>>,
154    pub(crate) sample_rate: u32,
155    pub(crate) channels: u16,
156    /// The speakers every block feeds.
157    pub(crate) speakers: SpeakerLayout,
158    /// The `WAVE_FORMAT_EXTENSIBLE` channel mask a layout other than its
159    /// count's default is written with; `None` writes a plain IEEE-float
160    /// header, which readers take as the default.
161    pub(crate) channel_mask: Option<u32>,
162    /// The block plane each interleaved channel comes from: WAVE requires
163    /// the mask's bit order.
164    pub(crate) plane_order: Vec<usize>,
165    pub(crate) frames_written: u64,
166    pub(crate) complete: bool,
167    /// One block's samples, interleaved, reused across blocks.
168    pub(crate) interleaved: Vec<u8>,
169}
170
171/// Bytes the WAV sink gathers before each write: about five seconds of
172/// 48 kHz stereo, so a minute of audio takes a few dozen writes.
173const WAV_WRITE_BUFFER: usize = 1 << 20;
174
175/// Audio/video sink that encodes and interleaves through the linked `FFmpeg`
176/// libraries without requiring command-line programs on the host. Video frames
177/// and audio blocks must be submitted in nondecreasing start-timestamp order;
178/// ready packets are muxed incrementally instead of retained for the full file.
179pub struct NativeAvEncodeSink {
180    pub(crate) final_path: PathBuf,
181    pub(crate) partial_path: PathBuf,
182    /// Held until the partial file is renamed into place or removed.
183    pub(crate) _partial_lock: crate::partial::PartialLock,
184    pub(crate) output: Option<OutputFile>,
185    pub(crate) video_encoder: AVCodecContext,
186    pub(crate) video_stream_index: i32,
187    pub(crate) video_encoder_time_base: AVRational,
188    /// One frame in `video_encoder_time_base`.
189    pub(crate) video_frame_units: i64,
190    pub(crate) video_stream_time_base: AVRational,
191    pub(crate) video_scaler: SwsContext,
192    /// Turns decoded YUV of any layout into the encoder's 4:2:0.
193    pub(crate) yuv_input: crate::convert::Yuv420pConverter,
194    pub(crate) width: i32,
195    pub(crate) height: i32,
196    pub(crate) last_video_pts: Option<i64>,
197    /// Audio encoding, backlog and packet draining run on a separate thread
198    /// so video encoding cannot serialize audio work.
199    pub(crate) audio: AudioEncodeThread,
200    /// The project's audio, as the audio encoder takes it.
201    pub(crate) audio_input: EncoderAudioInput,
202    pub(crate) video_packets: VecDeque<QueuedPacket>,
203    pub(crate) last_input_ticks: Option<i64>,
204    pub(crate) complete: bool,
205    pub(crate) video_selection: NativeVideoEncoderSelection,
206    pub(crate) stages: EncodeStageTimings,
207}
208
209/// A command to the audio encode thread: one frame to encode, or the end of the input.
210pub(crate) enum AudioCommand {
211    /// One frame of samples to encode, and the input tick the input reached.
212    /// Every frame is the encoder's frame size but the last, which may be
213    /// shorter.
214    Block(Vec<Vec<f32>>, i64),
215    /// No more input: flush the encoder and stop, the input having reached
216    /// the tick.
217    Finish(i64),
218}
219
220/// What the audio encode thread has produced, and how far it has got.
221///
222/// The muxer stays on the video worker beside `pop_next_packet`, so the only
223/// thing crossing threads is finished packets and a watermark.
224#[derive(Default)]
225pub(crate) struct AudioQueueState {
226    /// Packets in production order, which for one stream is DTS order.
227    pub(crate) packets: VecDeque<QueuedPacket>,
228    /// Input ticks the thread has consumed and produced every packet for.
229    ///
230    /// The muxer will not write a video packet past this. That is what keeps
231    /// the container's interleave independent of when the audio thread runs:
232    /// it writes exactly what the single-threaded sink would have written,
233    /// because the single-threaded sink had also consumed every audio block up
234    /// to the video frame it was about to encode.
235    pub(crate) consumed_ticks: i64,
236    /// Set once the thread has flushed its encoder and will produce no more.
237    pub(crate) finished: bool,
238    /// The thread's first failure, surfaced by the muxer.
239    pub(crate) failure: Option<String>,
240    /// The thread's share of the sink's stage totals.
241    pub(crate) stages: EncodeStageTimings,
242}
243
244/// Handle to the audio encode thread.
245pub(crate) struct AudioEncodeThread {
246    commands: Option<SyncSender<AudioCommand>>,
247    join: Option<JoinHandle<()>>,
248    state: Arc<Mutex<AudioQueueState>>,
249}
250
251impl AudioEncodeThread {
252    pub(crate) fn lock(&self) -> std::sync::MutexGuard<'_, AudioQueueState> {
253        self.state
254            .lock()
255            .unwrap_or_else(std::sync::PoisonError::into_inner)
256    }
257
258    /// Hands one command over, reporting the thread's failure if it has
259    /// stopped.
260    pub(crate) fn send(&self, command: AudioCommand) -> Result<(), EncodeError> {
261        let sent = match self.commands.as_ref() {
262            Some(sender) => sender.send(command).is_ok(),
263            None => return Err(EncodeError::Adapter("audio encoder is closed".into())),
264        };
265        if sent {
266            return Ok(());
267        }
268        let failure = self.lock().failure.clone();
269        Err(EncodeError::Adapter(failure.unwrap_or_else(|| {
270            "audio encoder stopped before accepting a block".into()
271        })))
272    }
273
274    /// Stops the thread, leaving whatever it produced in the queue.
275    pub(crate) fn shutdown(&mut self) {
276        drop(self.commands.take());
277        if let Some(join) = self.join.take() {
278            let _ = join.join();
279        }
280    }
281}
282
283impl Drop for AudioEncodeThread {
284    fn drop(&mut self) {
285        self.shutdown();
286    }
287}
288
289/// A finished packet waiting for the muxer, ordered by its DTS in ticks.
290pub(crate) struct QueuedPacket {
291    pub(crate) sort_ticks: i64,
292    pub(crate) packet: AVPacket,
293}
294
295impl NativeAvEncodeSink {
296    /// Creates linked-library video, audio, and container encoders without
297    /// touching the final destination.
298    ///
299    /// # Errors
300    /// Returns an encoder error for invalid configuration, paths, or stale
301    /// component outputs.
302    pub fn create(path: impl AsRef<Path>, config: AvEncodeConfig) -> Result<Self, EncodeError> {
303        Self::create_selected(path, config, None, &codec_thread_budget)
304    }
305
306    /// Creates the requested concrete video encoder by its `FFmpeg` short name.
307    ///
308    /// # Errors
309    /// Returns an encoder error when the name is unavailable, implements a
310    /// different codec, or the remaining configuration is invalid.
311    pub fn create_with_video_encoder(
312        path: impl AsRef<Path>,
313        config: AvEncodeConfig,
314        encoder_name: &str,
315    ) -> Result<Self, EncodeError> {
316        Self::create_selected(path, config, Some(encoder_name), &codec_thread_budget)
317    }
318
319    /// Creates an encoder whose software thread count the caller decides.
320    ///
321    /// `software_threads` is asked, at most once, only when a candidate that
322    /// runs on the CPU is opened: the caller sizes the rest of its work from
323    /// [`NativeVideoEncoderSelection`] afterwards, so a hardware candidate that
324    /// fails to open and falls through to software still gets a software
325    /// encoder's share of the CPU.
326    ///
327    /// `encoder_name` is the concrete encoder the caller pinned. `None` lets
328    /// the candidates for the codec be tried in preference order, which is
329    /// what makes the open-time fallback reachable.
330    ///
331    /// # Errors
332    /// Returns an encoder error when no candidate opens, or for invalid
333    /// configuration, paths, codecs, or muxers.
334    pub fn create_with_video_encoder_threads(
335        path: impl AsRef<Path>,
336        config: AvEncodeConfig,
337        encoder_name: Option<&str>,
338        software_threads: &dyn Fn() -> i64,
339    ) -> Result<Self, EncodeError> {
340        Self::create_selected(path, config, encoder_name, software_threads)
341    }
342
343    #[allow(clippy::too_many_lines)]
344    pub(crate) fn create_selected(
345        path: impl AsRef<Path>,
346        config: AvEncodeConfig,
347        encoder_name: Option<&str>,
348        software_threads: &dyn Fn() -> i64,
349    ) -> Result<Self, EncodeError> {
350        crate::video_encode::check_video_config(&config.video)?;
351        if !config
352            .video
353            .container
354            .offers_audio(config.audio_codec.model())
355        {
356            return Err(EncodeError::Adapter(format!(
357                "{} does not offer audio codec {}",
358                config.video.container,
359                config.audio_codec.as_str()
360            )));
361        }
362        if config.audio_sample_rate == 0
363            || config.audio_channels == 0
364            || config.audio_channels > 8
365            || config.audio_bit_rate == 0
366        {
367            return Err(EncodeError::Adapter(format!(
368                "the audio output ({} channels at {} Hz, {} bit/s) is refused: 1 to 8 channels, a positive rate and bitrate",
369                config.audio_channels, config.audio_sample_rate, config.audio_bit_rate
370            )));
371        }
372        let final_path = path.as_ref().to_owned();
373        let partial = PartialOutput::claim(&final_path)?;
374        let partial_path = partial.path().to_owned();
375        let mut output = partial.open_muxer(config.video.container)?;
376        let global_header =
377            output.oformat().flags & i32::try_from(ffi::AVFMT_GLOBALHEADER).unwrap_or(0) != 0;
378
379        let width = i32::try_from(config.video.width)
380            .map_err(|_| EncodeError::Adapter("width is out of range".into()))?;
381        let height = i32::try_from(config.video.height)
382            .map_err(|_| EncodeError::Adapter("height is out of range".into()))?;
383        let (video_encoder, video_selection) = open_video_encoder(&VideoEncoderOpenRequest {
384            config: &config.video,
385            requested_name: encoder_name,
386            threads: crate::capabilities::SoftwareEncoderThreads::new(software_threads),
387            global_header,
388            width,
389            height,
390        })?;
391        // The frame length, or the finest unit an encoder that cannot hold it takes.
392        let video_encoder_time_base = video_encoder.time_base;
393
394        let audio = open_audio_encoder(
395            AudioStreamConfig {
396                codec: config.audio_codec,
397                sample_rate: config.audio_sample_rate,
398                channels: config.audio_channels,
399                bit_rate: config.audio_bit_rate,
400            },
401            global_header,
402        )?;
403        let audio_encoder_time_base = audio.encoder_time_base;
404
405        let video_stream_index;
406        {
407            let mut stream = output.new_stream();
408            stream.set_time_base(video_encoder_time_base);
409            stream.set_codecpar(video_encoder.extract_codecpar());
410            crate::video_encode::describe_output_stream(
411                &mut stream,
412                &video_encoder,
413                config.video.display,
414                config.video.container,
415            )?;
416            video_stream_index = stream.index;
417        }
418        let audio_stream_index;
419        {
420            let mut stream = output.new_stream();
421            stream.set_time_base(audio_encoder_time_base);
422            stream.set_codecpar(audio.encoder.extract_codecpar());
423            audio_stream_index = stream.index;
424        }
425        output
426            .write_header(&mut crate::convert::muxer_header_options(
427                config.video.container,
428            ))
429            .map_err(encode_adapter_error)?;
430        let video_stream_time_base =
431            output.streams()[usize::try_from(video_stream_index).unwrap_or(0)].time_base;
432        let audio_stream_time_base =
433            output.streams()[usize::try_from(audio_stream_index).unwrap_or(0)].time_base;
434        let mut video_scaler = SwsContext::get_context(
435            width,
436            height,
437            ffi::AV_PIX_FMT_RGBA,
438            width,
439            height,
440            ffi::AV_PIX_FMT_YUV420P,
441            ffi::SWS_BILINEAR,
442            None,
443            None,
444            None,
445        )
446        .ok_or_else(|| EncodeError::Adapter("cannot create RGBA to YUV scaler".into()))?;
447        configure_rgba_to_yuv(&mut video_scaler, config.video.color)
448            .map_err(encode_adapter_error)?;
449        let audio_input = audio.input;
450        let audio = spawn_audio_encoder(AudioEncoderConfig {
451            encoder: audio.encoder,
452            stream_index: audio_stream_index,
453            encoder_time_base: audio_encoder_time_base,
454            stream_time_base: audio_stream_time_base,
455            sample_rate: audio.encoder_rate,
456            sample_format: audio.sample_format,
457        })?;
458        let partial_lock = partial.keep();
459        Ok(Self {
460            final_path,
461            partial_path,
462            _partial_lock: partial_lock,
463            output: Some(output),
464            video_encoder,
465            video_stream_index,
466            video_encoder_time_base,
467            video_frame_units: crate::mux::frame_units(
468                config.video.frame_rate,
469                video_encoder_time_base,
470            ),
471            video_stream_time_base,
472            video_scaler,
473            yuv_input: crate::convert::Yuv420pConverter::new(config.video.color),
474            width,
475            height,
476            last_video_pts: None,
477            audio,
478            audio_input,
479            video_packets: VecDeque::new(),
480            last_input_ticks: None,
481            complete: false,
482            video_selection,
483            stages: EncodeStageTimings::default(),
484        })
485    }
486
487    /// Audio packets the thread has produced and the muxer has not written.
488    #[cfg(test)]
489    pub(crate) fn queued_audio_packets(&self) -> usize {
490        self.audio.lock().packets.len()
491    }
492
493    /// Where this sink's time has gone so far, both threads together.
494    #[must_use]
495    pub fn stage_timings(&self) -> EncodeStageTimings {
496        let audio = self.audio.lock().stages;
497        EncodeStageTimings {
498            audio_send_frame: audio.audio_send_frame,
499            audio_receive_packet: audio.audio_receive_packet,
500            ..self.stages
501        }
502    }
503
504    /// The video encoder this sink opened, and the candidates it passed over.
505    #[must_use]
506    pub fn video_selection(&self) -> &NativeVideoEncoderSelection {
507        &self.video_selection
508    }
509
510    pub(crate) fn cleanup(&mut self) -> Result<(), EncodeError> {
511        // Stop the audio thread before the muxer goes, so nothing is queued against a closed
512        // output.
513        self.audio.shutdown();
514        drop(self.output.take());
515        match fs::remove_file(&self.partial_path) {
516            Ok(()) => Ok(()),
517            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
518            Err(error) => Err(encode_adapter_error(error)),
519        }
520    }
521
522    /// Queues one finished video packet for the muxer.
523    fn queue_video_packet(&mut self, mut packet: AVPacket) {
524        packet.set_stream_index(self.video_stream_index);
525        crate::mux::fill_frame_duration(&mut packet, self.video_frame_units);
526        packet.rescale_ts(self.video_encoder_time_base, self.video_stream_time_base);
527        let timestamp = if packet.dts == ffi::AV_NOPTS_VALUE {
528            packet.pts
529        } else {
530            packet.dts
531        };
532        self.video_packets.push_back(QueuedPacket {
533            sort_ticks: av_rescale_q(timestamp, self.video_stream_time_base, tick_base()),
534            packet,
535        });
536    }
537
538    /// Takes the next packet the muxer may write, or `None` to wait.
539    ///
540    /// Deterministic interleaving follows two rules:
541    /// - Earlier DTS wins; video wins ties so thread scheduling cannot affect output.
542    /// - Hold video until audio has consumed all input ticks through its timestamp.
543    ///   The coordinator's channel bounds how far video can get ahead.
544    fn pop_next_packet(&mut self, flushing: bool) -> Result<Option<QueuedPacket>, EncodeError> {
545        let mut state = self.audio.lock();
546        if let Some(error) = state.failure.clone() {
547            return Err(EncodeError::Adapter(error));
548        }
549        let take_audio = match (self.video_packets.front(), state.packets.front()) {
550            (Some(video), Some(audio)) => audio.sort_ticks < video.sort_ticks,
551            (None, Some(_)) => true,
552            (_, None) => false,
553        };
554        if take_audio {
555            return Ok(state.packets.pop_front());
556        }
557        let Some(video) = self.video_packets.front() else {
558            return Ok(None);
559        };
560        if !flushing && !state.finished && state.consumed_ticks < video.sort_ticks {
561            return Ok(None);
562        }
563        drop(state);
564        Ok(self.video_packets.pop_front())
565    }
566
567    pub(crate) fn write_ready_packets(&mut self, flushing: bool) -> Result<(), EncodeError> {
568        while let Some(mut queued) = self.pop_next_packet(flushing)? {
569            // The muxer is shared, so the write is charged to the stream whose packet it is.
570            let video = queued.packet.stream_index == self.video_stream_index;
571            let total = if video {
572                &mut self.stages.video_mux_write
573            } else {
574                &mut self.stages.audio_mux_write
575            };
576            let output = self
577                .output
578                .as_mut()
579                .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?;
580            timed(total, || output.interleaved_write_frame(&mut queued.packet))
581                .map_err(|error| encode_adapter_error_at("packet write failed", error))?;
582        }
583        Ok(())
584    }
585
586    pub(crate) fn drain_video_packets(&mut self, flushing: bool) -> Result<(), EncodeError> {
587        loop {
588            let received = timed(&mut self.stages.video_receive_packet, || {
589                self.video_encoder.receive_packet()
590            });
591            match received {
592                Ok(packet) => self.queue_video_packet(packet),
593                Err(RsmpegError::EncoderDrainError) if !flushing => return Ok(()),
594                Err(RsmpegError::EncoderFlushedError | RsmpegError::EncoderDrainError)
595                    if flushing =>
596                {
597                    return Ok(());
598                }
599                Err(error) => {
600                    return Err(encode_adapter_error_at(
601                        "video encoder packet drain failed",
602                        error,
603                    ));
604                }
605            }
606        }
607    }
608
609    /// Encodes a frame the caller already packed, without copying it.
610    ///
611    /// See [`crate::NativeVideoEncodeSink::write_prepared_video`]; this is the
612    /// same handover for the muxed audio/video sink.
613    ///
614    /// # Errors
615    /// Returns an error for a frame sized differently from this encoder, a
616    /// non-monotonic timestamp, or a codec failure.
617    pub fn write_prepared_video(
618        &mut self,
619        frame: &mut Yuv420pFrameBuffer,
620        pts: MediaTime,
621    ) -> Result<(), EncodeError> {
622        let (width, height) = frame.size();
623        if width != usize::try_from(self.width).unwrap_or(0)
624            || height != usize::try_from(self.height).unwrap_or(0)
625        {
626            return Err(EncodeError::Adapter(
627                "encoder input dimensions do not match its configuration".into(),
628            ));
629        }
630        let pts = av_rescale_q(pts.ticks(), tick_base(), self.video_encoder_time_base);
631        if self.last_video_pts.is_some_and(|previous| pts <= previous) {
632            return Err(EncodeError::InvalidTimestamp);
633        }
634        frame.set_presentation(pts, self.video_encoder_time_base);
635        timed(&mut self.stages.video_send_frame, || {
636            self.video_encoder.send_frame(Some(frame.frame()))
637        })
638        .map_err(|error| {
639            encode_adapter_error_at("video encoder rejected a YUV420P frame", error)
640        })?;
641        self.last_video_pts = Some(pts);
642        self.drain_video_packets(false)?;
643        self.write_ready_packets(false)
644    }
645
646    pub(crate) fn write_video(&mut self, frame: &DecodedVideoFrame) -> Result<(), EncodeError> {
647        if frame.visible_rect.width != u32::try_from(self.width).unwrap_or(0)
648            || frame.visible_rect.height != u32::try_from(self.height).unwrap_or(0)
649        {
650            return Err(EncodeError::Adapter(
651                "encoder input dimensions do not match its configuration".into(),
652            ));
653        }
654        let pts = av_rescale_q(frame.pts.ticks(), tick_base(), self.video_encoder_time_base);
655        if self.last_video_pts.is_some_and(|previous| pts <= previous) {
656            return Err(EncodeError::InvalidTimestamp);
657        }
658        if let Some(mut yuv) = self.yuv_input.convert(frame, self.width, self.height)? {
659            yuv.set_pts(pts);
660            yuv.set_time_base(self.video_encoder_time_base);
661            timed(&mut self.stages.video_send_frame, || {
662                self.video_encoder.send_frame(Some(&yuv))
663            })
664            .map_err(|error| {
665                encode_adapter_error_at("video encoder rejected a YUV420P frame", error)
666            })?;
667            self.last_video_pts = Some(pts);
668            self.drain_video_packets(false)?;
669            return self.write_ready_packets(false);
670        }
671        if frame.pixel_format != PixelFormat::Rgba8 || frame.planes.len() != 1 {
672            return Err(EncodeError::Adapter(
673                "encoder expects YUV planes or one RGBA8 plane".into(),
674            ));
675        }
676        let plane = &frame.planes[0];
677        let expected = usize::try_from(self.width)
678            .ok()
679            .and_then(|width| {
680                usize::try_from(self.height)
681                    .ok()
682                    .and_then(|height| width.checked_mul(height))
683            })
684            .and_then(|pixels| pixels.checked_mul(4))
685            .ok_or_else(|| EncodeError::Adapter("frame dimensions overflow".into()))?;
686        if plane.bytes.len() != expected
687            || plane.stride != u32::try_from(self.width).unwrap_or(0) * 4
688        {
689            return Err(EncodeError::Adapter(
690                "RGBA plane is not tightly packed".into(),
691            ));
692        }
693        // No alpha in the output, so premultiplied RGB is kept: frames arrive opaque over the
694        // project background, and dividing alpha out would show a translucent layer at full
695        // strength.
696        let rgba = alpha2_ffmpeg_bridge::rgba_frame(&plane.bytes, self.width, self.height)
697            .map_err(encode_adapter_error)?;
698        let yuv_image = AVImage::new(ffi::AV_PIX_FMT_YUV420P, self.width, self.height, 1)
699            .ok_or_else(|| EncodeError::Adapter("cannot allocate YUV input".into()))?;
700        let mut yuv = AVFrameWithImage::new(yuv_image);
701        self.video_scaler
702            .scale_frame(&rgba, 0, self.height, &mut yuv)
703            .map_err(encode_adapter_error)?;
704        yuv.set_pts(pts);
705        yuv.set_time_base(self.video_encoder_time_base);
706        timed(&mut self.stages.video_send_frame, || {
707            self.video_encoder.send_frame(Some(&yuv))
708        })
709        .map_err(|error| {
710            encode_adapter_error_at("video encoder rejected a converted YUV420P frame", error)
711        })?;
712        self.last_video_pts = Some(pts);
713        self.drain_video_packets(false)?;
714        self.write_ready_packets(false)
715    }
716
717    /// Buffers one audio block and hands the thread every whole encoder
718    /// frame it completes.
719    pub(crate) fn write_audio(&mut self, block: &AudioBlock) -> Result<(), EncodeError> {
720        let frames = self.audio_input.push(block)?;
721        let covered = self.audio_input.received_ticks();
722        for frame in frames {
723            self.audio.send(AudioCommand::Block(frame, covered))?;
724        }
725        // An audio-only stretch has no video packet to trigger a drain, so the queue would grow.
726        self.write_ready_packets(false)
727    }
728}
729
730/// What the audio encode thread needs from the sink that created it.
731pub(crate) struct AudioEncoderConfig {
732    pub(crate) encoder: AVCodecContext,
733    pub(crate) stream_index: i32,
734    pub(crate) encoder_time_base: AVRational,
735    pub(crate) stream_time_base: AVRational,
736    /// The rate the encoder runs at.
737    pub(crate) sample_rate: u32,
738    /// The sample format its frames carry.
739    pub(crate) sample_format: ffi::AVSampleFormat,
740}
741
742/// Starts the audio encoder on its own thread.
743///
744/// The channel is two deep: the coordinator hands blocks in faster than the
745/// video encoder consumes frames, and a deeper queue would only buy backlog
746/// the muxer has to hold anyway.
747///
748/// # Errors
749/// When the OS refuses the thread (a process or cgroup thread limit, or no
750/// memory for its stack).
751pub(crate) fn spawn_audio_encoder(
752    config: AudioEncoderConfig,
753) -> Result<AudioEncodeThread, EncodeError> {
754    let (commands, receiver) = mpsc::sync_channel(2);
755    let state = Arc::new(Mutex::new(AudioQueueState::default()));
756    let worker_state = Arc::clone(&state);
757    let run = move || run_audio_encoder(config, &receiver, &worker_state);
758    let builder = thread::Builder::new().name("alpha2-audio-encode".to_owned());
759    #[cfg(test)]
760    let spawned = if REFUSE_AUDIO_ENCODE_THREAD.with(std::cell::Cell::take) {
761        drop(run);
762        Err(std::io::Error::from(std::io::ErrorKind::WouldBlock))
763    } else {
764        builder.spawn(run)
765    };
766    #[cfg(not(test))]
767    let spawned = builder.spawn(run);
768    let join = spawned.map_err(|error| {
769        encode_adapter_error_at("failed to start the audio encode thread", error)
770    })?;
771    Ok(AudioEncodeThread {
772        commands: Some(commands),
773        join: Some(join),
774        state,
775    })
776}
777
778#[cfg(test)]
779thread_local! {
780    /// Makes the next audio encode thread on this thread fail to start, as a
781    /// thread limit does.
782    pub(crate) static REFUSE_AUDIO_ENCODE_THREAD: std::cell::Cell<bool> =
783        const { std::cell::Cell::new(false) };
784}
785
786/// Encodes blocks into packets until the channel closes or a `Finish` lands.
787fn run_audio_encoder(
788    mut config: AudioEncoderConfig,
789    commands: &Receiver<AudioCommand>,
790    state: &Arc<Mutex<AudioQueueState>>,
791) {
792    let mut encoded_samples = 0_u64;
793    loop {
794        let Ok(command) = commands.recv() else {
795            // The sink dropped the sender without finishing (abort or cancel); nothing will read
796            // the queue.
797            return;
798        };
799        let (channels, covered, last) = match command {
800            AudioCommand::Block(channels, covered) => (Some(channels), covered, false),
801            AudioCommand::Finish(covered) => (None, covered, true),
802        };
803        let mut stages = EncodeStageTimings::default();
804        let outcome = encode_audio_block(
805            &mut config,
806            channels.as_deref(),
807            &mut encoded_samples,
808            &mut stages,
809        );
810        let mut guard = state
811            .lock()
812            .unwrap_or_else(std::sync::PoisonError::into_inner);
813        guard.stages.audio_send_frame = guard
814            .stages
815            .audio_send_frame
816            .saturating_add(stages.audio_send_frame);
817        guard.stages.audio_receive_packet = guard
818            .stages
819            .audio_receive_packet
820            .saturating_add(stages.audio_receive_packet);
821        match outcome {
822            Ok(packets) => guard.packets.extend(packets),
823            Err(error) => {
824                if guard.failure.is_none() {
825                    guard.failure = Some(error.to_string());
826                }
827                guard.finished = true;
828                return;
829            }
830        }
831        guard.consumed_ticks = guard.consumed_ticks.max(covered);
832        if last {
833            guard.finished = true;
834            return;
835        }
836    }
837}
838
839/// Encodes one audio frame, or with none flushes the encoder (which is where
840/// its delayed tail packets come from), and drains whatever packets that
841/// produced.
842fn encode_audio_block(
843    config: &mut AudioEncoderConfig,
844    channels: Option<&[Vec<f32>]>,
845    encoded_samples: &mut u64,
846    stages: &mut EncodeStageTimings,
847) -> Result<Vec<QueuedPacket>, EncodeError> {
848    let last = channels.is_none();
849    if let Some(channels) = channels {
850        let pts = i64::try_from(*encoded_samples).map_err(|_| EncodeError::InvalidTimestamp)?;
851        let frame = alpha2_ffmpeg_bridge::audio_frame(
852            channels,
853            config.sample_format,
854            i32::try_from(config.sample_rate).unwrap_or(i32::MAX),
855            pts,
856        )
857        .map_err(encode_adapter_error)?;
858        timed(&mut stages.audio_send_frame, || {
859            config.encoder.send_frame(Some(&frame))
860        })
861        .map_err(|error| encode_adapter_error_at("audio encoder rejected a frame", error))?;
862        let samples = channels.first().map_or(0, Vec::len);
863        *encoded_samples =
864            encoded_samples.saturating_add(u64::try_from(samples).unwrap_or(u64::MAX));
865    } else {
866        timed(&mut stages.audio_send_frame, || {
867            config.encoder.send_frame(None)
868        })
869        .map_err(encode_adapter_error)?;
870    }
871    let mut packets = Vec::new();
872    loop {
873        let received = timed(&mut stages.audio_receive_packet, || {
874            config.encoder.receive_packet()
875        });
876        match received {
877            Ok(mut packet) => {
878                packet.set_stream_index(config.stream_index);
879                packet.rescale_ts(config.encoder_time_base, config.stream_time_base);
880                let timestamp = if packet.dts == ffi::AV_NOPTS_VALUE {
881                    packet.pts
882                } else {
883                    packet.dts
884                };
885                packets.push(QueuedPacket {
886                    sort_ticks: av_rescale_q(timestamp, config.stream_time_base, tick_base()),
887                    packet,
888                });
889            }
890            Err(RsmpegError::EncoderDrainError) if !last => return Ok(packets),
891            Err(RsmpegError::EncoderFlushedError | RsmpegError::EncoderDrainError) if last => {
892                return Ok(packets);
893            }
894            Err(error) => {
895                return Err(encode_adapter_error_at(
896                    "audio encoder packet drain failed",
897                    error,
898                ));
899            }
900        }
901    }
902}
903
904impl EncodeSink for NativeAvEncodeSink {
905    fn write(&mut self, event: EncodeEvent, cancel: &CancelToken) -> Result<(), EncodeError> {
906        if cancel.is_cancelled() {
907            self.cleanup()?;
908            return Err(EncodeError::Cancelled);
909        }
910        let ticks = match &event {
911            EncodeEvent::Video(frame) => frame.pts.ticks(),
912            EncodeEvent::Audio(block) => block.start_time().ticks(),
913        };
914        if self
915            .last_input_ticks
916            .is_some_and(|previous| ticks < previous)
917        {
918            return Err(EncodeError::InvalidTimestamp);
919        }
920        match event {
921            EncodeEvent::Video(frame) => self.write_video(&frame)?,
922            EncodeEvent::Audio(block) => self.write_audio(&block)?,
923        }
924        self.last_input_ticks = Some(ticks);
925        Ok(())
926    }
927    fn finish(&mut self, cancel: &CancelToken) -> Result<(), EncodeError> {
928        if cancel.is_cancelled() {
929            self.cleanup()?;
930            return Err(EncodeError::Cancelled);
931        }
932        // Let the thread flush its encoder and wait, so every audio packet exists before the muxer
933        // decides the final interleave.
934        let covered = self.audio_input.received_ticks();
935        for frame in self.audio_input.finish() {
936            self.audio.send(AudioCommand::Block(frame, covered))?;
937        }
938        self.audio.send(AudioCommand::Finish(covered))?;
939        self.audio.shutdown();
940        if let Some(error) = self.audio.lock().failure.clone() {
941            return Err(EncodeError::Adapter(error));
942        }
943        timed(&mut self.stages.video_send_frame, || {
944            self.video_encoder.send_frame(None)
945        })
946        .map_err(encode_adapter_error)?;
947        self.drain_video_packets(true)?;
948        self.write_ready_packets(true)?;
949        beat_encode_call();
950        self.output
951            .as_mut()
952            .ok_or_else(|| EncodeError::Adapter("muxer is already closed".into()))?
953            .write_trailer()
954            .map_err(encode_adapter_error)?;
955        // A write error reported only at close fails the export here, before the rename.
956        if let Some(output) = self.output.take() {
957            beat_encode_call();
958            output
959                .close()
960                .map_err(|error| encode_adapter_error_at("closing the output", error))?;
961        }
962        crate::partial::publish(&self.partial_path, &self.final_path)?;
963        self.complete = true;
964        Ok(())
965    }
966    fn abort(&mut self) -> Result<(), EncodeError> {
967        self.cleanup()
968    }
969}
970
971impl Drop for NativeAvEncodeSink {
972    fn drop(&mut self) {
973        if !self.complete {
974            let _ = self.cleanup();
975        }
976    }
977}
978
979impl NativeWavEncodeSink {
980    /// Creates a non-overwriting sibling-partial WAV output of `channels` in
981    /// the count's default layout.
982    ///
983    /// # Errors
984    /// Returns an error for invalid PCM format, existing paths, or filesystem
985    /// failures.
986    pub fn create(
987        path: impl AsRef<Path>,
988        sample_rate: u32,
989        channels: u16,
990    ) -> Result<Self, EncodeError> {
991        let speakers = SpeakerLayout::default_for(usize::from(channels))
992            .ok_or_else(|| EncodeError::Adapter("invalid WAV format".into()))?;
993        Self::create_with_speakers(path, sample_rate, speakers)
994    }
995
996    /// Creates a WAV output whose blocks feed `speakers`.
997    ///
998    /// The count's default layout gets a plain header, which every reader
999    /// takes as that layout. Any other is written as `WAVE_FORMAT_EXTENSIBLE`
1000    /// with its channel mask, the planes in the mask's bit order, which WAVE
1001    /// requires.
1002    ///
1003    /// # Errors
1004    /// Returns an error for invalid PCM format, a layout a WAVE channel mask
1005    /// cannot say (an unnamed or repeated speaker, or one past the 18 WAVE
1006    /// positions), existing paths, or filesystem failures.
1007    pub fn create_with_speakers(
1008        path: impl AsRef<Path>,
1009        sample_rate: u32,
1010        speakers: SpeakerLayout,
1011    ) -> Result<Self, EncodeError> {
1012        let channels = u16::try_from(speakers.channels())
1013            .ok()
1014            .filter(|_| sample_rate != 0 && speakers.channels() <= MAX_AUDIO_CHANNELS)
1015            .ok_or_else(|| EncodeError::Adapter("invalid WAV format".into()))?;
1016        let (channel_mask, plane_order) = wav_channel_layout(&speakers)?;
1017        let final_path = path.as_ref().to_owned();
1018        let partial = PartialOutput::claim(&final_path)?;
1019        let partial_path = partial.path().to_owned();
1020        let mut file = BufWriter::with_capacity(WAV_WRITE_BUFFER, partial.open_file()?);
1021        file.write_all(&wav_header_with_mask(
1022            sample_rate,
1023            channels,
1024            channel_mask,
1025            0,
1026        )?)
1027        .map_err(encode_adapter_error)?;
1028        let partial_lock = partial.keep();
1029        Ok(Self {
1030            final_path,
1031            partial_path,
1032            _partial_lock: partial_lock,
1033            file: Some(file),
1034            sample_rate,
1035            channels,
1036            speakers,
1037            channel_mask,
1038            plane_order,
1039            frames_written: 0,
1040            complete: false,
1041            interleaved: Vec::new(),
1042        })
1043    }
1044
1045    pub(crate) fn write_block(&mut self, block: &AudioBlock) -> Result<(), EncodeError> {
1046        if block.sample_rate != self.sample_rate
1047            || block.channels.len() != usize::from(self.channels)
1048            || block.channels.is_empty()
1049        {
1050            return Err(EncodeError::Adapter("audio block format changed".into()));
1051        }
1052        if block.speakers != self.speakers {
1053            return Err(EncodeError::Adapter("audio block speakers changed".into()));
1054        }
1055        let frames = block.channels[0].len();
1056        if block.channels.iter().any(|channel| channel.len() != frames) {
1057            return Err(EncodeError::Adapter("audio channel lengths differ".into()));
1058        }
1059        if u64::try_from(block.start_sample).ok() != Some(self.frames_written) {
1060            return Err(EncodeError::InvalidTimestamp);
1061        }
1062        // Refused before writing, rather than after writing 4 GiB that is then removed.
1063        let frames_after = self
1064            .frames_written
1065            .saturating_add(u64::try_from(frames).unwrap_or(u64::MAX));
1066        self.data_bytes(frames_after)?;
1067        let file = self
1068            .file
1069            .as_mut()
1070            .ok_or_else(|| EncodeError::Adapter("WAV sink is closed".into()))?;
1071        self.interleaved.clear();
1072        for frame in 0..frames {
1073            for plane in &self.plane_order {
1074                self.interleaved
1075                    .extend_from_slice(&block.channels[*plane][frame].to_le_bytes());
1076            }
1077        }
1078        file.write_all(&self.interleaved)
1079            .map_err(encode_adapter_error)?;
1080        self.frames_written = self
1081            .frames_written
1082            .saturating_add(u64::try_from(frames).unwrap_or(u64::MAX));
1083        Ok(())
1084    }
1085
1086    /// The `data` chunk's size once `frames` frames are written, when the
1087    /// RIFF chunk holding it stays within its 32-bit size.
1088    fn data_bytes(&self, frames: u64) -> Result<u32, EncodeError> {
1089        let overhead = if self.channel_mask.is_some() {
1090            WAV_EXTENSIBLE_OVERHEAD
1091        } else {
1092            WAV_PLAIN_OVERHEAD
1093        };
1094        #[cfg(test)]
1095        let limit = RIFF_LIMIT.with(std::cell::Cell::get);
1096        #[cfg(not(test))]
1097        let limit = u64::from(u32::MAX);
1098        frames
1099            .checked_mul(u64::from(self.channels))
1100            .and_then(|samples| samples.checked_mul(4))
1101            .filter(|bytes| bytes.saturating_add(u64::from(overhead)) <= limit)
1102            .and_then(|bytes| u32::try_from(bytes).ok())
1103            .ok_or_else(|| {
1104                EncodeError::Adapter(format!(
1105                    "{frames} frames of {} channels are more than the 4 GiB a WAV file holds",
1106                    self.channels
1107                ))
1108            })
1109    }
1110
1111    pub(crate) fn commit(&mut self) -> Result<(), EncodeError> {
1112        let data_bytes = self.data_bytes(self.frames_written)?;
1113        let header = wav_header_with_mask(
1114            self.sample_rate,
1115            self.channels,
1116            self.channel_mask,
1117            data_bytes,
1118        )?;
1119        let mut file = self
1120            .file
1121            .take()
1122            .ok_or_else(|| EncodeError::Adapter("WAV sink is closed".into()))?;
1123        file.seek(SeekFrom::Start(0))
1124            .and_then(|_| file.write_all(&header))
1125            .and_then(|()| file.flush())
1126            .map_err(encode_adapter_error)?;
1127        drop(file);
1128        crate::partial::publish(&self.partial_path, &self.final_path)?;
1129        self.complete = true;
1130        Ok(())
1131    }
1132}
1133
1134impl AudioBlockSink for NativeWavEncodeSink {
1135    fn write_audio(&mut self, block: &AudioBlock, cancel: &CancelToken) -> Result<(), EncodeError> {
1136        if cancel.is_cancelled() {
1137            self.abort_audio()?;
1138            return Err(EncodeError::Cancelled);
1139        }
1140        self.write_block(block)
1141    }
1142    fn finish_audio(&mut self, cancel: &CancelToken) -> Result<(), EncodeError> {
1143        if cancel.is_cancelled() {
1144            self.abort_audio()?;
1145            return Err(EncodeError::Cancelled);
1146        }
1147        self.commit()
1148    }
1149    fn abort_audio(&mut self) -> Result<(), EncodeError> {
1150        // Close without writing out samples the file is about to lose.
1151        drop(self.file.take().map(BufWriter::into_parts));
1152        match fs::remove_file(&self.partial_path) {
1153            Ok(()) => Ok(()),
1154            Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
1155            Err(error) => Err(encode_adapter_error(error)),
1156        }
1157    }
1158}
1159
1160impl EncodeSink for NativeWavEncodeSink {
1161    fn write(&mut self, event: EncodeEvent, cancel: &CancelToken) -> Result<(), EncodeError> {
1162        match event {
1163            EncodeEvent::Audio(block) => self.write_audio(&block, cancel),
1164            EncodeEvent::Video(_) => {
1165                Err(EncodeError::Adapter("WAV sink accepts audio only".into()))
1166            }
1167        }
1168    }
1169    fn finish(&mut self, cancel: &CancelToken) -> Result<(), EncodeError> {
1170        self.finish_audio(cancel)
1171    }
1172    fn abort(&mut self) -> Result<(), EncodeError> {
1173        self.abort_audio()
1174    }
1175}
1176
1177impl Drop for NativeWavEncodeSink {
1178    fn drop(&mut self) {
1179        if !self.complete {
1180            let _ = self.abort_audio();
1181        }
1182    }
1183}
1184
1185/// The channel mask a WAV of `speakers` is written with, `None` for the
1186/// count's default layout, and the plane each interleaved channel comes from.
1187fn wav_channel_layout(speakers: &SpeakerLayout) -> Result<(Option<u32>, Vec<usize>), EncodeError> {
1188    let identity = (0..speakers.channels()).collect::<Vec<_>>();
1189    if SpeakerLayout::default_for(speakers.channels()) == Some(*speakers) {
1190        return Ok((None, identity));
1191    }
1192    let mut mask = 0_u32;
1193    for speaker in speakers.speakers() {
1194        let bit = speaker.ffmpeg_channel();
1195        if bit >= 18 || mask & (1 << bit) != 0 {
1196            return Err(EncodeError::Adapter(format!(
1197                "a WAVE channel mask cannot say the speakers {}",
1198                speakers.describe()
1199            )));
1200        }
1201        mask |= 1 << bit;
1202    }
1203    let mut order = identity;
1204    order.sort_by_key(|plane| speakers.speakers()[*plane].ffmpeg_channel());
1205    Ok((Some(mask), order))
1206}
1207
1208/// The RIFF chunk's size beyond its `data` chunk's samples, plain header.
1209const WAV_PLAIN_OVERHEAD: u32 = 36;
1210/// The same with a `WAVE_FORMAT_EXTENSIBLE` header.
1211const WAV_EXTENSIBLE_OVERHEAD: u32 = 60;
1212
1213#[cfg(test)]
1214thread_local! {
1215    /// The most bytes a RIFF chunk holds, for WAV sinks on this thread: a
1216    /// test lowers it from `u32::MAX`.
1217    pub(crate) static RIFF_LIMIT: std::cell::Cell<u64> =
1218        const { std::cell::Cell::new(u32::MAX as u64) };
1219}
1220
1221fn riff_size(data_bytes: u32, overhead: u32) -> Result<u32, EncodeError> {
1222    data_bytes
1223        .checked_add(overhead)
1224        .ok_or_else(|| EncodeError::Adapter("WAV exceeds the RIFF size limit".into()))
1225}
1226
1227/// A WAV header: a plain IEEE-float one, or `WAVE_FORMAT_EXTENSIBLE` with
1228/// `channel_mask` and the IEEE-float subformat.
1229///
1230/// # Errors
1231/// When the RIFF chunk's size does not fit its 32 bits.
1232pub(crate) fn wav_header_with_mask(
1233    sample_rate: u32,
1234    channels: u16,
1235    channel_mask: Option<u32>,
1236    data_bytes: u32,
1237) -> Result<Vec<u8>, EncodeError> {
1238    let Some(mask) = channel_mask else {
1239        return Ok(wav_header(sample_rate, channels, data_bytes)?.to_vec());
1240    };
1241    let plain = wav_header(sample_rate, channels, 0)?;
1242    let mut header = Vec::with_capacity(68);
1243    header.extend_from_slice(b"RIFF");
1244    header.extend_from_slice(&riff_size(data_bytes, WAV_EXTENSIBLE_OVERHEAD)?.to_le_bytes());
1245    header.extend_from_slice(b"WAVEfmt ");
1246    header.extend_from_slice(&40_u32.to_le_bytes());
1247    header.extend_from_slice(&0xfffe_u16.to_le_bytes());
1248    // Channels, rate, byte rate, block align and bits per sample, as the plain header has them.
1249    header.extend_from_slice(&plain[22..36]);
1250    header.extend_from_slice(&22_u16.to_le_bytes());
1251    header.extend_from_slice(&32_u16.to_le_bytes());
1252    header.extend_from_slice(&mask.to_le_bytes());
1253    // KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.
1254    header.extend_from_slice(&[
1255        0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b,
1256        0x71,
1257    ]);
1258    header.extend_from_slice(b"data");
1259    header.extend_from_slice(&data_bytes.to_le_bytes());
1260    Ok(header)
1261}
1262
1263/// A plain IEEE-float WAV header.
1264///
1265/// # Errors
1266/// When the RIFF chunk's size does not fit its 32 bits.
1267pub(crate) fn wav_header(
1268    sample_rate: u32,
1269    channels: u16,
1270    data_bytes: u32,
1271) -> Result<[u8; 44], EncodeError> {
1272    let mut header = [0_u8; 44];
1273    header[0..4].copy_from_slice(b"RIFF");
1274    header[4..8].copy_from_slice(&riff_size(data_bytes, WAV_PLAIN_OVERHEAD)?.to_le_bytes());
1275    header[8..12].copy_from_slice(b"WAVE");
1276    header[12..16].copy_from_slice(b"fmt ");
1277    header[16..20].copy_from_slice(&16_u32.to_le_bytes());
1278    header[20..22].copy_from_slice(&3_u16.to_le_bytes());
1279    header[22..24].copy_from_slice(&channels.to_le_bytes());
1280    header[24..28].copy_from_slice(&sample_rate.to_le_bytes());
1281    let byte_rate = sample_rate
1282        .saturating_mul(u32::from(channels))
1283        .saturating_mul(4);
1284    header[28..32].copy_from_slice(&byte_rate.to_le_bytes());
1285    header[32..34].copy_from_slice(&channels.saturating_mul(4).to_le_bytes());
1286    header[34..36].copy_from_slice(&32_u16.to_le_bytes());
1287    header[36..40].copy_from_slice(b"data");
1288    header[40..44].copy_from_slice(&data_bytes.to_le_bytes());
1289    Ok(header)
1290}