I use the subprocess.h library to try to take over ffmpeg's output and input. Here's the link to the library: https://github.com/sheredom/subprocess.h
I did a test where the program calls ffmpeg to transcode a video and output process information, and then the user can press Enter to send 'q' to ffmpeg to stop the transcoding. Its command is
ffmpeg -y -i input.mp4 -c:v libx264 out.mp4
source code
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include "subprocess.h"
std::atomic<bool> ffmpeg_running(true);
// Background thread: read and print FFmpeg stdout/stderr
void output_reader(struct subprocess_s* process) {
char buffer[1024];
while (ffmpeg_running.load()) {
unsigned int bytes_read = 0;
// Read stderr (FFmpeg progress usually goes here)
bytes_read = subprocess_read_stderr(process, buffer, sizeof(buffer) - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
std::cerr << buffer << std::flush;
}
if (!subprocess_alive(process)) {
break;
}
// Small sleep to avoid busy-waiting
std::this_thread::sleep_for(std::chrono::milliseconds(30));
}
}
int main() {
// Fixed FFmpeg command
const char* command_line[] = {
"ffmpeg",
"-y",
"-i", "input.mp4",
"-c:v", "libx264",
"out.mp4",
NULL
};
struct subprocess_s process;
// Enable async option so we can read output while the process runs
int result = subprocess_create(command_line, subprocess_option_enable_async | subprocess_option_inherit_environment, &process);
if (result != 0) {
std::cerr << "Failed to start FFmpeg, error code: " << result << std::endl;
return 1;
}
// Start the background reader thread
std::thread reader_thread(output_reader, &process);
std::cout << "FFmpeg is transcoding... Press Enter to send 'q' to ffmpeg and quit gracefully.\n";
// Wait for user to press Enter
std::cin.get();
// Send 'q' to FFmpeg's stdin
FILE* p_stdin = subprocess_stdin(&process);
if (p_stdin) {
fputs("q", p_stdin);
fflush(p_stdin);
}
// Wait for FFmpeg to exit
int process_return = 0;
subprocess_join(&process, &process_return);
// Signal the reader thread to stop
ffmpeg_running.store(false);
// Join the reader thread
if (reader_thread.joinable()) {
reader_thread.join();
}
subprocess_destroy(&process);
std::cout << "\nFFmpeg exited with return code: " << process_return << std::endl;
return 0;
}Output after the code is compiled and run
FFmpeg is transcoding... Press Enter to send 'q' to ffmpeg and quit gracefully.
ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.2.1 (GCC) 20200122
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfree
...
[libx264 @ 06858580] i16 v,h,dc,p: 25% 52% 15% 8%
[libx264 @ 06858580] i8 v,h,dc,ddl,ddr,vr,hd,vl,hu: 17% 24% 54% 1% 1% 0% 1% 0% 1%
[libx264 @ 06858580] i4 v,h,dc,ddl,ddr,vr,hd,vl,hu: 31% 36% 17% 2% 3% 3% 3% 2
FFmpeg exited with return code: 0
Above is the final correct code, and before getting it, I made a few mistakes.
1.At first, I tried reading stdout, but ffmpeg doesn't output progress to stdout—it sends all information to stderr. So at first, I didn't get the correct output.
// Read stdout incorrectly
bytes_read = subprocess_read_stdout(process, buffer, sizeof(buffer) - 1);
2.At first, I was using this way to create a process
subprocess_create(command_line, subprocess_option_enable_async, &process);
Without this flag to inherit environment variables, ffmpeg initially output a message.
Error in GnuTLS initialization: Failed to acquire random data.
Although ffmpeg keeps processing, it didn't run correctly.
3.At first, when ffmpeg finishes transcoding without anyone stopping it, the thread loop won't exit and gets stuck in an infinite loop. So you need to check the process status in the loop to decide whether it needs to end on its own.
if (!subprocess_alive(process)) {
break;
}
About why the ffmpeg output is read in a separate thread, this project's introduction says
Reading Asynchronously
If you want to be able to read from a process before calling subprocess_join on it, you cannot use subprocess_stdout or subprocess_stderr because the various operating systems that this library supports do not allow for this.
Instead you first have to call subprocess_create and specify the subprocess_option_enable_async option - which enables asynchronous reading.
Then you must use the subprocess_read_stdout and subprocess_read_stderr helper functions to do any reading from either pipe. Note that these calls may block if there isn't any data ready to be read.
According to the introduction, the FILE handle returned by subprocess_stdin() can be written to while the process is running, so sending 'q' to ffmpeg is fine. But subprocess_stdout() / subprocess_stderr() can't be used before subprocess_join(). If you need to read the process output before the process ends, you have to use subprocess_read_stdout/stderr together with subprocess_option_enable_async to read it.
But these two async functions can still block when there's no data to read. The point of putting them in a separate reading thread is to isolate that blocking in the background, so the main thread can respond to Enter or call subprocess_stdin to send commands anytime.