1use 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#[derive(Copy, Clone, Debug, Eq, PartialEq)]
31pub struct AacStreamConfig {
32 pub sample_rate: u32,
34 pub channels: u16,
36 pub bit_rate: u32,
38}
39
40pub(crate) struct VideoStream {
42 pub(crate) parameters: AVCodecParameters,
44 pub(crate) time_base: AVRational,
46 pub(crate) copied: bool,
49}
50
51pub(crate) enum AudioStream {
53 Encode(AacStreamConfig),
55 Copy {
57 parameters: AVCodecParameters,
59 time_base: AVRational,
61 },
62}
63
64enum MuxedAudioTrack {
66 Encoded(MuxedAudio),
67 Copied {
68 stream_index: i32,
69 source_time_base: AVRational,
70 stream_time_base: AVRational,
71 },
72}
73
74pub(crate) struct Mp4Muxer {
76 final_path: PathBuf,
77 partial_path: PathBuf,
78 _partial_lock: crate::partial::PartialLock,
80 format: Option<OutputFile>,
81 stream_index: i32,
82 stream_time_base: AVRational,
85 complete: bool,
86 audio: Option<MuxedAudioTrack>,
87 audio_stream_index: i32,
88 audio_packets: VecDeque<QueuedPacket>,
91 pending_header: Option<PendingHeader>,
94 parameter_sets: Option<Vec<u8>>,
98 audio_rescale: Option<(AVRational, AVRational)>,
101}
102
103struct MuxedAudio {
105 thread: AudioEncodeThread,
106 input: AacInput,
107}
108
109impl Mp4Muxer {
110 #[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 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 #[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 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 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 ¶meter_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 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 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 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 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 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 self.write_ready_audio(false, stages)
342 }
343
344 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 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 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 #[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 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 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 pub(crate) fn abort(&mut self) {
472 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 self.abort();
488 }
489}
490
491struct Unwritten {
494 final_path: PathBuf,
495 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 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 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 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 fn time_base(&self, index: i32) -> AVRational {
584 self.format.streams()[usize::try_from(index).unwrap_or(0)].time_base
585 }
586
587 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 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 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
653struct PendingHeader {
655 audio_time_base: Option<AVRational>,
658}
659
660struct OpenedAac {
662 encoder: AVCodecContext,
663 encoder_time_base: AVRational,
664 project_rate: u32,
666 encoder_rate: u32,
668 channels: usize,
669 frame_size: usize,
670}
671
672fn 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
711fn 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 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
754pub(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
770pub(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}