Skip to main content

video_rs/
encode.rs

1extern crate ffmpeg_next as ffmpeg;
2
3use ffmpeg::codec::codec::Codec as AvCodec;
4use ffmpeg::codec::encoder::video::Encoder as AvEncoder;
5use ffmpeg::codec::encoder::video::Video as AvVideo;
6use ffmpeg::codec::flag::Flags as AvCodecFlags;
7use ffmpeg::codec::packet::Packet as AvPacket;
8use ffmpeg::codec::{Context as AvContext, Id as AvCodecId};
9use ffmpeg::format::flag::Flags as AvFormatFlags;
10use ffmpeg::software::scaling::context::Context as AvScaler;
11use ffmpeg::software::scaling::flag::Flags as AvScalerFlags;
12use ffmpeg::util::error::EAGAIN;
13use ffmpeg::util::format::Pixel as AvPixel;
14use ffmpeg::util::mathematics::rescale::TIME_BASE;
15use ffmpeg::util::picture::Type as AvFrameType;
16use ffmpeg::Error as AvError;
17use ffmpeg::Rational as AvRational;
18
19use crate::error::Error;
20use crate::ffi;
21#[cfg(feature = "ndarray")]
22use crate::frame::Frame;
23use crate::frame::{PixelFormat, RawFrame, FRAME_PIXEL_FORMAT};
24use crate::io::private::Write;
25use crate::io::{Writer, WriterBuilder};
26use crate::location::Location;
27use crate::options::Options;
28#[cfg(feature = "ndarray")]
29use crate::time::Time;
30
31type Result<T> = std::result::Result<T, Error>;
32
33/// Builds an [`Encoder`].
34pub struct EncoderBuilder<'a> {
35    destination: Location,
36    settings: Settings,
37    options: Option<&'a Options>,
38    format: Option<&'a str>,
39    interleaved: bool,
40}
41
42impl<'a> EncoderBuilder<'a> {
43    /// Create an encoder with the specified destination and settings.
44    ///
45    /// * `destination` - Where to encode to.
46    /// * `settings` - Encoding settings.
47    pub fn new(destination: impl Into<Location>, settings: Settings) -> Self {
48        Self {
49            destination: destination.into(),
50            settings,
51            options: None,
52            format: None,
53            interleaved: false,
54        }
55    }
56
57    /// Set the output options for the encoder.
58    ///
59    /// # Arguments
60    ///
61    /// * `options` - The output options.
62    pub fn with_options(mut self, options: &'a Options) -> Self {
63        self.options = Some(options);
64        self
65    }
66
67    /// Set the container format for the encoder.
68    ///
69    /// # Arguments
70    ///
71    /// * `format` - Container format to use.
72    pub fn with_format(mut self, format: &'a str) -> Self {
73        self.format = Some(format);
74        self
75    }
76
77    /// Set interleaved. This will cause the encoder to use interleaved write instead of normal
78    /// write.
79    pub fn interleaved(mut self) -> Self {
80        self.interleaved = true;
81        self
82    }
83
84    /// Build an [`Encoder`].
85    pub fn build(self) -> Result<Encoder> {
86        let mut writer_builder = WriterBuilder::new(self.destination);
87        if let Some(options) = self.options {
88            writer_builder = writer_builder.with_options(options);
89        }
90        if let Some(format) = self.format {
91            writer_builder = writer_builder.with_format(format);
92        }
93        Encoder::from_writer(writer_builder.build()?, self.interleaved, self.settings)
94    }
95}
96
97/// Encodes frames into a video stream.
98///
99/// # Example
100///
101/// ```ignore
102/// let encoder = Encoder::new(
103///     Path::new("video_in.mp4"),
104///     Settings::for_h264_yuv420p(800, 600, 30.0)
105/// )
106/// .unwrap();
107///
108/// let decoder = Decoder::new(Path::new("video_out.mkv")).unwrap();
109/// decoder
110///     .decode_iter()
111///     .take_while(Result::is_ok)
112///     .map(|frame| encoder
113///         .encode(frame.unwrap())
114///         .expect("Failed to encode frame."),
115///     );
116/// ```
117pub struct Encoder {
118    writer: Writer,
119    writer_stream_index: usize,
120    encoder: AvEncoder,
121    encoder_time_base: AvRational,
122    keyframe_interval: u64,
123    interleaved: bool,
124    scaler: AvScaler,
125    scaler_width: u32,
126    scaler_height: u32,
127    frame_count: u64,
128    have_written_header: bool,
129    have_written_trailer: bool,
130}
131
132impl Encoder {
133    /// Create an encoder with the specified destination and settings.
134    ///
135    /// * `destination` - Where to encode to.
136    /// * `settings` - Encoding settings.
137    #[inline]
138    pub fn new(destination: impl Into<Location>, settings: Settings) -> Result<Self> {
139        EncoderBuilder::new(destination, settings).build()
140    }
141
142    /// Encode a single `ndarray` frame.
143    ///
144    /// # Arguments
145    ///
146    /// * `frame` - Frame to encode in `HWC` format and standard layout.
147    /// * `source_timestamp` - Frame timestamp of original source. This is necessary to make sure
148    ///   the output will be timed correctly.
149    #[cfg(feature = "ndarray")]
150    pub fn encode(&mut self, frame: &Frame, source_timestamp: Time) -> Result<()> {
151        let (height, width, channels) = frame.dim();
152        if height != self.scaler_height as usize
153            || width != self.scaler_width as usize
154            || channels != 3
155        {
156            return Err(Error::InvalidFrameFormat);
157        }
158
159        let mut frame = ffi::convert_ndarray_to_frame_rgb24(frame).map_err(Error::BackendError)?;
160
161        frame.set_pts(
162            source_timestamp
163                .aligned_with_rational(self.encoder_time_base)
164                .into_value(),
165        );
166
167        self.encode_raw(frame)
168    }
169
170    /// Encode a single raw frame.
171    ///
172    /// # Arguments
173    ///
174    /// * `frame` - Frame to encode.
175    pub fn encode_raw(&mut self, frame: RawFrame) -> Result<()> {
176        if frame.width() != self.scaler_width
177            || frame.height() != self.scaler_height
178            || frame.format() != FRAME_PIXEL_FORMAT
179        {
180            return Err(Error::InvalidFrameFormat);
181        }
182
183        // Write file header if we hadn't done that yet.
184        if !self.have_written_header {
185            self.writer.write_header()?;
186            self.have_written_header = true;
187        }
188
189        // Reformat frame to target pixel format.
190        let mut frame = self.scale(frame)?;
191        // Producer key frame every once in a while
192        if self.frame_count.is_multiple_of(self.keyframe_interval) {
193            frame.set_kind(AvFrameType::I);
194        }
195
196        self.encoder
197            .send_frame(&frame)
198            .map_err(Error::BackendError)?;
199        // Increment frame count regardless of whether or not frame is written, see
200        // https://github.com/oddity-ai/video-rs/issues/46.
201        self.frame_count += 1;
202
203        if let Some(packet) = self.encoder_receive_packet()? {
204            self.write(packet)?;
205        }
206
207        Ok(())
208    }
209
210    /// Signal to the encoder that writing has finished. This will cause any packets in the encoder
211    /// to be flushed and a trailer to be written if the container format has one.
212    ///
213    /// Note: If you don't call this function before dropping the encoder, it will be called
214    /// automatically. This will block the caller thread. Any errors cannot be propagated in this
215    /// case.
216    pub fn finish(&mut self) -> Result<()> {
217        if self.have_written_header && !self.have_written_trailer {
218            self.have_written_trailer = true;
219            self.flush()?;
220            self.writer.write_trailer()?;
221        }
222
223        Ok(())
224    }
225
226    /// Get encoder time base.
227    #[inline]
228    pub fn time_base(&self) -> AvRational {
229        self.encoder_time_base
230    }
231
232    /// Create an encoder from a [`Writer`].
233    ///
234    /// # Arguments
235    ///
236    /// * `writer` - [`Writer`] to create encoder from.
237    /// * `interleaved` - Whether or not to use interleaved write.
238    /// * `settings` - Encoder settings to use.
239    fn from_writer(mut writer: Writer, interleaved: bool, settings: Settings) -> Result<Self> {
240        let global_header = writer
241            .output
242            .format()
243            .flags()
244            .contains(AvFormatFlags::GLOBAL_HEADER);
245
246        let mut writer_stream = writer.output.add_stream(settings.codec())?;
247        let writer_stream_index = writer_stream.index();
248
249        let mut encoder_context = match settings.codec() {
250            Some(codec) => ffi::codec_context_as(&codec)?,
251            None => AvContext::new(),
252        };
253
254        // Some formats require this flag to be set or the output will
255        // not be playable by dumb players.
256        if global_header {
257            encoder_context.set_flags(AvCodecFlags::GLOBAL_HEADER);
258        }
259
260        let mut encoder = encoder_context.encoder().video()?;
261        settings.apply_to(&mut encoder);
262
263        // Just use the ffmpeg global time base which is precise enough
264        // that we should never get in trouble.
265        encoder.set_time_base(TIME_BASE);
266
267        let encoder = encoder.open_with(settings.options().to_dict())?;
268        let encoder_time_base = ffi::get_encoder_time_base(&encoder);
269
270        writer_stream.set_parameters(&encoder);
271
272        let scaler_width = encoder.width();
273        let scaler_height = encoder.height();
274        let scaler = AvScaler::get(
275            FRAME_PIXEL_FORMAT,
276            scaler_width,
277            scaler_height,
278            encoder.format(),
279            scaler_width,
280            scaler_height,
281            AvScalerFlags::empty(),
282        )?;
283
284        Ok(Self {
285            writer,
286            writer_stream_index,
287            encoder,
288            encoder_time_base,
289            keyframe_interval: settings.keyframe_interval,
290            interleaved,
291            scaler,
292            scaler_width,
293            scaler_height,
294            frame_count: 0,
295            have_written_header: false,
296            have_written_trailer: false,
297        })
298    }
299
300    /// Apply scaling (or pixel reformatting in this case) on the frame with the scaler we
301    /// initialized earlier.
302    ///
303    /// # Arguments
304    ///
305    /// * `frame` - Frame to rescale.
306    fn scale(&mut self, frame: RawFrame) -> Result<RawFrame> {
307        let mut frame_scaled = RawFrame::empty();
308        self.scaler
309            .run(&frame, &mut frame_scaled)
310            .map_err(Error::BackendError)?;
311        // Copy over PTS from old frame.
312        frame_scaled.set_pts(frame.pts());
313
314        Ok(frame_scaled)
315    }
316
317    /// Pull an encoded packet from the decoder. This function also handles the possible `EAGAIN`
318    /// result, in which case we just need to go again.
319    fn encoder_receive_packet(&mut self) -> Result<Option<AvPacket>> {
320        let mut packet = AvPacket::empty();
321        let encode_result = self.encoder.receive_packet(&mut packet);
322        match encode_result {
323            Ok(()) => Ok(Some(packet)),
324            Err(AvError::Other { errno }) if errno == EAGAIN => Ok(None),
325            Err(err) => Err(err.into()),
326        }
327    }
328
329    /// Acquire the time base of the output stream.
330    fn stream_time_base(&mut self) -> AvRational {
331        self.writer
332            .output
333            .stream(self.writer_stream_index)
334            .unwrap()
335            .time_base()
336    }
337
338    /// Write encoded packet to output stream.
339    ///
340    /// # Arguments
341    ///
342    /// * `packet` - Encoded packet.
343    fn write(&mut self, mut packet: AvPacket) -> Result<()> {
344        packet.set_stream(self.writer_stream_index);
345        packet.set_position(-1);
346        packet.rescale_ts(self.encoder_time_base, self.stream_time_base());
347        if self.interleaved {
348            self.writer.write_interleaved(&mut packet)?;
349        } else {
350            self.writer.write(&mut packet)?;
351        };
352
353        Ok(())
354    }
355
356    /// Flush the encoder, drain any packets that still need processing.
357    fn flush(&mut self) -> Result<()> {
358        // Maximum number of invocations to `encoder_receive_packet`
359        // to drain the items still on the queue before giving up.
360        const MAX_DRAIN_ITERATIONS: u32 = 100;
361
362        // Notify the encoder that the last frame has been sent.
363        self.encoder.send_eof()?;
364
365        // We need to drain the items still in the encoders queue.
366        for _ in 0..MAX_DRAIN_ITERATIONS {
367            match self.encoder_receive_packet() {
368                Ok(Some(packet)) => self.write(packet)?,
369                Ok(None) => continue,
370                Err(_) => break,
371            }
372        }
373
374        Ok(())
375    }
376}
377
378impl Drop for Encoder {
379    fn drop(&mut self) {
380        let _ = self.finish();
381    }
382}
383
384/// Holds a logical combination of encoder settings.
385#[derive(Debug, Clone)]
386pub struct Settings {
387    width: u32,
388    height: u32,
389    pixel_format: AvPixel,
390    keyframe_interval: u64,
391    options: Options,
392}
393
394impl Settings {
395    /// Default keyframe interval.
396    const KEY_FRAME_INTERVAL: u64 = 12;
397
398    /// This is the assumed FPS for the encoder to use. Note that this does not need to be correct
399    /// exactly.
400    const FRAME_RATE: i32 = 30;
401
402    /// Create encoder settings for an H264 stream with YUV420p pixel format. This will encode to
403    /// arguably the most widely compatible video file since H264 is a common codec and YUV420p is
404    /// the most commonly used pixel format.
405    pub fn preset_h264_yuv420p(width: usize, height: usize, realtime: bool) -> Settings {
406        let options = if realtime {
407            Options::preset_h264_realtime()
408        } else {
409            Options::preset_h264()
410        };
411
412        Self {
413            width: width as u32,
414            height: height as u32,
415            pixel_format: AvPixel::YUV420P,
416            keyframe_interval: Self::KEY_FRAME_INTERVAL,
417            options,
418        }
419    }
420
421    /// Create encoder settings for an H264 stream with a custom pixel format and options.
422    /// This allows for greater flexibility in encoding settings, enabling specific requirements
423    /// or optimizations to be set depending on the use case.
424    ///
425    /// # Arguments
426    ///
427    /// * `width` - The width of the video stream.
428    /// * `height` - The height of the video stream.
429    /// * `pixel_format` - The desired pixel format for the video stream.
430    /// * `options` - Custom H264 encoding options.
431    ///
432    /// # Return value
433    ///
434    /// A `Settings` instance with the specified configuration.+
435    pub fn preset_h264_custom(
436        width: usize,
437        height: usize,
438        pixel_format: PixelFormat,
439        options: Options,
440    ) -> Settings {
441        Self {
442            width: width as u32,
443            height: height as u32,
444            pixel_format,
445            keyframe_interval: Self::KEY_FRAME_INTERVAL,
446            options,
447        }
448    }
449
450    /// Set the keyframe interval.
451    pub fn set_keyframe_interval(&mut self, keyframe_interval: u64) {
452        self.keyframe_interval = keyframe_interval;
453    }
454
455    /// Set the keyframe interval.
456    pub fn with_keyframe_interval(mut self, keyframe_interval: u64) -> Self {
457        self.set_keyframe_interval(keyframe_interval);
458        self
459    }
460
461    /// Apply the settings to an encoder.
462    ///
463    /// # Arguments
464    ///
465    /// * `encoder` - Encoder to apply settings to.
466    ///
467    /// # Return value
468    ///
469    /// New encoder with settings applied.
470    fn apply_to(&self, encoder: &mut AvVideo) {
471        encoder.set_width(self.width);
472        encoder.set_height(self.height);
473        encoder.set_format(self.pixel_format);
474        encoder.set_frame_rate(Some((Self::FRAME_RATE, 1)));
475    }
476
477    /// Get codec.
478    fn codec(&self) -> Option<AvCodec> {
479        // Try to use the libx264 decoder. If it is not available, then use use whatever default
480        // h264 decoder we have.
481        Some(
482            ffmpeg::encoder::find_by_name("libx264")
483                .unwrap_or(ffmpeg::encoder::find(AvCodecId::H264)?),
484        )
485    }
486
487    /// Get encoder options.
488    fn options(&self) -> &Options {
489        &self.options
490    }
491}
492
493unsafe impl Send for Encoder {}
494unsafe impl Sync for Encoder {}