rust-ffmpeg icon indicating copy to clipboard operation
rust-ffmpeg copied to clipboard

The Frame's `data` attribute's length can be misleading

Open dceddia opened this issue 4 years ago • 0 comments

I'm using rust-ffmpeg with ffmpeg 4.4. I ran into an issue playing audio where I was getting weird blips of noise, but only sometimes. It would change between runs, randomly. I figured it out, so I'm posting this here for anyone else running into it. (the problem in #64 might be similar)

I thought uninitialized memory might be to blame, so I tried checksumming the packets coming out of the file - they were repeatable and fine. I tried the same on the data coming out of each frame.data(0), and that varied between runs. Weird!

Digging into it a bit, I realized that my audio format was coming out of the file as F32 Planar. Each frame reported it had 1024 samples, and an 8192 byte linesize. So frame.data(0) was 8192 bytes, but this is too big: 1 channel (plane) of 1024 4-byte f32's is only 4096 bytes, so ffmpeg was only filling half the buffer.

When I copied frame.data(0) into my audio callback, I got half good stuff and half noise, and sometimes I got lucky and the noise was initialized to 0.

I tried the same operation with a C program calling ffmpeg and got the same result, so I don't think rust-ffmpeg is doing anything wrong here.

I realized in the end that what I needed to do was ignore the extra bytes in the frame.data(0) buffer, and explicitly take only the first N that the frame said it contained, where N depends on the number of samples, channels, and whether the data is planar or interleaved.

// Pluck off the valid bytes and append them to a buffer to play
let num_bytes = resampled.samples() * (resampled.channels() as usize) * resampled.format().bytes();
buffer.extend_from_slice(&resampled.data(0)[0..num_bytes]);

This is fiddly though, and it'd be nice to have an accessor that could provide all the valid data. The Frame.plane function is close, but only works for planar data. I adapted it into this packed function:

#[inline]
pub fn packed<T: Sample>(&self) -> &[T] {
    if !self.is_packed() {
        panic!("data is not packed");
    }

    if !<T as Sample>::is_valid(self.format(), self.channels()) {
        panic!("unsupported type");
    }

    unsafe { slice::from_raw_parts((*self.as_ptr()).data[0] as *const T, self.samples() * self.channels() as usize) }
}

With that I'm able to play sound! 🎉 Here's a working example repo.

dceddia avatar Aug 28 '21 21:08 dceddia