ffav/easy/
writer.rs

1use super::{owned::*, AVResult};
2use crate::ffi::{AVCodecID::*, AVFieldOrder::*, AVMediaType::*, AVPixelFormat::*, *};
3use std::convert::TryInto;
4use std::fmt::Debug;
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7use std::time::{Duration, Instant};
8
9/// Trait for Media Description.
10pub trait MediaDesc {
11    /// Returns the CodecID.
12    fn codec_id(&self) -> AVCodecID {
13        Default::default()
14    }
15
16    /// Cast to AudioDesc reference.
17    fn as_audio_desc(&self) -> Option<&AudioDesc> {
18        None
19    }
20
21    /// Cast to VideoDesc reference.
22    fn as_video_desc(&self) -> Option<&VideoDesc> {
23        None
24    }
25}
26
27impl Debug for &dyn MediaDesc {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "MediaDesc {{ codec_id: {:?} }}", self.codec_id())
30    }
31}
32
33impl Debug for Box<dyn MediaDesc> {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        write!(f, "MediaDesc {{ codec_id: {:?} }}", self.codec_id())
36    }
37}
38
39/// Trait for Writer.
40pub trait Writer {
41    /// Write the header of the format to the stream.
42    fn write_header(&mut self) -> AVResult<()>;
43
44    /// Write frame bytes to the stream.
45    /// # Arguments
46    /// * `bytes` - Stream byte data.
47    /// * `pts` - Timestamp of the frame.
48    /// * `duration` - Duration of the frame.
49    /// * `is_key_frame` - True if is key frame.
50    /// * `stream_index` - Index of the stream.
51    fn write_bytes(
52        &mut self,
53        bytes: &[u8],
54        pts: i64,
55        duration: i64,
56        is_key_frame: bool,
57        stream_index: usize,
58    ) -> AVResult<()>;
59
60    /// Write the trailer of the format to the stream.
61    fn write_trailer(&mut self) -> AVResult<()>;
62
63    /// Close all resouces accessed by the muxer.
64    fn close(&mut self);
65
66    /// Flush all buffered data to stream destionation.
67    fn flush(&mut self);
68
69    /// Returns the size of the stream processed.
70    fn size(&self) -> u64;
71}
72
73impl Debug for &dyn Writer {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "Writer @ 0x{:p}", self)
76    }
77}
78
79impl Debug for Box<dyn Writer> {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        write!(f, "Writer @ 0x{:p}", self)
82    }
83}
84
85/// Audio Description
86#[derive(Copy, Clone, Debug, Default)]
87pub struct AudioDesc {
88    pub codec_id: AVCodecID,
89    pub sample_fmt: AVSampleFormat,
90    pub bit_rate: i64,
91    pub sample_rate: usize,
92    pub channels: usize,
93}
94
95impl MediaDesc for AudioDesc {
96    fn codec_id(&self) -> AVCodecID {
97        self.codec_id
98    }
99}
100
101impl AudioDesc {
102    pub fn new() -> Self {
103        Default::default()
104    }
105}
106
107/// Video Description
108#[derive(Copy, Clone, Debug, Default)]
109pub struct VideoDesc {
110    pub codec_id: AVCodecID,
111    pub width: i32,
112    pub height: i32,
113    pub bit_rate: i64,
114    pub time_base: AVRational,
115    pub gop_size: i32,
116    pub pix_fmt: AVPixelFormat,
117}
118
119impl MediaDesc for VideoDesc {
120    fn codec_id(&self) -> AVCodecID {
121        self.codec_id
122    }
123    fn as_video_desc(&self) -> Option<&VideoDesc> {
124        Some(self)
125    }
126}
127
128impl VideoDesc {
129    pub fn new() -> Self {
130        Default::default()
131    }
132
133    pub fn with_h264(width: i32, height: i32, bit_rate: i64, time_unit: i32) -> Self {
134        Self {
135            codec_id: AV_CODEC_ID_H264,
136            width,
137            height,
138            bit_rate,
139            time_base: AVRational::with_normalize(time_unit),
140            gop_size: 12,
141            pix_fmt: AV_PIX_FMT_YUV420P,
142        }
143    }
144
145    pub fn with_h265(width: i32, height: i32, bit_rate: i64, time_unit: i32) -> Self {
146        Self {
147            codec_id: AV_CODEC_ID_HEVC,
148            width,
149            height,
150            bit_rate,
151            time_base: AVRational::with_normalize(time_unit),
152            gop_size: 12,
153            pix_fmt: AV_PIX_FMT_YUV420P,
154        }
155    }
156}
157
158/// Stream Information
159#[derive(Debug)]
160pub struct Stream {
161    stream: AVStreamOwned,
162    in_time_base: AVRational,
163}
164
165/// Simple Writer for Muxing Audio and Video.
166#[derive(Debug)]
167pub struct SimpleWriter {
168    ctx: AVFormatContextOwned,
169    format_options: String,
170    streams: Vec<Stream>,
171    header_writed: bool,
172    trailer_writed: bool,
173}
174
175impl Drop for SimpleWriter {
176    fn drop(&mut self) {
177        self.close();
178    }
179}
180
181impl Writer for SimpleWriter {
182    /// Write the header of the format to the stream.
183    fn write_header(&mut self) -> AVResult<()> {
184        Ok(())
185    }
186
187    /// Write frame bytes to the stream.
188    /// # Arguments
189    /// * `bytes` - Stream byte data.
190    /// * `pts` - Timestamp of the frame.
191    /// * `duration` - Duration of the frame.
192    /// * `is_key_frame` - True if is key frame.
193    /// * `stream_index` - Index of the stream.
194    fn write_bytes(
195        &mut self,
196        bytes: &[u8],
197        pts: i64,
198        duration: i64,
199        is_key_frame: bool,
200        stream_index: usize,
201    ) -> AVResult<()> {
202        if !self.header_writed {
203            self.ctx.write_header(Some(&self.format_options))?;
204            self.header_writed = true;
205        }
206        unsafe {
207            let stm = self.streams.get(stream_index).unwrap();
208            let in_time_base = stm.in_time_base;
209            let out_time_base = stm.stream.time_base;
210            let mut pkt = AVPacket::default();
211            let pts = av_rescale_q_rnd(
212                pts,
213                in_time_base,
214                out_time_base,
215                AVRounding::new().near_inf().pass_min_max(),
216            );
217            pkt.pts = pts;
218            pkt.dts = pts;
219            pkt.data = bytes.as_ptr() as *mut u8;
220            pkt.size = bytes.len().try_into()?;
221            pkt.stream_index = stream_index.try_into()?;
222            pkt.flags = if is_key_frame { AV_PKT_FLAG_KEY } else { 0 };
223            pkt.duration = av_rescale_q(duration, in_time_base, out_time_base);
224            pkt.pos = -1;
225            self.ctx.write_frame_interleaved(&mut pkt)?;
226            self.ctx.flush();
227            Ok(())
228        }
229    }
230
231    /// Write the trailer to finish the muxing.
232    fn write_trailer(&mut self) -> AVResult<()> {
233        if self.header_writed && !self.trailer_writed {
234            self.ctx.write_trailer()?;
235            self.trailer_writed = true;
236            self.flush();
237        }
238        Ok(())
239    }
240
241    /// Close all resouces accessed by the muxer.
242    fn close(&mut self) {
243        self.write_trailer().unwrap();
244        self.ctx.flush();
245    }
246
247    /// Flush all buffered data to stream destionation.
248    fn flush(&mut self) {
249        self.ctx.flush();
250    }
251
252    /// Returns the size of the stream processed.
253    fn size(&self) -> u64 {
254        self.ctx.size()
255    }
256}
257
258impl SimpleWriter {
259    /// Create a new simple writer.
260    /// # Arguments
261    /// * `path` - Path of the output file.
262    /// * `descs` - Media description of input streams.
263    /// * `format` - The format to muxing,like: mp4, mpegts.
264    /// * `format_options` - The options for muxing format,like: movfragement.
265    pub fn new<P>(
266        path: P,
267        descs: &[&dyn MediaDesc],
268        format: Option<&str>,
269        format_options: Option<&str>,
270    ) -> AVResult<Self>
271    where
272        P: AsRef<Path> + Sized,
273    {
274        let mut ctx = AVFormatContextOwned::with_output(path, format, None)?;
275        let mut streams: Vec<Stream> = vec![];
276        for desc in descs {
277            let codec_id = desc.codec_id();
278            match codec_id {
279                AV_CODEC_ID_H264 | AV_CODEC_ID_HEVC => {
280                    let desc = desc.as_video_desc().unwrap();
281                    let mut st = ctx.new_stream(codec_id)?;
282                    // st.time_base = AVRational::new(1, 90000);
283                    if let Some(par) = st.codecpar_mut() {
284                        par.codec_type = AVMEDIA_TYPE_VIDEO;
285                        par.codec_id = codec_id;
286                        par.bit_rate = desc.bit_rate;
287                        par.width = desc.width;
288                        par.height = desc.height;
289                        par.field_order = AV_FIELD_UNKNOWN;
290                        par.sample_aspect_ratio = AVRational::new(0, 1);
291                        par.profile = FF_PROFILE_UNKNOWN;
292                        par.level = FF_LEVEL_UNKNOWN;
293                    }
294                    streams.push(Stream {
295                        stream: st,
296                        in_time_base: desc.time_base,
297                    });
298                }
299                _ => {}
300            }
301        }
302        Ok(Self {
303            ctx,
304            format_options: format_options.unwrap_or("").to_owned(),
305            streams,
306            header_writed: false,
307            trailer_writed: false,
308        })
309    }
310}
311
312/// The Callback for returns the the fragment file name.
313/// # Arguments
314/// * `index` - Current Fragment Index.
315pub type FormatLocationCallback = dyn Fn(usize) -> String;
316
317/// The Callback for before and after split fragment.
318/// # Arguments
319/// * `index` - Current Fragment Index.
320pub type SplitNotifier = dyn Fn(usize);
321
322/// Options for SplitWriter.
323#[derive(Default)]
324pub struct SplitOptions {
325    output_path: Option<PathBuf>,
326    format_location: Option<Box<FormatLocationCallback>>,
327    before_split: Option<Box<SplitNotifier>>,
328    after_split: Option<Box<SplitNotifier>>,
329    max_files: Option<usize>,
330    max_size_bytes: Option<u64>,
331    max_size_time: Option<u64>,
332    max_overhead: Option<f32>,
333    split_at_keyframe: Option<bool>,
334    start_index: Option<usize>,
335}
336
337impl Debug for SplitOptions {
338    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
339        f.debug_struct("SplitOptions")
340            .field("output_path", &self.output_path)
341            .field("max_files", &self.max_files)
342            .field("max_size_bytes", &self.max_size_bytes)
343            .field("max_size_time", &self.max_size_time)
344            .field("max_overhead", &self.max_overhead)
345            .field("split_at_keyframe", &self.split_at_keyframe)
346            .field("start_index", &self.start_index)
347            .finish()
348    }
349}
350
351/// Split Writer for Muxing Audio and Video.
352pub struct SplitWriter {
353    /// Media descriptions.
354    medias: Vec<Box<dyn MediaDesc>>,
355    /// The format to muxing.
356    format: Option<String>,
357    /// The options of muxing format.
358    format_options: Option<String>,
359    /// The underly writer.
360    writer: Option<Box<dyn Writer>>,
361    /// The location of the files to write.
362    output_path: PathBuf,
363    /// Callback for returns the location to be used for the next output file.
364    format_location: Option<Box<FormatLocationCallback>>,
365    /// Callback on before split fragment.
366    before_split: Option<Box<SplitNotifier>>,
367    /// Callback on after split fragment.
368    after_split: Option<Box<SplitNotifier>>,
369    /// Maximum number of files to keep on disk. Once the maximum is reached,
370    /// old files start to be deleted to make room for new ones.
371    max_files: usize,
372    /// Max amount of data per file (in bytes, 0=disable).
373    max_size_bytes: u64,
374    /// Max amount of time per file (in ns, 0=disable).
375    max_size_time: u64,
376    /// Extra size/time overhead of muxing.
377    max_overhead: f32,
378    /// Split at key frame input.
379    split_at_keyframe: bool,
380    /// Start value of fragment index.
381    start_index: usize,
382    /// Current value of fragment index.
383    current_index: usize,
384    /// Start time of the current fragment.
385    start_time: Instant,
386    /// The data flow started,
387    started: bool,
388    ///
389    need_key_frame: bool,
390    split_wait_for_key_frame: bool,
391}
392
393impl Debug for SplitWriter {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        write!(f, "SplitWriter @ 0x{:p}", self)
396    }
397}
398
399impl Writer for SplitWriter {
400    fn write_header(&mut self) -> AVResult<()> {
401        if let Some(writer) = &mut self.writer {
402            writer.write_header()
403        } else {
404            Err("The underly writer does not ready".into())
405        }
406    }
407
408    fn write_bytes(
409        &mut self,
410        bytes: &[u8],
411        pts: i64,
412        duration: i64,
413        is_key_frame: bool,
414        stream_index: usize,
415    ) -> AVResult<()> {
416        if self.can_split_now(is_key_frame, stream_index) {
417            self.split_now();
418        }
419
420        if self.writer.is_none() {
421            let writer = SimpleWriter::new(
422                self.format_location(self.current_index).to_str().unwrap(),
423                &self
424                    .medias
425                    .iter()
426                    .map(Deref::deref)
427                    .collect::<Vec<&dyn MediaDesc>>(),
428                self.format.as_deref(),
429                self.format_options.as_deref(),
430            )?;
431            self.writer = Some(Box::new(writer));
432            self.start_time = Instant::now();
433            self.started = true;
434        }
435
436        if let Some(ref mut writer) = self.writer {
437            writer.write_bytes(bytes, pts, duration, is_key_frame, stream_index)?;
438        }
439
440        Ok(())
441    }
442
443    fn write_trailer(&mut self) -> AVResult<()> {
444        if let Some(writer) = &mut self.writer {
445            writer.write_trailer()
446        } else {
447            Err("The underly writer does not ready".into())
448        }
449    }
450
451    fn close(&mut self) {
452        if let Some(writer) = &mut self.writer {
453            writer.close();
454        }
455    }
456
457    fn flush(&mut self) {
458        if let Some(writer) = &mut self.writer {
459            writer.flush();
460        }
461    }
462
463    fn size(&self) -> u64 {
464        if let Some(writer) = &self.writer {
465            writer.size()
466        } else {
467            0
468        }
469    }
470}
471
472impl SplitWriter {
473    /// Create a new writer with multipart files.
474    /// # Arguments
475    /// * `descs` - Media description of input streams.
476    /// * `format` - The format to muxing,like: mp4, mpegts.
477    /// * `format_options` - The options for muxing format,like: movfragement.
478    /// * `split_options` - The options for multipart files.
479    /// # Panics
480    /// The `output_path` must be set.
481    pub fn new(
482        descs: Vec<Box<dyn MediaDesc>>,
483        format: Option<&str>,
484        format_options: Option<&str>,
485        split_options: SplitOptions,
486    ) -> AVResult<Self> {
487        let mut need_key_frame = false;
488        for d in descs.iter() {
489            if d.codec_id().has_gop() {
490                need_key_frame = true;
491            }
492        }
493        Ok(Self {
494            medias: descs,
495            format: format.map(String::from),
496            format_options: format_options.map(String::from),
497            writer: None,
498            output_path: split_options.output_path.unwrap(),
499            format_location: split_options.format_location,
500            before_split: split_options.before_split,
501            after_split: split_options.after_split,
502            max_files: split_options.max_files.unwrap_or(0),
503            max_size_bytes: split_options.max_size_bytes.unwrap_or(0),
504            max_size_time: split_options.max_size_time.unwrap_or(0),
505            max_overhead: split_options.max_overhead.unwrap_or(0.1f32),
506            split_at_keyframe: split_options.split_at_keyframe.unwrap_or(true),
507            start_index: split_options.start_index.unwrap_or(0),
508            current_index: split_options.start_index.unwrap_or(0),
509            start_time: Instant::now(),
510            started: false,
511            need_key_frame,
512            split_wait_for_key_frame: false,
513        })
514    }
515
516    /// Returns `true` if `writer.size() >= max_size_bytes`.
517    pub(crate) fn is_bytes_overrun(&mut self) -> bool {
518        let mut exceeded = false;
519        if let Some(ref writer) = self.writer {
520            if self.max_size_bytes > 0 && writer.size() >= self.max_size_bytes {
521                exceeded = true
522            }
523        }
524        exceeded
525    }
526
527    /// Returns `true` if `writer.size() >= max_size_bytes * (1.0 + max_overhead)`.
528    pub(crate) fn is_bytes_overflow(&mut self) -> bool {
529        let mut exceeded = false;
530        if let Some(ref writer) = self.writer {
531            let overhead_bytes = self.max_size_bytes * (self.max_overhead * 100.0) as u64 / 100;
532            if self.max_size_bytes > 0 && writer.size() >= self.max_size_bytes + overhead_bytes {
533                exceeded = true
534            }
535        }
536        exceeded
537    }
538
539    /// Returns `true` if `time >= max_size_time`.
540    pub(crate) fn is_time_overrun(&mut self) -> bool {
541        self.max_size_time > 0
542            && self.start_time.elapsed() >= Duration::from_nanos(self.max_size_time)
543    }
544
545    /// Returns `true` if `time >= max_size_time * (1.0 + max_overhead)`.
546    pub(crate) fn is_time_overflow(&mut self) -> bool {
547        let overhead_time = self.max_size_time * (self.max_overhead * 100.0) as u64 / 100;
548        self.max_size_time > 0
549            && self.start_time.elapsed() >= Duration::from_nanos(self.max_size_time + overhead_time)
550    }
551
552    /// Return `true` if can split fragment now.
553    pub fn can_split_now(&mut self, is_key_frame: bool, stream_index: usize) -> bool {
554        let mut split_now: bool = false;
555        if self.split_wait_for_key_frame {
556            split_now = self.stream_has_key_frame(stream_index) && is_key_frame;
557            self.split_wait_for_key_frame = false;
558        } else {
559            let overrun = self.is_bytes_overrun() || self.is_time_overrun();
560            if overrun && self.split_at_keyframe && self.need_key_frame {
561                self.split_wait_for_key_frame = true;
562            } else {
563                split_now = overrun;
564            }
565        }
566        let overflow = self.is_bytes_overflow() || self.is_time_overflow();
567        split_now || overflow
568    }
569
570    /// Clean older files.
571    pub fn clean_files(&self) {
572        if self.max_files > 0 && (self.current_index - self.start_index) >= self.max_files - 1 {
573            let index = self.current_index - (self.max_files - 1);
574            if index >= self.start_index {
575                let old_file = self.format_location(index);
576                std::fs::remove_file(old_file).unwrap();
577            }
578        }
579    }
580
581    /// Returns the extension of the format.
582    pub fn ext_of_format(format: Option<&str>) -> &'static str {
583        format
584            .map(|s| match s {
585                "mp4" => ".mp4",
586                "mpegts" => ".ts",
587                _ => "dat",
588            })
589            .unwrap_or("dat")
590    }
591
592    /// Returns the fragment file location.
593    pub fn format_location(&self, index: usize) -> PathBuf {
594        let loc = if let Some(ref cb) = self.format_location {
595            cb(index)
596        } else {
597            format!(
598                "MED{:06}{}",
599                index,
600                Self::ext_of_format(self.format.as_deref())
601            )
602        };
603        let path = self.output_path.join(loc);
604        if let Some(parent) = path.parent() {
605            std::fs::create_dir_all(parent).unwrap();
606        }
607        path
608    }
609
610    /// Close the output file and create a new one.
611    pub fn split_now(&mut self) {
612        if let Some(ref cb) = self.before_split {
613            cb(self.current_index);
614        }
615        let _ = self.writer.take();
616        self.clean_files();
617        self.current_index += 1;
618        if let Some(ref cb) = self.after_split {
619            cb(self.current_index);
620        }
621    }
622
623    /// Return `true` if the stream has `key_frame` props.
624    pub fn stream_has_key_frame(&self, stream_index: usize) -> bool {
625        self.medias[stream_index].codec_id().has_gop()
626    }
627}
628
629/// Options Builder for the SimpleWriter.
630#[derive(Default)]
631pub struct OpenOptions {
632    medias: Vec<Box<dyn MediaDesc>>,
633    format: Option<String>,
634    format_options: Option<String>,
635    format_location: Option<Box<FormatLocationCallback>>,
636    before_split: Option<Box<SplitNotifier>>,
637    after_split: Option<Box<SplitNotifier>>,
638    max_files: Option<usize>,
639    max_size_bytes: Option<u64>,
640    max_size_time: Option<u64>,
641    max_overhead: Option<f32>,
642    split_at_keyframe: Option<bool>,
643    start_index: Option<usize>,
644}
645
646impl Debug for OpenOptions {
647    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
648        write!(f, "OpenOptions @ 0x{:p}", self)
649    }
650}
651
652impl OpenOptions {
653    /// Create an new Options Builder for the SimpleWriter.
654    pub fn new() -> Self {
655        Default::default()
656    }
657
658    /// Add a media description to the output format.
659    pub fn media<T>(mut self, media: T) -> Self
660    where
661        T: MediaDesc + Sized + 'static,
662    {
663        self.medias.push(Box::new(media));
664        self
665    }
666
667    /// Specified the muxing format of the output format.
668    pub fn format<S>(mut self, format: S) -> Self
669    where
670        S: Into<String>,
671    {
672        self.format = Some(format.into());
673        self
674    }
675
676    /// Specified the muxing format options of the output format.
677    pub fn format_options<S>(mut self, format_options: S) -> Self
678    where
679        S: Into<String>,
680    {
681        self.format_options = Some(format_options.into());
682        self
683    }
684
685    /// Callback for returns the location to be used for the next output file.
686    pub fn format_location<F>(mut self, format_location: F) -> Self
687    where
688        F: Fn(usize) -> String + 'static,
689    {
690        self.format_location = Some(Box::new(format_location));
691        self
692    }
693
694    /// Callback before split fragment.
695    pub fn before_split<F>(mut self, before_split: F) -> Self
696    where
697        F: Fn(usize) + 'static,
698    {
699        self.before_split = Some(Box::new(before_split));
700        self
701    }
702
703    /// Callback after split fragment.
704    pub fn after_split<F>(mut self, after_split: F) -> Self
705    where
706        F: Fn(usize) + 'static,
707    {
708        self.after_split = Some(Box::new(after_split));
709        self
710    }
711
712    /// Maximum number of files to keep on disk.
713    pub fn max_files(mut self, max_files: usize) -> Self {
714        self.max_files = Some(max_files);
715        self
716    }
717
718    /// Max amount of data per file (in bytes, 0=disable).
719    pub fn max_size_bytes(mut self, max_size_bytes: u64) -> Self {
720        self.max_size_bytes = Some(max_size_bytes);
721        self
722    }
723
724    /// Max amount of time per file (in ns, 0=disable).
725    pub fn max_size_time(mut self, max_size_time: u64) -> Self {
726        self.max_size_time = Some(max_size_time);
727        self
728    }
729
730    /// Extra size/time overhead of muxing (0.02 = 2%).
731    pub fn max_overhead(mut self, max_overhead: f32) -> Self {
732        self.max_overhead = Some(max_overhead);
733        self
734    }
735
736    /// Split immediately if `split_at_keyframe = false`.
737    /// The option ignored when `size > max_size_bytes + max_size_bytes * max_overhead`
738    /// or `time > max_size_time + max_size_time * max_overhead`.
739    pub fn split_at_keyframe(mut self, split_at_keyframe: bool) -> Self {
740        self.split_at_keyframe = Some(split_at_keyframe);
741        self
742    }
743
744    /// Start value of fragment index.
745    pub fn start_index(mut self, start_index: usize) -> Self {
746        self.start_index = Some(start_index);
747        self
748    }
749
750    /// Open the output file and returns the SimpleWriter.
751    pub fn open<P>(self, path: P) -> AVResult<Box<dyn Writer>>
752    where
753        P: AsRef<Path> + Sized,
754    {
755        if self.format_location.is_some() || self.max_files.is_some() {
756            let split_options = SplitOptions {
757                output_path: Some(AsRef::<Path>::as_ref(&path).to_path_buf()),
758                format_location: self.format_location,
759                before_split: self.before_split,
760                after_split: self.after_split,
761                max_files: self.max_files,
762                max_size_bytes: self.max_size_bytes,
763                max_size_time: self.max_size_time,
764                max_overhead: self.max_overhead,
765                split_at_keyframe: self.split_at_keyframe,
766                start_index: self.start_index,
767            };
768            let writer = SplitWriter::new(
769                self.medias,
770                self.format.as_deref(),
771                self.format_options.as_deref(),
772                split_options,
773            )?;
774            Ok(Box::new(writer))
775        } else {
776            let medias: Vec<&dyn MediaDesc> = self.medias.iter().map(Deref::deref).collect();
777            let writer = SimpleWriter::new(
778                path,
779                &medias[..],
780                self.format.as_deref(),
781                self.format_options.as_deref(),
782            )?;
783            Ok(Box::new(writer))
784        }
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791
792    #[test]
793    fn test_simple_writer() {
794        let a_desc = AudioDesc::new();
795        let v_desc = VideoDesc::with_h264(352, 288, 4000, 1000000);
796        let example_bytes = include_bytes!("../../examples/envivio-352x288.264.framed");
797        for _ in 0..100 {
798            let mut mp4_writer = SimpleWriter::new(
799                "/tmp/envivio-352x288.264.mp4",
800                &[&a_desc, &v_desc],
801                None,
802                Some("movflags=frag_keyframe"),
803            )
804            .unwrap();
805            let mut ts_writer = SimpleWriter::new(
806                "/tmp/envivio-352x288.264.ts",
807                &[&a_desc, &v_desc],
808                Some("mpegts"),
809                Some("mpegts_copyts=1"),
810            )
811            .unwrap();
812            let mut offset: usize = 0;
813            let mut pts = 0;
814            while offset + 4 < example_bytes.len() {
815                let size_bytes = &example_bytes[offset..offset + 4];
816                let frame_size = i32::from_be_bytes(size_bytes.try_into().unwrap()) as usize;
817                offset += 4;
818                let frame_bytes = &example_bytes[offset..offset + frame_size];
819                offset += frame_size;
820                mp4_writer
821                    .write_bytes(frame_bytes, pts, 40000, false, 0)
822                    .unwrap();
823                ts_writer
824                    .write_bytes(frame_bytes, pts, 40000, false, 0)
825                    .unwrap();
826                pts += 40000;
827            }
828        }
829    }
830}