moonfire_ffmpeg/
avformat.rs

1// Copyright (C) 2017-2020 Scott Lamb <slamb@slamb.org>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::avcodec::{
5    av_init_packet, moonfire_ffmpeg_packet_alloc, moonfire_ffmpeg_packet_free, AVCodecParameters,
6    AVPacket, InputCodecParameters, Packet,
7};
8use crate::avutil::{Dictionary, Error};
9use std::cell::RefCell;
10use std::convert::TryFrom;
11use std::ffi::CStr;
12use std::marker::PhantomData;
13use std::ptr;
14
15//#[link(name = "avformat")]
16extern "C" {
17    pub(crate) fn avformat_version() -> libc::c_int;
18    pub(crate) fn avformat_configuration() -> *mut libc::c_char;
19
20    fn avformat_alloc_context() -> *mut AVFormatContext;
21
22    fn avio_alloc_context(
23        buffer: *const u8,
24        buffer_size: libc::c_int,
25        write_flag: libc::c_int,
26        opaque: *const libc::c_void,
27        read_packet: Option<
28            unsafe extern "C" fn(
29                opaque: *const libc::c_void,
30                buf: *mut u8,
31                buf_size: libc::c_int,
32            ) -> libc::c_int,
33        >,
34        write_packet: Option<
35            unsafe extern "C" fn(
36                opaque: *const libc::c_void,
37                buf: *const u8,
38                buf_size: libc::c_int,
39            ) -> libc::c_int,
40        >,
41        seek: Option<
42            unsafe extern "C" fn(
43                opaque: *const libc::c_void,
44                offset: i64,
45                whence: libc::c_int,
46            ) -> i64,
47        >,
48    ) -> *mut AVIOContext;
49    fn avio_context_free(s: *mut *mut AVIOContext);
50
51    //fn avformat_alloc_output_context2(ctx: *mut *mut AVFormatContext, oformat: *mut AVOutputFormat,
52    //                                  format_name: *const libc::c_char,
53    //                                  filename: *const libc::c_char) -> libc::c_int;
54    fn avformat_open_input(
55        ctx: *mut *mut AVFormatContext,
56        url: *const libc::c_char,
57        fmt: *const AVInputFormat,
58        options: *mut Dictionary,
59    ) -> libc::c_int;
60    fn avformat_close_input(ctx: *mut *mut AVFormatContext);
61    fn avformat_find_stream_info(
62        ctx: *mut AVFormatContext,
63        options: *mut Dictionary,
64    ) -> libc::c_int;
65    //fn avformat_new_stream(s: *mut AVFormatContext, c: *const AVCodec) -> *mut AVStream;
66    //fn avformat_write_header(c: *mut AVFormatContext, opts: *mut *mut AVDictionary) -> libc::c_int;
67    fn av_read_frame(ctx: *mut AVFormatContext, p: *mut AVPacket) -> libc::c_int;
68    pub(crate) fn av_register_all();
69    pub(crate) fn avformat_network_init() -> libc::c_int;
70}
71
72//#[link(name = "wrapper")]
73extern "C" {
74    pub(crate) static moonfire_ffmpeg_compiled_libavformat_version: libc::c_int;
75
76    static moonfire_ffmpeg_avseek_force: libc::c_int;
77    static moonfire_ffmpeg_avseek_size: libc::c_int;
78    static moonfire_ffmpeg_seek_set: libc::c_int;
79    static moonfire_ffmpeg_seek_cur: libc::c_int;
80    static moonfire_ffmpeg_seek_end: libc::c_int;
81
82    fn moonfire_ffmpeg_fctx_streams(ctx: *const AVFormatContext) -> StreamsLen;
83    //fn moonfire_ffmpeg_fctx_open_write(ctx: *mut AVFormatContext,
84    //                                   url: *const libc::c_char) -> libc::c_int;
85    //
86
87    fn moonfire_ffmpeg_fctx_set_pb(ctx: *mut AVFormatContext, pb: *mut AVIOContext);
88
89    fn moonfire_ffmpeg_ioctx_set_direct(pb: *mut AVIOContext);
90
91    fn moonfire_ffmpeg_stream_codecpar(stream: *const AVStream) -> *const AVCodecParameters;
92    fn moonfire_ffmpeg_stream_duration(stream: *const AVStream) -> i64;
93    fn moonfire_ffmpeg_stream_time_base(stream: *const AVStream) -> crate::avutil::Rational;
94}
95
96// No ABI stability assumption here; use heap allocation/deallocation and accessors only.
97#[repr(C)]
98struct AVFormatContext {
99    _private: [u8; 0],
100}
101#[repr(C)]
102struct AVIOContext {
103    _private: [u8; 0],
104}
105#[repr(C)]
106struct AVInputFormat {
107    _private: [u8; 0],
108}
109#[repr(C)]
110struct AVStream {
111    _private: [u8; 0],
112}
113
114pub struct InputFormatContext<'a> {
115    /// When using `InputFormatContext::with_io_context`, `ctx` has a `pb` member which has an
116    /// `opaque` referencing `_io_ctx`.
117    _io_ctx: PhantomData<&'a mut dyn IoContext>,
118    ctx: *mut AVFormatContext,
119    pkt: RefCell<*mut AVPacket>,
120}
121
122/// Mode argument to `IoContext::seek`.
123pub enum Whence {
124    /// Return the size (if possible) without actually seeking.
125    Size,
126
127    /// Sets a position relative to the start of the file, returning the new position.
128    Set,
129
130    /// Sets a position relative to the current position, returning the new position.
131    Cur,
132
133    /// Sets a position relative to the end of the file, returning the new position.
134    End,
135}
136
137/// An implementation of the IO operations needed by libavformat.
138/// See ffmpeg's `avio_alloc_context` and `avformat_open_input` for more information.
139pub trait IoContext {
140    /// Returns true iff this is readable; then `read` should be implemented.
141    fn readable(&self) -> bool {
142        false
143    }
144
145    /// Returns true iff this is writable; then `write` should be implemented.
146    fn writable(&self) -> bool {
147        false
148    }
149
150    /// Returns true iff this is seekable; then `seek` should be implemented.
151    fn seekable(&self) -> bool {
152        false
153    }
154
155    /// Returns true iff the buffer should be skipped; this is a hint; ffmpeg may use the buffer
156    /// anyway.
157    fn direct(&self) -> bool {
158        false
159    }
160
161    /// Returns the desired length of the buffer.
162    fn buf_len(&self) -> usize;
163
164    /// Reads up to the given number of bytes into `buf`.
165    #[allow(unused_variables)]
166    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
167        Err(Error::enosys())
168    }
169
170    /// Writes up to the given number of bytes into `buf`.
171    #[allow(unused_variables)]
172    fn write(&mut self, buf: &[u8]) -> Result<usize, Error> {
173        Err(Error::enosys())
174    }
175
176    /// Performs the operation described by `whence`.
177    /// `force` corresponds to the `AVSEEK_FORCE` flag.
178    #[allow(unused_variables)]
179    fn seek(&mut self, offset: i64, whence: Whence, force: bool) -> Result<u64, Error> {
180        Err(Error::enosys())
181    }
182}
183
184/// An `IoContext` implementation for an immutable slice.
185pub struct SliceIoContext<'a> {
186    slice: &'a [u8],
187    pos: usize,
188}
189
190impl<'a> SliceIoContext<'a> {
191    pub fn new(slice: &'a [u8]) -> Self {
192        Self { slice, pos: 0 }
193    }
194}
195
196impl<'a> IoContext for SliceIoContext<'a> {
197    fn readable(&self) -> bool {
198        true
199    }
200    fn writable(&self) -> bool {
201        false
202    }
203    fn seekable(&self) -> bool {
204        true
205    }
206    fn direct(&self) -> bool {
207        true
208    }
209    fn buf_len(&self) -> usize {
210        std::cmp::min(self.slice.len(), 4096)
211    }
212    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
213        let copy_len = std::cmp::min(buf.len(), self.slice.len() - self.pos);
214        buf[0..copy_len].copy_from_slice(&self.slice[self.pos..self.pos + copy_len]);
215        self.pos += copy_len;
216        Ok(copy_len)
217    }
218    fn seek(&mut self, offset: i64, whence: Whence, _force: bool) -> Result<u64, Error> {
219        let offset = usize::try_from(offset).map_err(|_| Error::invalid_data())?;
220        let new_pos = match whence {
221            Whence::Size => return Ok(u64::try_from(self.slice.len()).unwrap()),
222            Whence::Set => offset,
223            Whence::Cur => self
224                .pos
225                .checked_add(offset)
226                .ok_or_else(Error::invalid_data)?,
227            Whence::End => self
228                .pos
229                .checked_add(offset)
230                .ok_or_else(Error::invalid_data)?,
231        };
232        if new_pos > self.slice.len() {
233            return Err(Error::invalid_data());
234        }
235        self.pos = new_pos;
236        Ok(u64::try_from(self.pos).unwrap())
237    }
238}
239
240struct IoContextWrapper<'a> {
241    // The opaque pointer passed to the callbacks must be thin, so create a box here so we have a
242    // stable address to the fat pointer itself.
243    _ctx: Box<&'a mut dyn IoContext>,
244
245    avio_ctx: ptr::NonNull<AVIOContext>,
246}
247
248/// Implements the `read_packet` argument to `avio_alloc_context`.
249unsafe extern "C" fn ioctx_read_packet(
250    opaque: *const libc::c_void,
251    buf_data: *mut u8,
252    buf_len: libc::c_int,
253) -> libc::c_int {
254    let ctx: &mut &mut dyn IoContext = &mut *(opaque as *mut &mut dyn IoContext);
255    let buf = std::slice::from_raw_parts_mut(buf_data, usize::try_from(buf_len).unwrap());
256    match ctx.read(buf) {
257        Ok(l) => libc::c_int::try_from(l).unwrap(),
258        Err(e) => e.get(),
259    }
260}
261
262/// Implements the `write_packet` argument to `avio_alloc_context`.
263unsafe extern "C" fn ioctx_write_packet(
264    opaque: *const libc::c_void,
265    buf_data: *const u8,
266    buf_len: libc::c_int,
267) -> libc::c_int {
268    let ctx: &mut &mut dyn IoContext = &mut *(opaque as *mut &mut dyn IoContext);
269    let buf = std::slice::from_raw_parts(buf_data, usize::try_from(buf_len).unwrap());
270    match ctx.write(buf) {
271        Ok(l) => libc::c_int::try_from(l).unwrap(),
272        Err(e) => e.get(),
273    }
274}
275
276/// Implements the `seek_packet` argument to `avio_alloc_context`.
277unsafe extern "C" fn ioctx_seek(
278    opaque: *const libc::c_void,
279    offset: i64,
280    whence: libc::c_int,
281) -> i64 {
282    let ctx: &mut &mut dyn IoContext = &mut *(opaque as *mut &mut dyn IoContext);
283    let avseek_force = moonfire_ffmpeg_avseek_force;
284    let force = (whence & avseek_force) != 0;
285    let w = whence & !avseek_force;
286    let whence = if (w & moonfire_ffmpeg_avseek_size) != 0 {
287        Whence::Size
288    } else if w == moonfire_ffmpeg_seek_set {
289        Whence::Set
290    } else if w == moonfire_ffmpeg_seek_cur {
291        Whence::Cur
292    } else if w == moonfire_ffmpeg_seek_end {
293        Whence::End
294    } else {
295        panic!("invalid whence {}", whence);
296    };
297    match ctx.seek(offset, whence, force) {
298        Ok(p) => i64::try_from(p).unwrap(),
299        Err(e) => i64::from(e.get()),
300    }
301}
302
303impl<'a> IoContextWrapper<'a> {
304    fn new(ctx: &'a mut dyn IoContext) -> Result<Self, Error> {
305        let ctx = Box::new(ctx);
306        let buf_len = ctx.buf_len();
307        let mut buf = crate::avutil::Alloc::new(buf_len)?;
308        let avio_ctx = ptr::NonNull::new(unsafe {
309            let opaque: &&mut dyn IoContext = &ctx;
310            avio_alloc_context(
311                buf.as_ptr() as *const u8,
312                i32::try_from(buf_len).unwrap(),
313                if ctx.writable() { 1 } else { 0 },
314                opaque as *const &mut dyn IoContext as *mut core::ffi::c_void,
315                if ctx.readable() {
316                    Some(ioctx_read_packet)
317                } else {
318                    None
319                },
320                if ctx.writable() {
321                    Some(ioctx_write_packet)
322                } else {
323                    None
324                },
325                if ctx.seekable() {
326                    Some(ioctx_seek)
327                } else {
328                    None
329                },
330            )
331        })
332        .ok_or_else(Error::enomem)?;
333        std::mem::forget(buf); // owned by avio_ctx iff alloc is successful.
334        if ctx.direct() {
335            unsafe { moonfire_ffmpeg_ioctx_set_direct(avio_ctx.as_ptr()) };
336        }
337        Ok(Self {
338            avio_ctx,
339            _ctx: ctx,
340        })
341    }
342
343    fn release(self) -> *mut AVIOContext {
344        let p = self.avio_ctx.as_ptr();
345        std::mem::forget(self);
346        p
347    }
348}
349
350impl<'a> Drop for IoContextWrapper<'a> {
351    fn drop(&mut self) {
352        let mut p = self.avio_ctx.as_ptr();
353        unsafe { avio_context_free(&mut p) };
354    }
355}
356
357impl<'a> InputFormatContext<'a> {
358    pub fn open(source: &CStr, dict: &mut Dictionary) -> Result<Self, Error> {
359        let mut ctx = ptr::null_mut();
360        Error::wrap(unsafe { avformat_open_input(&mut ctx, source.as_ptr(), ptr::null(), dict) })?;
361        let pkt = unsafe { moonfire_ffmpeg_packet_alloc() };
362        if pkt.is_null() {
363            unsafe { avformat_close_input(&mut ctx) };
364            return Err(Error::enomem());
365        }
366        unsafe { av_init_packet(pkt) };
367        Ok(InputFormatContext {
368            ctx,
369            _io_ctx: PhantomData,
370            pkt: RefCell::new(pkt),
371        })
372    }
373
374    pub fn with_io_context(
375        source: &CStr,
376        io_ctx: &'a mut dyn IoContext,
377        dict: &mut Dictionary,
378    ) -> Result<Self, Error> {
379        let wrapper = IoContextWrapper::new(io_ctx)?;
380        let mut ctx = unsafe { avformat_alloc_context() };
381        if ctx.is_null() {
382            return Err(Error::enomem());
383        }
384        // Note that `ctx` is freed by `avformat_open_input` on failure, including the avio_ctx.
385        unsafe { moonfire_ffmpeg_fctx_set_pb(ctx, wrapper.release()) };
386        Error::wrap(unsafe { avformat_open_input(&mut ctx, source.as_ptr(), ptr::null(), dict) })?;
387        let pkt = unsafe { moonfire_ffmpeg_packet_alloc() };
388        if pkt.is_null() {
389            unsafe { avformat_close_input(&mut ctx) };
390            return Err(Error::enomem());
391        }
392        unsafe { av_init_packet(pkt) };
393        Ok(InputFormatContext {
394            _io_ctx: PhantomData,
395            ctx,
396            pkt: RefCell::new(pkt),
397        })
398    }
399
400    pub fn find_stream_info(&mut self) -> Result<(), Error> {
401        Error::wrap(unsafe { avformat_find_stream_info(self.ctx, ptr::null_mut()) })?;
402        Ok(())
403    }
404
405    // XXX: non-mut because of lexical lifetime woes in the caller. This is also why we need a
406    // RefCell.
407    pub fn read_frame(&self) -> Result<Packet<'_>, Error> {
408        let pkt = self.pkt.borrow();
409        Error::wrap(unsafe { av_read_frame(self.ctx, *pkt) })?;
410        Ok(Packet(pkt))
411    }
412
413    pub fn streams(&self) -> Streams {
414        Streams(unsafe {
415            let s = moonfire_ffmpeg_fctx_streams(self.ctx);
416            std::slice::from_raw_parts(s.streams, s.len as usize)
417        })
418    }
419}
420
421unsafe impl<'a> Send for InputFormatContext<'a> {}
422
423impl<'a> Drop for InputFormatContext<'a> {
424    fn drop(&mut self) {
425        unsafe {
426            moonfire_ffmpeg_packet_free(*self.pkt.borrow());
427            avformat_close_input(&mut self.ctx);
428        }
429    }
430}
431
432// matches moonfire_ffmpeg_streams_len
433#[repr(C)]
434struct StreamsLen {
435    streams: *const *const AVStream,
436    len: libc::size_t,
437}
438
439pub struct Streams<'owner>(&'owner [*const AVStream]);
440
441impl<'owner> Streams<'owner> {
442    pub fn get(&self, i: usize) -> InputStream<'owner> {
443        InputStream(unsafe { self.0[i].as_ref() }.unwrap())
444    }
445    pub fn len(&self) -> usize {
446        self.0.len()
447    }
448    pub fn is_empty(&self) -> bool {
449        self.len() == 0
450    }
451}
452
453pub struct InputStream<'o>(&'o AVStream);
454
455impl<'o> InputStream<'o> {
456    pub fn codecpar(&self) -> InputCodecParameters<'o> {
457        InputCodecParameters(unsafe { moonfire_ffmpeg_stream_codecpar(self.0).as_ref() }.unwrap())
458    }
459
460    pub fn time_base(&self) -> crate::avutil::Rational {
461        unsafe { moonfire_ffmpeg_stream_time_base(self.0) }
462    }
463
464    pub fn duration(&self) -> i64 {
465        unsafe { moonfire_ffmpeg_stream_duration(self.0) }
466    }
467}
468
469#[cfg(test)]
470mod test {
471    use cstr::cstr;
472
473    fn with_packets<F>(ctx: &mut super::InputFormatContext, mut f: F)
474    where
475        F: FnMut(crate::avcodec::Packet),
476    {
477        loop {
478            let pkt = match ctx.read_frame() {
479                Err(e) if e.is_eof() => break,
480                Err(e) => panic!("{}", e),
481                Ok(p) => p,
482            };
483            f(pkt);
484        }
485    }
486
487    #[test]
488    fn file() {
489        crate::Ffmpeg::new();
490        let mut dict = crate::avutil::Dictionary::new();
491        let mut ctx =
492            super::InputFormatContext::open(cstr!("src/testdata/clip.mp4"), &mut dict).unwrap();
493        let mut pts = Vec::new();
494        with_packets(&mut ctx, |pkt| pts.push(pkt.pts().unwrap()));
495        assert_eq!(pts, &[0, 29700, 59400, 90000, 119700, 149400]);
496    }
497
498    // Directly reference it as a slice.
499    #[test]
500    fn slice() {
501        crate::Ffmpeg::new();
502        let mut dict = crate::avutil::Dictionary::new();
503        let mut io_ctx = super::SliceIoContext::new(include_bytes!("testdata/clip.mp4"));
504        let mut ctx =
505            super::InputFormatContext::with_io_context(cstr!(""), &mut io_ctx, &mut dict).unwrap();
506        let mut pts = Vec::new();
507        with_packets(&mut ctx, |pkt| pts.push(pkt.pts().unwrap()));
508        assert_eq!(pts, &[0, 29700, 59400, 90000, 119700, 149400]);
509    }
510}