How to read from a child process stdout pipe without blocking?

Windows 11:

let mut ffmpeg = Command::new(r".\ffmpeg")
            .args([
                "-f",
                "gdigrab",
                "-framerate",
                "1",
                "-i",
                "desktop",
                "-c:v",
                "libx264",
                "-crf",
                "32",
                "-pix_fmt",
                "yuv420p",
                &output_path.to_str().unwrap(),
                "abc.mp4",
                "-f",
                "rawvideo",
                "-",
            ])
            .stdin(Stdio::null())
            .stdout(Stdio::piped())
            .stderr(Stdio::inherit())
            .spawn()
            .unwrap();
[vost#1:0/rawvideo @ 000002353b621880] Error submitting a packet to the muxer: Invalid argument.50    
    Last message repeated 1 times
[out#1/rawvideo @ 000002353b620ec0] Error muxing a packet
[out#1/rawvideo @ 000002353b620ec0] Task finished with error code: -22 (Invalid argument)
[out#1/rawvideo @ 000002353b620ec0] Terminating thread with return code -22 (Invalid argument)
[out#0/mp4 @ 000002353b616900] video:327KiB audio:0KiB subtitle:0KiB other streams:0KiB global headers:0KiB muxing overhead: 0.254129%
[out#1/rawvideo @ 000002353b620ec0] Error writing trailer: Invalid argument
[out#1/rawvideo @ 000002353b620ec0] Error closing file: Invalid argument

This program encounters an error, but it works fine when I replace stdio::piped() with a file handle. I suspect the pipe gets full and blocks, causing issues with ffmpeg.

I already tried to use BufReader and mpsc, but still failed.

let ffmpeg_stdout = ffmpeg.stdout.take().unwarp();
let mut buf_reader = BufReader::new(ffmpeg_stdout);
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
    let mut buffer = vec![0u8; 8192];
    loop {
        match buf_reader.read(&mut buffer) {
            Ok(0) => break,
            Ok(bytes_read) => {
                if tx.send(buffer[..bytes_read].to_vec()).is_err() {
                    break;
                }
            }
            Err(_) => break,
        }
    }

I also tried to use duct crate

let mut cmd = cmd(r"ffmpeg", ffmpeg_args).reader().unwrap();
loop {
    let mut buf = [0u8; 8196];
    cmd.read_exact(&mut buf);
}

The process's memory usage keeps increasing, which suggests that the contents of the pipe are not being cleared/taken.

I have no idea. Please tell me how to solve this, thanks.

There is so much wrong with this code:

  1. It's unwrap() not unwarp()
  2. Why do you manually try to buffer a BufReader?
  3. You send through a mpsc channel, but never receive the data, which is why the channel's buffer is filling up.

I had more trouble with getting ffmpeg to reliably output the right data to stdout, rather than with reading it in Rust. The working code below is a bit simpler than yours, and uses yuv4mpegpipe instead of rawvideo:

use std::process::{Command, Stdio};
use std::io::Read;

fn main() {
    let args = &["-f", "gdigrab", "-framerate", "1", "-i", "desktop", "-pix_fmt", "yuv420p", "-f", "yuv4mpegpipe", "-"];
    let mut cmd = Command::new("ffmpeg.exe").args(args).stdout(Stdio::piped()).spawn().unwrap();
    let mut stdout = cmd.stdout.take().unwrap();
    let mut buf = vec![0u8; 8192];
    loop {
        match stdout.read(&mut buf) {
            Err(e) => { eprintln!("{e}"); break; }
            Ok(0) => break,
            Ok(_) => print!("."),
        }
    }
    println!("{}", cmd.wait().unwrap());
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.