scuffle_ffmpeg/
encoder.rs

1use std::ptr::NonNull;
2
3use crate::codec::EncoderCodec;
4use crate::dict::Dictionary;
5use crate::error::{FfmpegError, FfmpegErrorCode};
6use crate::ffi::*;
7use crate::frame::{AudioChannelLayout, GenericFrame};
8use crate::io::Output;
9use crate::packet::Packet;
10use crate::rational::Rational;
11use crate::smart_object::SmartPtr;
12use crate::{AVFormatFlags, AVPixelFormat, AVSampleFormat};
13
14/// Represents an encoder.
15pub struct Encoder {
16    incoming_time_base: Rational,
17    outgoing_time_base: Rational,
18    encoder: SmartPtr<AVCodecContext>,
19    stream_index: i32,
20    previous_dts: i64,
21}
22
23/// Safety: `Encoder` can be sent between threads.
24unsafe impl Send for Encoder {}
25
26/// Represents the settings for a video encoder.
27#[derive(bon::Builder)]
28pub struct VideoEncoderSettings {
29    width: i32,
30    height: i32,
31    frame_rate: Rational,
32    pixel_format: AVPixelFormat,
33    gop_size: Option<i32>,
34    qmax: Option<i32>,
35    qmin: Option<i32>,
36    thread_count: Option<i32>,
37    thread_type: Option<i32>,
38    sample_aspect_ratio: Option<Rational>,
39    bitrate: Option<i64>,
40    rc_min_rate: Option<i64>,
41    rc_max_rate: Option<i64>,
42    rc_buffer_size: Option<i32>,
43    max_b_frames: Option<i32>,
44    codec_specific_options: Option<Dictionary>,
45    flags: Option<i32>,
46    flags2: Option<i32>,
47}
48
49impl VideoEncoderSettings {
50    fn apply(self, encoder: &mut AVCodecContext) -> Result<(), FfmpegError> {
51        if self.width <= 0 || self.height <= 0 || self.frame_rate.numerator <= 0 || self.pixel_format == AVPixelFormat::None
52        {
53            return Err(FfmpegError::Arguments(
54                "width, height, frame_rate and pixel_format must be set",
55            ));
56        }
57
58        encoder.width = self.width;
59        encoder.height = self.height;
60        encoder.pix_fmt = self.pixel_format.into();
61        encoder.sample_aspect_ratio = self
62            .sample_aspect_ratio
63            .map(Into::into)
64            .unwrap_or(encoder.sample_aspect_ratio);
65        encoder.framerate = self.frame_rate.into();
66        encoder.thread_count = self.thread_count.unwrap_or(encoder.thread_count);
67        encoder.thread_type = self.thread_type.unwrap_or(encoder.thread_type);
68        encoder.gop_size = self.gop_size.unwrap_or(encoder.gop_size);
69        encoder.qmax = self.qmax.unwrap_or(encoder.qmax);
70        encoder.qmin = self.qmin.unwrap_or(encoder.qmin);
71        encoder.bit_rate = self.bitrate.unwrap_or(encoder.bit_rate);
72        encoder.rc_min_rate = self.rc_min_rate.unwrap_or(encoder.rc_min_rate);
73        encoder.rc_max_rate = self.rc_max_rate.unwrap_or(encoder.rc_max_rate);
74        encoder.rc_buffer_size = self.rc_buffer_size.unwrap_or(encoder.rc_buffer_size);
75        encoder.max_b_frames = self.max_b_frames.unwrap_or(encoder.max_b_frames);
76        encoder.flags = self.flags.unwrap_or(encoder.flags);
77        encoder.flags2 = self.flags2.unwrap_or(encoder.flags2);
78
79        Ok(())
80    }
81}
82
83/// Represents the settings for an audio encoder.
84#[derive(bon::Builder)]
85pub struct AudioEncoderSettings {
86    sample_rate: i32,
87    ch_layout: AudioChannelLayout,
88    sample_fmt: AVSampleFormat,
89    thread_count: Option<i32>,
90    thread_type: Option<i32>,
91    bitrate: Option<i64>,
92    rc_min_rate: Option<i64>,
93    rc_max_rate: Option<i64>,
94    rc_buffer_size: Option<i32>,
95    codec_specific_options: Option<Dictionary>,
96    flags: Option<i32>,
97    flags2: Option<i32>,
98}
99
100impl AudioEncoderSettings {
101    fn apply(self, encoder: &mut AVCodecContext) -> Result<(), FfmpegError> {
102        if self.sample_rate <= 0 || self.sample_fmt == AVSampleFormat::None {
103            return Err(FfmpegError::Arguments(
104                "sample_rate, channel_layout and sample_fmt must be set",
105            ));
106        }
107
108        encoder.sample_rate = self.sample_rate;
109        self.ch_layout.apply(&mut encoder.ch_layout);
110        encoder.sample_fmt = self.sample_fmt.into();
111        encoder.thread_count = self.thread_count.unwrap_or(encoder.thread_count);
112        encoder.thread_type = self.thread_type.unwrap_or(encoder.thread_type);
113        encoder.bit_rate = self.bitrate.unwrap_or(encoder.bit_rate);
114        encoder.rc_min_rate = self.rc_min_rate.unwrap_or(encoder.rc_min_rate);
115        encoder.rc_max_rate = self.rc_max_rate.unwrap_or(encoder.rc_max_rate);
116        encoder.rc_buffer_size = self.rc_buffer_size.unwrap_or(encoder.rc_buffer_size);
117        encoder.flags = self.flags.unwrap_or(encoder.flags);
118        encoder.flags2 = self.flags2.unwrap_or(encoder.flags2);
119
120        Ok(())
121    }
122}
123
124/// Represents the settings for an encoder.
125pub enum EncoderSettings {
126    /// Video encoder settings.
127    Video(VideoEncoderSettings),
128    /// Audio encoder settings.
129    Audio(AudioEncoderSettings),
130}
131
132impl EncoderSettings {
133    fn apply(self, encoder: &mut AVCodecContext) -> Result<(), FfmpegError> {
134        match self {
135            EncoderSettings::Video(video_settings) => video_settings.apply(encoder),
136            EncoderSettings::Audio(audio_settings) => audio_settings.apply(encoder),
137        }
138    }
139
140    const fn codec_specific_options(&mut self) -> Option<&mut Dictionary> {
141        match self {
142            EncoderSettings::Video(video_settings) => video_settings.codec_specific_options.as_mut(),
143            EncoderSettings::Audio(audio_settings) => audio_settings.codec_specific_options.as_mut(),
144        }
145    }
146}
147
148impl From<VideoEncoderSettings> for EncoderSettings {
149    fn from(settings: VideoEncoderSettings) -> Self {
150        EncoderSettings::Video(settings)
151    }
152}
153
154impl From<AudioEncoderSettings> for EncoderSettings {
155    fn from(settings: AudioEncoderSettings) -> Self {
156        EncoderSettings::Audio(settings)
157    }
158}
159
160impl Encoder {
161    /// Creates a new encoder.
162    pub fn new<T: Send + Sync>(
163        codec: EncoderCodec,
164        output: &mut Output<T>,
165        incoming_time_base: impl Into<Rational>,
166        outgoing_time_base: impl Into<Rational>,
167        settings: impl Into<EncoderSettings>,
168    ) -> Result<Self, FfmpegError> {
169        if codec.as_ptr().is_null() {
170            return Err(FfmpegError::NoEncoder);
171        }
172
173        let mut settings = settings.into();
174
175        let global_header = output
176            .output_flags()
177            .is_some_and(|flags| flags & AVFormatFlags::GlobalHeader != 0);
178
179        let destructor = |ptr: &mut *mut AVCodecContext| {
180            // Safety: `avcodec_free_context` is safe to call when the pointer is valid, and it is because it comes from `avcodec_alloc_context3`.
181            unsafe { avcodec_free_context(ptr) };
182        };
183
184        // Safety: `avcodec_alloc_context3` is safe to call.
185        let encoder = unsafe { avcodec_alloc_context3(codec.as_ptr()) };
186
187        // Safety: The pointer here is valid and the destructor has been setup to handle the cleanup.
188        let mut encoder = unsafe { SmartPtr::wrap_non_null(encoder, destructor) }.ok_or(FfmpegError::Alloc)?;
189
190        let mut ost = output.add_stream(None).ok_or(FfmpegError::NoStream)?;
191
192        let encoder_mut = encoder.as_deref_mut_except();
193
194        let incoming_time_base = incoming_time_base.into();
195        let outgoing_time_base = outgoing_time_base.into();
196
197        encoder_mut.time_base = incoming_time_base.into();
198
199        let mut codec_options = settings.codec_specific_options().cloned();
200
201        let codec_options_ptr = codec_options
202            .as_mut()
203            .map(|options| options.as_mut_ptr_ref() as *mut *mut _)
204            .unwrap_or(std::ptr::null_mut());
205
206        settings.apply(encoder_mut)?;
207
208        if global_header {
209            encoder_mut.flags |= AV_CODEC_FLAG_GLOBAL_HEADER as i32;
210        }
211
212        // Safety: `avcodec_open2` is safe to call, 'encoder' and 'codec' and
213        // 'codec_options_ptr' are a valid pointers.
214        FfmpegErrorCode(unsafe { avcodec_open2(encoder_mut, codec.as_ptr(), codec_options_ptr) }).result()?;
215
216        // Safety: The pointer here is valid.
217        let ost_mut = unsafe { NonNull::new(ost.as_mut_ptr()).ok_or(FfmpegError::NoStream)?.as_mut() };
218
219        // Safety: `avcodec_parameters_from_context` is safe to call, 'ost' and
220        // 'encoder' are valid pointers.
221        FfmpegErrorCode(unsafe { avcodec_parameters_from_context(ost_mut.codecpar, encoder_mut) }).result()?;
222
223        ost.set_time_base(outgoing_time_base);
224
225        Ok(Self {
226            incoming_time_base,
227            outgoing_time_base,
228            encoder,
229            stream_index: ost.index(),
230            previous_dts: i64::MIN,
231        })
232    }
233
234    /// Sends an EOF frame to the encoder.
235    pub fn send_eof(&mut self) -> Result<(), FfmpegError> {
236        // Safety: `self.encoder` is a valid pointer.
237        FfmpegErrorCode(unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), std::ptr::null()) }).result()?;
238        Ok(())
239    }
240
241    /// Sends a frame to the encoder.
242    pub fn send_frame(&mut self, frame: &GenericFrame) -> Result<(), FfmpegError> {
243        // Safety: `self.encoder` and `frame` are valid pointers.
244        FfmpegErrorCode(unsafe { avcodec_send_frame(self.encoder.as_mut_ptr(), frame.as_ptr()) }).result()?;
245        Ok(())
246    }
247
248    /// Receives a packet from the encoder.
249    pub fn receive_packet(&mut self) -> Result<Option<Packet>, FfmpegError> {
250        let mut packet = Packet::new()?;
251
252        // Safety: `self.encoder` and `packet` are valid pointers.
253        let ret = FfmpegErrorCode(unsafe { avcodec_receive_packet(self.encoder.as_mut_ptr(), packet.as_mut_ptr()) });
254
255        match ret {
256            FfmpegErrorCode::Eagain | FfmpegErrorCode::Eof => Ok(None),
257            code if code.is_success() => {
258                if cfg!(debug_assertions) {
259                    debug_assert!(
260                        packet.dts().is_some(),
261                        "packet dts is none, this should never happen, please report this bug"
262                    );
263                    let packet_dts = packet.dts().unwrap();
264                    debug_assert!(
265                        packet_dts >= self.previous_dts,
266                        "packet dts is less than previous dts: {} >= {}",
267                        packet_dts,
268                        self.previous_dts
269                    );
270                    self.previous_dts = packet_dts;
271                }
272
273                packet.convert_timebase(self.incoming_time_base, self.outgoing_time_base);
274                packet.set_stream_index(self.stream_index);
275                Ok(Some(packet))
276            }
277            code => Err(FfmpegError::Code(code)),
278        }
279    }
280
281    /// Returns the stream index of the encoder.
282    pub const fn stream_index(&self) -> i32 {
283        self.stream_index
284    }
285
286    /// Returns the incoming time base of the encoder.
287    pub const fn incoming_time_base(&self) -> Rational {
288        self.incoming_time_base
289    }
290
291    /// Returns the outgoing time base of the encoder.
292    pub const fn outgoing_time_base(&self) -> Rational {
293        self.outgoing_time_base
294    }
295}
296
297#[cfg(test)]
298#[cfg_attr(all(test, coverage_nightly), coverage(off))]
299mod tests {
300    use rusty_ffmpeg::ffi::AVRational;
301    use scuffle_bytes_util::zero_copy::Deserialize;
302    use scuffle_bytes_util::{BytesCow, IoResultExt};
303
304    use crate::codec::EncoderCodec;
305    use crate::decoder::Decoder;
306    use crate::dict::Dictionary;
307    use crate::encoder::{AudioChannelLayout, AudioEncoderSettings, Encoder, EncoderSettings, VideoEncoderSettings};
308    use crate::error::FfmpegError;
309    use crate::ffi::AVCodecContext;
310    use crate::io::{Input, Output, OutputOptions};
311    use crate::rational::Rational;
312    use crate::{AVChannelOrder, AVCodecID, AVMediaType, AVPixelFormat, AVSampleFormat, file_path};
313
314    #[test]
315    fn test_video_encoder_apply() {
316        let width = 1920;
317        let height = 1080;
318        let frame_rate = 30;
319        let pixel_format = AVPixelFormat::Yuv420p;
320        let sample_aspect_ratio = 1;
321        let gop_size = 12;
322        let qmax = 31;
323        let qmin = 1;
324        let thread_count = 4;
325        let thread_type = 2;
326        let bitrate = 8_000;
327        let rc_min_rate = 500_000;
328        let rc_max_rate = 2_000_000;
329        let rc_buffer_size = 1024;
330        let max_b_frames = 3;
331        let mut codec_specific_options = Dictionary::new();
332        codec_specific_options.set("preset", "ultrafast").unwrap();
333        codec_specific_options.set("crf", "23").unwrap();
334        let flags = 0x01;
335        let flags2 = 0x02;
336
337        let settings = VideoEncoderSettings::builder()
338            .width(width)
339            .height(height)
340            .frame_rate(frame_rate.into())
341            .pixel_format(pixel_format)
342            .sample_aspect_ratio(sample_aspect_ratio.into())
343            .gop_size(gop_size)
344            .qmax(qmax)
345            .qmin(qmin)
346            .thread_count(thread_count)
347            .thread_type(thread_type)
348            .bitrate(bitrate)
349            .rc_min_rate(rc_min_rate)
350            .rc_max_rate(rc_max_rate)
351            .rc_buffer_size(rc_buffer_size)
352            .max_b_frames(max_b_frames)
353            .codec_specific_options(codec_specific_options)
354            .flags(flags)
355            .flags2(flags2)
356            .build();
357
358        assert_eq!(settings.width, width);
359        assert_eq!(settings.height, height);
360        assert_eq!(settings.frame_rate, frame_rate.into());
361        assert_eq!(settings.pixel_format, pixel_format);
362        assert_eq!(settings.sample_aspect_ratio, Some(sample_aspect_ratio.into()));
363        assert_eq!(settings.gop_size, Some(gop_size));
364        assert_eq!(settings.qmax, Some(qmax));
365        assert_eq!(settings.qmin, Some(qmin));
366        assert_eq!(settings.thread_count, Some(thread_count));
367        assert_eq!(settings.thread_type, Some(thread_type));
368        assert_eq!(settings.bitrate, Some(bitrate));
369        assert_eq!(settings.rc_min_rate, Some(rc_min_rate));
370        assert_eq!(settings.rc_max_rate, Some(rc_max_rate));
371        assert_eq!(settings.rc_buffer_size, Some(rc_buffer_size));
372        assert_eq!(settings.max_b_frames, Some(max_b_frames));
373        assert!(settings.codec_specific_options.is_some());
374        let actual_codec_specific_options = settings.codec_specific_options.as_ref().unwrap();
375        assert_eq!(actual_codec_specific_options.get(c"preset"), Some(c"ultrafast"));
376        assert_eq!(actual_codec_specific_options.get(c"crf"), Some(c"23"));
377        assert_eq!(settings.flags, Some(flags));
378        assert_eq!(settings.flags2, Some(flags2));
379
380        // Safety: We are zeroing the memory for the encoder context.
381        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
382        let result = settings.apply(&mut encoder);
383        assert!(result.is_ok(), "Failed to apply settings: {:?}", result.err());
384
385        assert_eq!(encoder.width, width);
386        assert_eq!(encoder.height, height);
387        assert_eq!(AVPixelFormat(encoder.pix_fmt), pixel_format);
388        assert_eq!(Rational::from(encoder.sample_aspect_ratio), sample_aspect_ratio.into());
389        assert_eq!(Rational::from(encoder.framerate), frame_rate.into());
390        assert_eq!(encoder.thread_count, thread_count);
391        assert_eq!(encoder.thread_type, thread_type);
392        assert_eq!(encoder.gop_size, gop_size);
393        assert_eq!(encoder.qmax, qmax);
394        assert_eq!(encoder.qmin, qmin);
395        assert_eq!(encoder.bit_rate, bitrate);
396        assert_eq!(encoder.rc_min_rate, rc_min_rate);
397        assert_eq!(encoder.rc_max_rate, rc_max_rate);
398        assert_eq!(encoder.rc_buffer_size, rc_buffer_size);
399        assert_eq!(encoder.max_b_frames, max_b_frames);
400        assert_eq!(encoder.flags, flags);
401        assert_eq!(encoder.flags2, flags2);
402    }
403
404    #[test]
405    fn test_video_encoder_settings_apply_error() {
406        let settings = VideoEncoderSettings::builder()
407            .width(0)
408            .height(0)
409            .pixel_format(AVPixelFormat::Yuv420p)
410            .frame_rate(0.into())
411            .build();
412        // Safety: We are zeroing the memory for the encoder context.
413        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
414        let result = settings.apply(&mut encoder);
415
416        assert!(result.is_err());
417        assert_eq!(
418            result.unwrap_err(),
419            FfmpegError::Arguments("width, height, frame_rate and pixel_format must be set")
420        );
421    }
422
423    #[test]
424    fn test_audio_encoder_apply() {
425        let sample_rate = 44100;
426        let channel_count = 2;
427        let sample_fmt = AVSampleFormat::S16;
428        let thread_count = 4;
429        let thread_type = 1;
430        let bitrate = 128_000;
431        let rc_min_rate = 64_000;
432        let rc_max_rate = 256_000;
433        let rc_buffer_size = 1024;
434        let flags = 0x01;
435        let flags2 = 0x02;
436
437        let mut codec_specific_options = Dictionary::new();
438        codec_specific_options
439            .set(c"profile", c"high")
440            .expect("Failed to set profile");
441
442        let settings = AudioEncoderSettings::builder()
443            .sample_rate(sample_rate)
444            .ch_layout(AudioChannelLayout::new(channel_count).expect("channel_count is a valid value"))
445            .sample_fmt(sample_fmt)
446            .thread_count(thread_count)
447            .thread_type(thread_type)
448            .bitrate(bitrate)
449            .rc_min_rate(rc_min_rate)
450            .rc_max_rate(rc_max_rate)
451            .rc_buffer_size(rc_buffer_size)
452            .codec_specific_options(codec_specific_options)
453            .flags(flags)
454            .flags2(flags2)
455            .build();
456
457        assert_eq!(settings.sample_rate, sample_rate);
458        assert_eq!(settings.ch_layout.channel_count(), 2);
459        assert_eq!(settings.sample_fmt, sample_fmt);
460        assert_eq!(settings.thread_count, Some(thread_count));
461        assert_eq!(settings.thread_type, Some(thread_type));
462        assert_eq!(settings.bitrate, Some(bitrate));
463        assert_eq!(settings.rc_min_rate, Some(rc_min_rate));
464        assert_eq!(settings.rc_max_rate, Some(rc_max_rate));
465        assert_eq!(settings.rc_buffer_size, Some(rc_buffer_size));
466        assert!(settings.codec_specific_options.is_some());
467
468        let actual_codec_specific_options = settings.codec_specific_options.unwrap();
469        assert_eq!(actual_codec_specific_options.get(c"profile"), Some(c"high"));
470
471        assert_eq!(settings.flags, Some(flags));
472        assert_eq!(settings.flags2, Some(flags2));
473    }
474
475    #[test]
476    fn test_ch_layout_valid_layout() {
477        // Safety: This is safe to call and the channel layout is allocated on the stack.
478        let channel_layout = unsafe {
479            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
480                order: AVChannelOrder::Native.into(),
481                nb_channels: 2,
482                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 0b11 },
483                opaque: std::ptr::null_mut(),
484            })
485        };
486
487        channel_layout.validate().expect("channel_layout is a valid value");
488    }
489
490    #[test]
491    fn test_ch_layout_invalid_layout() {
492        // Safety: This is safe to call and the channel layout is allocated on the stack.
493        let channel_layout = unsafe {
494            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
495                order: AVChannelOrder::Unspecified.into(),
496                nb_channels: 0,
497                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 0 },
498                opaque: std::ptr::null_mut(),
499            })
500        };
501        let result: Result<(), FfmpegError> = channel_layout.validate();
502        assert_eq!(result.unwrap_err(), FfmpegError::Arguments("invalid channel layout"));
503    }
504
505    #[test]
506    fn test_audio_encoder_settings_apply_error() {
507        let settings = AudioEncoderSettings::builder()
508            .sample_rate(0)
509            .sample_fmt(AVSampleFormat::None)
510            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
511            .build();
512
513        // Safety: We are zeroing the memory for the encoder context.
514        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
515        let result = settings.apply(&mut encoder);
516
517        assert!(result.is_err());
518        assert_eq!(
519            result.unwrap_err(),
520            FfmpegError::Arguments("sample_rate, channel_layout and sample_fmt must be set")
521        );
522    }
523
524    #[test]
525    fn test_encoder_settings_apply_video() {
526        let sample_aspect_ratio = AVRational { num: 1, den: 1 };
527        let video_settings = VideoEncoderSettings::builder()
528            .width(1920)
529            .height(1080)
530            .frame_rate(30.into())
531            .pixel_format(AVPixelFormat::Yuv420p)
532            .sample_aspect_ratio(sample_aspect_ratio.into())
533            .gop_size(12)
534            .build();
535
536        // Safety: We are zeroing the memory for the encoder context.
537        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
538        let encoder_settings = EncoderSettings::Video(video_settings);
539        let result = encoder_settings.apply(&mut encoder);
540
541        assert!(result.is_ok(), "Failed to apply video settings: {:?}", result.err());
542        assert_eq!(encoder.width, 1920);
543        assert_eq!(encoder.height, 1080);
544        assert_eq!(AVPixelFormat(encoder.pix_fmt), AVPixelFormat::Yuv420p);
545        assert_eq!(Rational::from(encoder.sample_aspect_ratio), sample_aspect_ratio.into());
546    }
547
548    #[test]
549    fn test_encoder_settings_apply_audio() {
550        let audio_settings = AudioEncoderSettings::builder()
551            .sample_rate(44100)
552            .sample_fmt(AVSampleFormat::Fltp)
553            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
554            .thread_count(4)
555            .build();
556
557        // Safety: We are zeroing the memory for the encoder context.
558        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
559        let encoder_settings = EncoderSettings::Audio(audio_settings);
560        let result = encoder_settings.apply(&mut encoder);
561
562        assert!(result.is_ok(), "Failed to apply audio settings: {:?}", result.err());
563        assert_eq!(encoder.sample_rate, 44100);
564        assert_eq!(AVSampleFormat(encoder.sample_fmt), AVSampleFormat::Fltp);
565        assert_eq!(encoder.thread_count, 4);
566    }
567
568    #[test]
569    fn test_encoder_settings_codec_specific_options() {
570        let mut video_codec_options = Dictionary::new();
571        video_codec_options.set(c"preset", c"fast").expect("Failed to set preset");
572
573        let video_settings = VideoEncoderSettings::builder()
574            .width(8)
575            .height(8)
576            .frame_rate(30.into())
577            .pixel_format(AVPixelFormat::Yuv420p)
578            .codec_specific_options(video_codec_options.clone())
579            .build();
580        let mut encoder_settings = EncoderSettings::Video(video_settings);
581        let options = encoder_settings.codec_specific_options();
582
583        assert!(options.is_some());
584        assert_eq!(options.unwrap().get(c"preset"), Some(c"fast"));
585
586        let mut audio_codec_options = Dictionary::new();
587        audio_codec_options.set(c"bitrate", c"128k").expect("Failed to set bitrate");
588        let audio_settings = AudioEncoderSettings::builder()
589            .sample_rate(44100)
590            .sample_fmt(AVSampleFormat::Fltp)
591            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
592            .thread_count(4)
593            .codec_specific_options(audio_codec_options)
594            .build();
595        let mut encoder_settings = EncoderSettings::Audio(audio_settings);
596        let options = encoder_settings.codec_specific_options();
597
598        assert!(options.is_some());
599        assert_eq!(options.unwrap().get(c"bitrate"), Some(c"128k"));
600    }
601
602    #[test]
603    fn test_from_video_encoder_settings() {
604        let sample_aspect_ratio = AVRational { num: 1, den: 1 };
605        let video_settings = VideoEncoderSettings::builder()
606            .width(1920)
607            .height(1080)
608            .frame_rate(30.into())
609            .pixel_format(AVPixelFormat::Yuv420p)
610            .sample_aspect_ratio(sample_aspect_ratio.into())
611            .gop_size(12)
612            .build();
613        let encoder_settings: EncoderSettings = video_settings.into();
614
615        if let EncoderSettings::Video(actual_video_settings) = encoder_settings {
616            assert_eq!(actual_video_settings.width, 1920);
617            assert_eq!(actual_video_settings.height, 1080);
618            assert_eq!(actual_video_settings.frame_rate, 30.into());
619            assert_eq!(actual_video_settings.pixel_format, AVPixelFormat::Yuv420p);
620            assert_eq!(actual_video_settings.sample_aspect_ratio, Some(sample_aspect_ratio.into()));
621            assert_eq!(actual_video_settings.gop_size, Some(12));
622        } else {
623            panic!("Expected EncoderSettings::Video variant");
624        }
625    }
626
627    #[test]
628    fn test_from_audio_encoder_settings() {
629        let audio_settings = AudioEncoderSettings::builder()
630            .sample_rate(44100)
631            .sample_fmt(AVSampleFormat::Fltp)
632            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
633            .thread_count(4)
634            .build();
635        let encoder_settings: EncoderSettings = audio_settings.into();
636
637        if let EncoderSettings::Audio(actual_audio_settings) = encoder_settings {
638            assert_eq!(actual_audio_settings.sample_rate, 44100);
639            assert_eq!(actual_audio_settings.sample_fmt, AVSampleFormat::Fltp);
640            assert_eq!(actual_audio_settings.thread_count, Some(4));
641        } else {
642            panic!("Expected EncoderSettings::Audio variant");
643        }
644    }
645
646    #[test]
647    fn test_encoder_new_with_null_codec() {
648        let codec = EncoderCodec::empty();
649        let data = std::io::Cursor::new(Vec::new());
650        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
651        let mut output = Output::new(data, options).expect("Failed to create Output");
652        let incoming_time_base = AVRational { num: 1, den: 1000 };
653        let outgoing_time_base = AVRational { num: 1, den: 1000 };
654        let settings = VideoEncoderSettings::builder()
655            .width(0)
656            .height(0)
657            .pixel_format(AVPixelFormat::Yuv420p)
658            .frame_rate(0.into())
659            .build();
660        let result = Encoder::new(codec, &mut output, incoming_time_base, outgoing_time_base, settings);
661
662        assert!(matches!(result, Err(FfmpegError::NoEncoder)));
663    }
664
665    #[test]
666    fn test_encoder_new_success() {
667        let codec = EncoderCodec::new(AVCodecID::Mpeg4);
668        assert!(codec.is_some(), "Failed to find MPEG-4 encoder");
669        let data = std::io::Cursor::new(Vec::new());
670        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
671        let mut output = Output::new(data, options).expect("Failed to create Output");
672        let incoming_time_base = AVRational { num: 1, den: 1000 };
673        let outgoing_time_base = AVRational { num: 1, den: 1000 };
674        let settings = VideoEncoderSettings::builder()
675            .width(1920)
676            .height(1080)
677            .frame_rate(30.into())
678            .pixel_format(AVPixelFormat::Yuv420p)
679            .build();
680        let result = Encoder::new(codec.unwrap(), &mut output, incoming_time_base, outgoing_time_base, settings);
681
682        assert!(result.is_ok(), "Encoder creation failed: {:?}", result.err());
683
684        let encoder = result.unwrap();
685        assert_eq!(encoder.incoming_time_base, Rational::static_new::<1, 1000>());
686        assert_eq!(encoder.outgoing_time_base, Rational::static_new::<1, 1000>());
687        assert_eq!(encoder.stream_index, 0);
688    }
689
690    #[test]
691    fn test_send_eof() {
692        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder");
693        let data = std::io::Cursor::new(Vec::new());
694        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
695        let mut output = Output::new(data, options).expect("Failed to create Output");
696        let video_settings = VideoEncoderSettings::builder()
697            .width(640)
698            .height(480)
699            .frame_rate(30.into())
700            .pixel_format(AVPixelFormat::Yuv420p)
701            .build();
702        let mut encoder = Encoder::new(
703            codec,
704            &mut output,
705            AVRational { num: 1, den: 1000 },
706            AVRational { num: 1, den: 1000 },
707            video_settings,
708        )
709        .expect("Failed to create encoder");
710
711        let result = encoder.send_eof();
712        assert!(result.is_ok(), "send_eof returned an error: {:?}", result.err());
713        assert!(encoder.send_eof().is_err(), "send_eof should return an error");
714    }
715
716    #[test]
717    fn test_encoder_getters() {
718        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder");
719        let data = std::io::Cursor::new(Vec::new());
720        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
721        let mut output = Output::new(data, options).expect("Failed to create Output");
722        let incoming_time_base = AVRational { num: 1, den: 1000 };
723        let outgoing_time_base = AVRational { num: 1, den: 1000 };
724        let video_settings = VideoEncoderSettings::builder()
725            .width(640)
726            .height(480)
727            .frame_rate(30.into())
728            .pixel_format(AVPixelFormat::Yuv420p)
729            .build();
730        let encoder = Encoder::new(codec, &mut output, incoming_time_base, outgoing_time_base, video_settings)
731            .expect("Failed to create encoder");
732
733        let stream_index = encoder.stream_index();
734        assert_eq!(stream_index, 0, "Unexpected stream index: expected 0, got {stream_index}");
735
736        let actual_incoming_time_base = encoder.incoming_time_base();
737        assert_eq!(
738            actual_incoming_time_base,
739            incoming_time_base.into(),
740            "Unexpected incoming_time_base: expected {incoming_time_base:?}, got {actual_incoming_time_base:?}"
741        );
742
743        let actual_outgoing_time_base = encoder.outgoing_time_base();
744        assert_eq!(
745            actual_outgoing_time_base,
746            outgoing_time_base.into(),
747            "Unexpected outgoing_time_base: expected {outgoing_time_base:?}, got {actual_outgoing_time_base:?}"
748        );
749    }
750
751    #[test]
752    fn test_encoder_encode_video() {
753        let mut input = Input::open(file_path("avc_aac.mp4")).expect("Failed to open input file");
754        let streams = input.streams();
755        let video_stream = streams.best(AVMediaType::Video).expect("No video stream found");
756        let mut decoder = Decoder::new(&video_stream)
757            .expect("Failed to create decoder")
758            .video()
759            .expect("Failed to create video decoder");
760        let mut output = Output::seekable(
761            std::io::Cursor::new(Vec::new()),
762            OutputOptions::builder().format_name("mp4").unwrap().build(),
763        )
764        .expect("Failed to create Output");
765        let mut encoder = Encoder::new(
766            EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder"),
767            &mut output,
768            AVRational { num: 1, den: 1000 },
769            video_stream.time_base(),
770            VideoEncoderSettings::builder()
771                .width(decoder.width())
772                .height(decoder.height())
773                .frame_rate(decoder.frame_rate())
774                .pixel_format(decoder.pixel_format())
775                .build(),
776        )
777        .expect("Failed to create encoder");
778
779        output.write_header().expect("Failed to write header");
780
781        let input_stream_index = video_stream.index();
782
783        while let Some(packet) = input.receive_packet().expect("Failed to receive packet") {
784            if packet.stream_index() == input_stream_index {
785                decoder.send_packet(&packet).expect("Failed to send packet");
786                while let Some(frame) = decoder.receive_frame().expect("Failed to receive frame") {
787                    encoder.send_frame(&frame).expect("Failed to send frame");
788                    while let Some(packet) = encoder.receive_packet().expect("Failed to receive packet") {
789                        output.write_packet(&packet).expect("Failed to write packet");
790                    }
791                }
792            }
793        }
794
795        encoder.send_eof().expect("Failed to send EOF");
796        while let Some(packet) = encoder.receive_packet().expect("Failed to receive packet") {
797            output.write_packet(&packet).expect("Failed to write packet");
798        }
799
800        output.write_trailer().expect("Failed to write trailer");
801
802        let data = output.into_inner().into_inner();
803        let mut reader = scuffle_bytes_util::zero_copy::Slice::from(&data[..]);
804
805        let mut boxes = Vec::new();
806        loop {
807            let Some(mut any_box) = isobmff::UnknownBox::deserialize(&mut reader)
808                .eof_to_none()
809                .expect("failed to demux box")
810            else {
811                break;
812            };
813
814            if any_box.header.box_type.is_four_cc(b"mdat") || any_box.header.box_type.is_four_cc(b"moov") {
815                any_box.data = BytesCow::new();
816            }
817
818            boxes.push(any_box);
819        }
820        insta::assert_debug_snapshot!("test_encoder_encode_video", &boxes);
821    }
822
823    /// make sure [#248](https://github.com/ScuffleCloud/scuffle/pull/248) doesn't happen again
824    #[test]
825    fn test_pr_248() {
826        let mut output = Output::seekable(
827            std::io::Cursor::new(Vec::new()),
828            OutputOptions::builder().format_name("mp4").unwrap().build(),
829        )
830        .expect("Failed to create Output");
831
832        let mut settings = Dictionary::new();
833        settings.set(c"key", c"value").expect("Failed to set Dictionary entry");
834
835        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Missing MPEG-4 codec");
836
837        Encoder::new(
838            codec,
839            &mut output,
840            AVRational { num: 1, den: 100 },
841            AVRational { num: 1, den: 100 },
842            VideoEncoderSettings::builder()
843                .width(16)
844                .height(16)
845                .frame_rate(30.into())
846                .pixel_format(AVPixelFormat::Yuv420p)
847                .codec_specific_options(settings)
848                .build(),
849        )
850        .expect("Failed to create new Encoder");
851    }
852}