Ffi+ffmpeg recast opaque pointer to struct

I am a newbie when it comes to Rust and have been playing around with ffmpeg. I am trying to use the ffmpeg-sys crate to ingest a stream via a tcp socket. The whole point of the program is to consume the stream (both audio and video) and return the metadata (codec, framerate, bitrate, etc.).

The only api that I could find in ffmpeg to do this is creating my own AVIOContext via avio_alloc_context. Some blogs/tutorials I found on the web:

My project's dependencies:

[dependencies]
ffmpeg-sys = "3.4"
libc = "0.2"

The code:

extern crate ffmpeg_sys;
extern crate libc;

use ffmpeg_sys::*;

use std::ffi::CString;
use std::ptr;
use std::sync::mpsc;


#[repr(C)]
#[derive(Debug)]
pub struct Context {
    ctx: *mut AVIOContext,
    tx: mpsc::Sender<Vec<u8>>,
    rx: mpsc::Receiver<Vec<u8>>,
}

impl Context {
    pub fn new(size: usize) -> (Self, mpsc::Sender<Vec<u8>>) {
        unsafe {
            let buf: *mut u8 = av_mallocz(size) as *mut u8;

            if buf.is_null() {
                panic!("Cannot create buf");
            }

            let (tx, rx) = mpsc::channel();

            let mut ret = Self {
                ctx: ptr::null_mut(),
                tx: tx.clone(),
                rx: rx,
            };

            println!("context pointer = {:p}", &mut ret);

            let ctx = avio_alloc_context(
                buf,
                size as i32,
                0,
                &mut ret as *mut _ as *mut libc::c_void,
                Some(read_packet),
                None,
                None,
            );

            if ctx.is_null() {
                panic!("Cannot create AVIO context");
            }

            ret.ctx = ctx;

            (ret, tx)
        }
    }
}

impl Drop for Context {
    fn drop(&mut self) {
        if self.ctx.is_null() {
            return;
        }

        unsafe {
            av_free((*self.ctx).buffer as *mut _);
            avio_context_free(self.ctx as *mut _);
        }
    }
}


unsafe extern "C" fn read_packet(
    opaque: *mut libc::c_void,
    buf: *mut u8,
    size: libc::c_int,
    ) -> libc::c_int
{
    println!("read_packet opaque = {:p}", opaque);
    let ctx: &mut Context = &mut *(opaque as *mut _);

    // here be the boom
    let data = ctx.rx.recv();

    // TODO write data to the internal ffmpeg buffer at ctx.ctx.buffer
    0
}


fn main() {
    let (ctx, tx) = Context::new(4096);

    tx.send(b"Hello World".to_vec());

    unsafe {
        let mut fmt_ctx = avformat_alloc_context();

        (*fmt_ctx).pb = ctx.ctx;

        let blank_str = CString::new("").unwrap();
        let h264 = CString::new("h264").unwrap();

        avformat_open_input(
            &mut fmt_ctx as *mut _,
            blank_str.as_ptr(),
            av_find_input_format(h264.as_ptr()),
            ptr::null_mut(),
        );

        avformat_free_context(fmt_ctx);
    }
}

Note that I am 100% a nublet when it comes to rust and especially unsafe rust. If you run the above code (couldn't create a playground example because of the dependency on ffmpeg) it produces output like:

context pointer = 0x7ffee0156c50
read_packet opaque = 0x7ffee0156c50
Illegal instruction: 4

the api that ffmpeg exposes is to use an opaque pointer which i'm using to contain the AVIOContext pointer and the mpsc::Receiver<Vec<u8>> that I want to use to pass in the data.

I've used rust-lldb to debug the process and the opaque pointer appears to be pointing to the Context struct. When I cast the opaque pointer to a Context object, it is completely mangled in memory.

Any pointers (pun-intended) as to what is going on?

Thanks!

You can't safely cast Rust's references to pointers. Rust puts most things on the stack (like your let ret = Self {}), so it's very easy to cause use-after-free bugs on the C side by giving it bad pointers to Rust's temporary variables.

Use Box::into_raw for passing pointers to C, and Box::from_raw to free the data later (to never mix memory allocators).

1 Like

That suggestion helped me to solve my problem - thank you very much.