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: 0,
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 std::io::Write;
301
302    use bytes::{Buf, Bytes};
303    use rusty_ffmpeg::ffi::AVRational;
304    use sha2::Digest;
305
306    use crate::codec::EncoderCodec;
307    use crate::decoder::Decoder;
308    use crate::dict::Dictionary;
309    use crate::encoder::{AudioChannelLayout, AudioEncoderSettings, Encoder, EncoderSettings, VideoEncoderSettings};
310    use crate::error::FfmpegError;
311    use crate::ffi::AVCodecContext;
312    use crate::io::{Input, Output, OutputOptions};
313    use crate::rational::Rational;
314    use crate::{AVChannelOrder, AVCodecID, AVMediaType, AVPixelFormat, AVSampleFormat};
315
316    #[test]
317    fn test_video_encoder_apply() {
318        let width = 1920;
319        let height = 1080;
320        let frame_rate = 30;
321        let pixel_format = AVPixelFormat::Yuv420p;
322        let sample_aspect_ratio = 1;
323        let gop_size = 12;
324        let qmax = 31;
325        let qmin = 1;
326        let thread_count = 4;
327        let thread_type = 2;
328        let bitrate = 8_000;
329        let rc_min_rate = 500_000;
330        let rc_max_rate = 2_000_000;
331        let rc_buffer_size = 1024;
332        let max_b_frames = 3;
333        let mut codec_specific_options = Dictionary::new();
334        codec_specific_options.set("preset", "ultrafast").unwrap();
335        codec_specific_options.set("crf", "23").unwrap();
336        let flags = 0x01;
337        let flags2 = 0x02;
338
339        let settings = VideoEncoderSettings::builder()
340            .width(width)
341            .height(height)
342            .frame_rate(frame_rate.into())
343            .pixel_format(pixel_format)
344            .sample_aspect_ratio(sample_aspect_ratio.into())
345            .gop_size(gop_size)
346            .qmax(qmax)
347            .qmin(qmin)
348            .thread_count(thread_count)
349            .thread_type(thread_type)
350            .bitrate(bitrate)
351            .rc_min_rate(rc_min_rate)
352            .rc_max_rate(rc_max_rate)
353            .rc_buffer_size(rc_buffer_size)
354            .max_b_frames(max_b_frames)
355            .codec_specific_options(codec_specific_options)
356            .flags(flags)
357            .flags2(flags2)
358            .build();
359
360        assert_eq!(settings.width, width);
361        assert_eq!(settings.height, height);
362        assert_eq!(settings.frame_rate, frame_rate.into());
363        assert_eq!(settings.pixel_format, pixel_format);
364        assert_eq!(settings.sample_aspect_ratio, Some(sample_aspect_ratio.into()));
365        assert_eq!(settings.gop_size, Some(gop_size));
366        assert_eq!(settings.qmax, Some(qmax));
367        assert_eq!(settings.qmin, Some(qmin));
368        assert_eq!(settings.thread_count, Some(thread_count));
369        assert_eq!(settings.thread_type, Some(thread_type));
370        assert_eq!(settings.bitrate, Some(bitrate));
371        assert_eq!(settings.rc_min_rate, Some(rc_min_rate));
372        assert_eq!(settings.rc_max_rate, Some(rc_max_rate));
373        assert_eq!(settings.rc_buffer_size, Some(rc_buffer_size));
374        assert_eq!(settings.max_b_frames, Some(max_b_frames));
375        assert!(settings.codec_specific_options.is_some());
376        let actual_codec_specific_options = settings.codec_specific_options.as_ref().unwrap();
377        assert_eq!(actual_codec_specific_options.get(c"preset"), Some(c"ultrafast"));
378        assert_eq!(actual_codec_specific_options.get(c"crf"), Some(c"23"));
379        assert_eq!(settings.flags, Some(flags));
380        assert_eq!(settings.flags2, Some(flags2));
381
382        // Safety: We are zeroing the memory for the encoder context.
383        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
384        let result = settings.apply(&mut encoder);
385        assert!(result.is_ok(), "Failed to apply settings: {:?}", result.err());
386
387        assert_eq!(encoder.width, width);
388        assert_eq!(encoder.height, height);
389        assert_eq!(AVPixelFormat(encoder.pix_fmt), pixel_format);
390        assert_eq!(Rational::from(encoder.sample_aspect_ratio), sample_aspect_ratio.into());
391        assert_eq!(Rational::from(encoder.framerate), frame_rate.into());
392        assert_eq!(encoder.thread_count, thread_count);
393        assert_eq!(encoder.thread_type, thread_type);
394        assert_eq!(encoder.gop_size, gop_size);
395        assert_eq!(encoder.qmax, qmax);
396        assert_eq!(encoder.qmin, qmin);
397        assert_eq!(encoder.bit_rate, bitrate);
398        assert_eq!(encoder.rc_min_rate, rc_min_rate);
399        assert_eq!(encoder.rc_max_rate, rc_max_rate);
400        assert_eq!(encoder.rc_buffer_size, rc_buffer_size);
401        assert_eq!(encoder.max_b_frames, max_b_frames);
402        assert_eq!(encoder.flags, flags);
403        assert_eq!(encoder.flags2, flags2);
404    }
405
406    #[test]
407    fn test_video_encoder_settings_apply_error() {
408        let settings = VideoEncoderSettings::builder()
409            .width(0)
410            .height(0)
411            .pixel_format(AVPixelFormat::Yuv420p)
412            .frame_rate(0.into())
413            .build();
414        // Safety: We are zeroing the memory for the encoder context.
415        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
416        let result = settings.apply(&mut encoder);
417
418        assert!(result.is_err());
419        assert_eq!(
420            result.unwrap_err(),
421            FfmpegError::Arguments("width, height, frame_rate and pixel_format must be set")
422        );
423    }
424
425    #[test]
426    fn test_audio_encoder_apply() {
427        let sample_rate = 44100;
428        let channel_count = 2;
429        let sample_fmt = AVSampleFormat::S16;
430        let thread_count = 4;
431        let thread_type = 1;
432        let bitrate = 128_000;
433        let rc_min_rate = 64_000;
434        let rc_max_rate = 256_000;
435        let rc_buffer_size = 1024;
436        let flags = 0x01;
437        let flags2 = 0x02;
438
439        let mut codec_specific_options = Dictionary::new();
440        codec_specific_options
441            .set(c"profile", c"high")
442            .expect("Failed to set profile");
443
444        let settings = AudioEncoderSettings::builder()
445            .sample_rate(sample_rate)
446            .ch_layout(AudioChannelLayout::new(channel_count).expect("channel_count is a valid value"))
447            .sample_fmt(sample_fmt)
448            .thread_count(thread_count)
449            .thread_type(thread_type)
450            .bitrate(bitrate)
451            .rc_min_rate(rc_min_rate)
452            .rc_max_rate(rc_max_rate)
453            .rc_buffer_size(rc_buffer_size)
454            .codec_specific_options(codec_specific_options)
455            .flags(flags)
456            .flags2(flags2)
457            .build();
458
459        assert_eq!(settings.sample_rate, sample_rate);
460        assert_eq!(settings.ch_layout.channel_count(), 2);
461        assert_eq!(settings.sample_fmt, sample_fmt);
462        assert_eq!(settings.thread_count, Some(thread_count));
463        assert_eq!(settings.thread_type, Some(thread_type));
464        assert_eq!(settings.bitrate, Some(bitrate));
465        assert_eq!(settings.rc_min_rate, Some(rc_min_rate));
466        assert_eq!(settings.rc_max_rate, Some(rc_max_rate));
467        assert_eq!(settings.rc_buffer_size, Some(rc_buffer_size));
468        assert!(settings.codec_specific_options.is_some());
469
470        let actual_codec_specific_options = settings.codec_specific_options.unwrap();
471        assert_eq!(actual_codec_specific_options.get(c"profile"), Some(c"high"));
472
473        assert_eq!(settings.flags, Some(flags));
474        assert_eq!(settings.flags2, Some(flags2));
475    }
476
477    #[test]
478    fn test_ch_layout_valid_layout() {
479        // Safety: This is safe to call and the channel layout is allocated on the stack.
480        let channel_layout = unsafe {
481            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
482                order: AVChannelOrder::Native.into(),
483                nb_channels: 2,
484                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 0b11 },
485                opaque: std::ptr::null_mut(),
486            })
487        };
488
489        channel_layout.validate().expect("channel_layout is a valid value");
490    }
491
492    #[test]
493    fn test_ch_layout_invalid_layout() {
494        // Safety: This is safe to call and the channel layout is allocated on the stack.
495        let channel_layout = unsafe {
496            AudioChannelLayout::wrap(crate::ffi::AVChannelLayout {
497                order: AVChannelOrder::Unspecified.into(),
498                nb_channels: 0,
499                u: crate::ffi::AVChannelLayout__bindgen_ty_1 { mask: 0 },
500                opaque: std::ptr::null_mut(),
501            })
502        };
503        let result: Result<(), FfmpegError> = channel_layout.validate();
504        assert_eq!(result.unwrap_err(), FfmpegError::Arguments("invalid channel layout"));
505    }
506
507    #[test]
508    fn test_audio_encoder_settings_apply_error() {
509        let settings = AudioEncoderSettings::builder()
510            .sample_rate(0)
511            .sample_fmt(AVSampleFormat::None)
512            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
513            .build();
514
515        // Safety: We are zeroing the memory for the encoder context.
516        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
517        let result = settings.apply(&mut encoder);
518
519        assert!(result.is_err());
520        assert_eq!(
521            result.unwrap_err(),
522            FfmpegError::Arguments("sample_rate, channel_layout and sample_fmt must be set")
523        );
524    }
525
526    #[test]
527    fn test_encoder_settings_apply_video() {
528        let sample_aspect_ratio = AVRational { num: 1, den: 1 };
529        let video_settings = VideoEncoderSettings::builder()
530            .width(1920)
531            .height(1080)
532            .frame_rate(30.into())
533            .pixel_format(AVPixelFormat::Yuv420p)
534            .sample_aspect_ratio(sample_aspect_ratio.into())
535            .gop_size(12)
536            .build();
537
538        // Safety: We are zeroing the memory for the encoder context.
539        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
540        let encoder_settings = EncoderSettings::Video(video_settings);
541        let result = encoder_settings.apply(&mut encoder);
542
543        assert!(result.is_ok(), "Failed to apply video settings: {:?}", result.err());
544        assert_eq!(encoder.width, 1920);
545        assert_eq!(encoder.height, 1080);
546        assert_eq!(AVPixelFormat(encoder.pix_fmt), AVPixelFormat::Yuv420p);
547        assert_eq!(Rational::from(encoder.sample_aspect_ratio), sample_aspect_ratio.into());
548    }
549
550    #[test]
551    fn test_encoder_settings_apply_audio() {
552        let audio_settings = AudioEncoderSettings::builder()
553            .sample_rate(44100)
554            .sample_fmt(AVSampleFormat::Fltp)
555            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
556            .thread_count(4)
557            .build();
558
559        // Safety: We are zeroing the memory for the encoder context.
560        let mut encoder = unsafe { std::mem::zeroed::<AVCodecContext>() };
561        let encoder_settings = EncoderSettings::Audio(audio_settings);
562        let result = encoder_settings.apply(&mut encoder);
563
564        assert!(result.is_ok(), "Failed to apply audio settings: {:?}", result.err());
565        assert_eq!(encoder.sample_rate, 44100);
566        assert_eq!(AVSampleFormat(encoder.sample_fmt), AVSampleFormat::Fltp);
567        assert_eq!(encoder.thread_count, 4);
568    }
569
570    #[test]
571    fn test_encoder_settings_codec_specific_options() {
572        let mut video_codec_options = Dictionary::new();
573        video_codec_options.set(c"preset", c"fast").expect("Failed to set preset");
574
575        let video_settings = VideoEncoderSettings::builder()
576            .width(8)
577            .height(8)
578            .frame_rate(30.into())
579            .pixel_format(AVPixelFormat::Yuv420p)
580            .codec_specific_options(video_codec_options.clone())
581            .build();
582        let mut encoder_settings = EncoderSettings::Video(video_settings);
583        let options = encoder_settings.codec_specific_options();
584
585        assert!(options.is_some());
586        assert_eq!(options.unwrap().get(c"preset"), Some(c"fast"));
587
588        let mut audio_codec_options = Dictionary::new();
589        audio_codec_options.set(c"bitrate", c"128k").expect("Failed to set bitrate");
590        let audio_settings = AudioEncoderSettings::builder()
591            .sample_rate(44100)
592            .sample_fmt(AVSampleFormat::Fltp)
593            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
594            .thread_count(4)
595            .codec_specific_options(audio_codec_options)
596            .build();
597        let mut encoder_settings = EncoderSettings::Audio(audio_settings);
598        let options = encoder_settings.codec_specific_options();
599
600        assert!(options.is_some());
601        assert_eq!(options.unwrap().get(c"bitrate"), Some(c"128k"));
602    }
603
604    #[test]
605    fn test_from_video_encoder_settings() {
606        let sample_aspect_ratio = AVRational { num: 1, den: 1 };
607        let video_settings = VideoEncoderSettings::builder()
608            .width(1920)
609            .height(1080)
610            .frame_rate(30.into())
611            .pixel_format(AVPixelFormat::Yuv420p)
612            .sample_aspect_ratio(sample_aspect_ratio.into())
613            .gop_size(12)
614            .build();
615        let encoder_settings: EncoderSettings = video_settings.into();
616
617        if let EncoderSettings::Video(actual_video_settings) = encoder_settings {
618            assert_eq!(actual_video_settings.width, 1920);
619            assert_eq!(actual_video_settings.height, 1080);
620            assert_eq!(actual_video_settings.frame_rate, 30.into());
621            assert_eq!(actual_video_settings.pixel_format, AVPixelFormat::Yuv420p);
622            assert_eq!(actual_video_settings.sample_aspect_ratio, Some(sample_aspect_ratio.into()));
623            assert_eq!(actual_video_settings.gop_size, Some(12));
624        } else {
625            panic!("Expected EncoderSettings::Video variant");
626        }
627    }
628
629    #[test]
630    fn test_from_audio_encoder_settings() {
631        let audio_settings = AudioEncoderSettings::builder()
632            .sample_rate(44100)
633            .sample_fmt(AVSampleFormat::Fltp)
634            .ch_layout(AudioChannelLayout::new(2).expect("channel_count is a valid value"))
635            .thread_count(4)
636            .build();
637        let encoder_settings: EncoderSettings = audio_settings.into();
638
639        if let EncoderSettings::Audio(actual_audio_settings) = encoder_settings {
640            assert_eq!(actual_audio_settings.sample_rate, 44100);
641            assert_eq!(actual_audio_settings.sample_fmt, AVSampleFormat::Fltp);
642            assert_eq!(actual_audio_settings.thread_count, Some(4));
643        } else {
644            panic!("Expected EncoderSettings::Audio variant");
645        }
646    }
647
648    #[test]
649    fn test_encoder_new_with_null_codec() {
650        let codec = EncoderCodec::empty();
651        let data = std::io::Cursor::new(Vec::new());
652        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
653        let mut output = Output::new(data, options).expect("Failed to create Output");
654        let incoming_time_base = AVRational { num: 1, den: 1000 };
655        let outgoing_time_base = AVRational { num: 1, den: 1000 };
656        let settings = VideoEncoderSettings::builder()
657            .width(0)
658            .height(0)
659            .pixel_format(AVPixelFormat::Yuv420p)
660            .frame_rate(0.into())
661            .build();
662        let result = Encoder::new(codec, &mut output, incoming_time_base, outgoing_time_base, settings);
663
664        assert!(matches!(result, Err(FfmpegError::NoEncoder)));
665    }
666
667    #[test]
668    fn test_encoder_new_success() {
669        let codec = EncoderCodec::new(AVCodecID::Mpeg4);
670        assert!(codec.is_some(), "Failed to find MPEG-4 encoder");
671        let data = std::io::Cursor::new(Vec::new());
672        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
673        let mut output = Output::new(data, options).expect("Failed to create Output");
674        let incoming_time_base = AVRational { num: 1, den: 1000 };
675        let outgoing_time_base = AVRational { num: 1, den: 1000 };
676        let settings = VideoEncoderSettings::builder()
677            .width(1920)
678            .height(1080)
679            .frame_rate(30.into())
680            .pixel_format(AVPixelFormat::Yuv420p)
681            .build();
682        let result = Encoder::new(codec.unwrap(), &mut output, incoming_time_base, outgoing_time_base, settings);
683
684        assert!(result.is_ok(), "Encoder creation failed: {:?}", result.err());
685
686        let encoder = result.unwrap();
687        assert_eq!(encoder.incoming_time_base, Rational::static_new::<1, 1000>());
688        assert_eq!(encoder.outgoing_time_base, Rational::static_new::<1, 1000>());
689        assert_eq!(encoder.stream_index, 0);
690    }
691
692    #[test]
693    fn test_send_eof() {
694        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder");
695        let data = std::io::Cursor::new(Vec::new());
696        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
697        let mut output = Output::new(data, options).expect("Failed to create Output");
698        let video_settings = VideoEncoderSettings::builder()
699            .width(640)
700            .height(480)
701            .frame_rate(30.into())
702            .pixel_format(AVPixelFormat::Yuv420p)
703            .build();
704        let mut encoder = Encoder::new(
705            codec,
706            &mut output,
707            AVRational { num: 1, den: 1000 },
708            AVRational { num: 1, den: 1000 },
709            video_settings,
710        )
711        .expect("Failed to create encoder");
712
713        let result = encoder.send_eof();
714        assert!(result.is_ok(), "send_eof returned an error: {:?}", result.err());
715        assert!(encoder.send_eof().is_err(), "send_eof should return an error");
716    }
717
718    #[test]
719    fn test_encoder_getters() {
720        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder");
721        let data = std::io::Cursor::new(Vec::new());
722        let options = OutputOptions::builder().format_name("mp4").unwrap().build();
723        let mut output = Output::new(data, options).expect("Failed to create Output");
724        let incoming_time_base = AVRational { num: 1, den: 1000 };
725        let outgoing_time_base = AVRational { num: 1, den: 1000 };
726        let video_settings = VideoEncoderSettings::builder()
727            .width(640)
728            .height(480)
729            .frame_rate(30.into())
730            .pixel_format(AVPixelFormat::Yuv420p)
731            .build();
732        let encoder = Encoder::new(codec, &mut output, incoming_time_base, outgoing_time_base, video_settings)
733            .expect("Failed to create encoder");
734
735        let stream_index = encoder.stream_index();
736        assert_eq!(stream_index, 0, "Unexpected stream index: expected 0, got {stream_index}");
737
738        let actual_incoming_time_base = encoder.incoming_time_base();
739        assert_eq!(
740            actual_incoming_time_base,
741            incoming_time_base.into(),
742            "Unexpected incoming_time_base: expected {incoming_time_base:?}, got {actual_incoming_time_base:?}"
743        );
744
745        let actual_outgoing_time_base = encoder.outgoing_time_base();
746        assert_eq!(
747            actual_outgoing_time_base,
748            outgoing_time_base.into(),
749            "Unexpected outgoing_time_base: expected {outgoing_time_base:?}, got {actual_outgoing_time_base:?}"
750        );
751    }
752
753    #[test]
754    fn test_encoder_encode_video() {
755        let mut input = Input::open("../../assets/avc_aac.mp4").expect("Failed to open input file");
756        let streams = input.streams();
757        let video_stream = streams.best(AVMediaType::Video).expect("No video stream found");
758        let mut decoder = Decoder::new(&video_stream)
759            .expect("Failed to create decoder")
760            .video()
761            .expect("Failed to create video decoder");
762        let mut output = Output::seekable(
763            std::io::Cursor::new(Vec::new()),
764            OutputOptions::builder().format_name("mp4").unwrap().build(),
765        )
766        .expect("Failed to create Output");
767        let mut encoder = Encoder::new(
768            EncoderCodec::new(AVCodecID::Mpeg4).expect("Failed to find MPEG-4 encoder"),
769            &mut output,
770            AVRational { num: 1, den: 1000 },
771            video_stream.time_base(),
772            VideoEncoderSettings::builder()
773                .width(decoder.width())
774                .height(decoder.height())
775                .frame_rate(decoder.frame_rate())
776                .pixel_format(decoder.pixel_format())
777                .build(),
778        )
779        .expect("Failed to create encoder");
780
781        output.write_header().expect("Failed to write header");
782
783        let input_stream_index = video_stream.index();
784
785        while let Some(packet) = input.receive_packet().expect("Failed to receive packet") {
786            if packet.stream_index() == input_stream_index {
787                decoder.send_packet(&packet).expect("Failed to send packet");
788                while let Some(frame) = decoder.receive_frame().expect("Failed to receive frame") {
789                    encoder.send_frame(&frame).expect("Failed to send frame");
790                    while let Some(packet) = encoder.receive_packet().expect("Failed to receive packet") {
791                        output.write_packet(&packet).expect("Failed to write packet");
792                    }
793                }
794            }
795        }
796
797        encoder.send_eof().expect("Failed to send EOF");
798        while let Some(packet) = encoder.receive_packet().expect("Failed to receive packet") {
799            output.write_packet(&packet).expect("Failed to write packet");
800        }
801
802        output.write_trailer().expect("Failed to write trailer");
803
804        let mut cursor = std::io::Cursor::new(Bytes::from(output.into_inner().into_inner()));
805        let mut boxes = Vec::new();
806        while cursor.has_remaining() {
807            let mut _box = scuffle_mp4::DynBox::demux(&mut cursor).expect("Failed to demux box");
808            match &mut _box {
809                scuffle_mp4::DynBox::Mdat(mdat) => {
810                    mdat.data.iter_mut().for_each(|buf| {
811                        let mut hash = sha2::Sha256::new();
812                        hash.write_all(buf).unwrap();
813                        *buf = Bytes::new();
814                    });
815                }
816                scuffle_mp4::DynBox::Moov(moov) => {
817                    moov.traks.iter_mut().for_each(|trak| {
818                        // these can change from version to version
819                        trak.mdia.minf.stbl.stsd.entries.clear();
820                        if let Some(stsz) = trak.mdia.minf.stbl.stsz.as_mut() {
821                            stsz.samples.clear();
822                        }
823                        trak.mdia.minf.stbl.stco.entries.clear();
824                    });
825                }
826                _ => {}
827            }
828            boxes.push(_box);
829        }
830        insta::assert_debug_snapshot!("test_encoder_encode_video", &boxes);
831    }
832
833    /// make sure [#248](https://github.com/ScuffleCloud/scuffle/pull/248) doesn't happen again
834    #[test]
835    fn test_pr_248() {
836        let mut output = Output::seekable(
837            std::io::Cursor::new(Vec::new()),
838            OutputOptions::builder().format_name("mp4").unwrap().build(),
839        )
840        .expect("Failed to create Output");
841
842        let mut settings = Dictionary::new();
843        settings.set(c"key", c"value").expect("Failed to set Dictionary entry");
844
845        let codec = EncoderCodec::new(AVCodecID::Mpeg4).expect("Missing MPEG-4 codec");
846
847        Encoder::new(
848            codec,
849            &mut output,
850            AVRational { num: 1, den: 100 },
851            AVRational { num: 1, den: 100 },
852            VideoEncoderSettings::builder()
853                .width(16)
854                .height(16)
855                .frame_rate(30.into())
856                .pixel_format(AVPixelFormat::Yuv420p)
857                .codec_specific_options(settings)
858                .build(),
859        )
860        .expect("Failed to create new Encoder");
861    }
862}