You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下C程序检测FFmpeg管道EOF:read阻塞无法退出的求助

问题

在Linux环境下编写C程序,需要捕获FFmpeg的输出数据并转发。通过自定义的popen2实现调用FFmpeg,目前可以通过read()从管道获取数据,但当FFmpeg停止运行时,read()会一直阻塞,无法检测到EOF。

已知read()检测EOF需要管道两端都关闭,因此当检测到FFmpeg崩溃或被终止时,关闭了管道的读端,但程序仍卡在read()中。

请问假设哪里有误?如何正确检测管道另一端(如FFmpeg)停止发送数据?

相关代码

#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "helper.h"

#define READ 0
#define WRITE 1


FILE *sink_stream;
int ffmpeg_sink;

pid_t popen2_2(const char **command, int *infp, int *outfp);
void* pthread_sink_ffmpeg(void *arg);

pid_t popen2_2(const char **command, int *infp, int *outfp)
{

    int p_stdin[2], p_stdout[2];
    pid_t pid;
    
    int devNull = open("/dev/null", O_WRONLY);

    if (pipe(p_stdin) != 0 || pipe(p_stdout) != 0)
        return -1;

    pid = fork();

    if (pid < 0)
        return pid;
    else if (pid == 0) //first fork
    {
        close(p_stdin[WRITE]);
        dup2(p_stdin[READ], READ); 
        
        close(p_stdout[READ]);
        dup2(p_stdout[WRITE], WRITE);
        
        //dup2(devNull,2); //pipes stderr to /dev/null....

        execvp(*command, command);
        _exit(1);
    }

    if (infp == NULL)
    {
        close(p_stdin[WRITE]);
        close(p_stdin[READ]);
    }
    else
    {
        *infp = p_stdin[WRITE];
    }
    
    if (outfp == NULL)
    {
        close(p_stdout[WRITE]);
        close(p_stdout[READ]);
    }
    else
    {
        *outfp = p_stdout[READ];
    }
    
    return pid;
}

void* pthread_sink_ffmpeg(void *arg)
{
    char *pbuffer;

    int length, res;
    
    pbuffer = malloc(4096);

    while(1)
    {
        /* Wait for input */    
        dbgfprintf("Waiting for input...");
                
        while( (length = read(ffmpeg_sink, pbuffer, 1024)) )
        {
            fprintf(stderr, "Read [ %d ]...\r", length);
        }

        /* RIP */
        dbgfprintf("Done for now..");
        
    }

    free(pbuffer);

}

int main( void )
{
    int wstatus;
    pid_t child_pid, w;
    pthread_t t_ffmpeg_source;
        
    const char *command_ffmpeg[] = {"ffmpeg", 
        "-hide_banner",
        "-re",
        "-i", "http://relay.ah.fm/192k",
        "-c:a", "libfdk_aac",
        "-profile:a", "aac_he_v2",
        "-b:a", "128k",
        "-f", "adts",
        "pipe:1",
        NULL};
        
    child_pid = popen2_2(command_ffmpeg, NULL, &ffmpeg_sink);
    dbgfprintf("Started ffmpeg with pid [ %d ]...", child_pid);
    
    pthread_create(&t_ffmpeg_source, NULL, pthread_sink_ffmpeg, NULL);
    
    do {
       w = waitpid(child_pid, &wstatus, WUNTRACED | WCONTINUED);
    } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); 
    
    dbgfprintf("ffmpeg terminated...");
    close(ffmpeg_sink);
    
    pthread_join(t_ffmpeg_source, NULL);
}

尝试过的另一种读取方式

sink_stream = fdopen(ffmpeg_sink, "r");
while( !feof(sink_stream) )
{
    length = fread(pbuffer, 1, 4096, sink_stream);
        
    fprintf(stderr, "Read [ %d ]...\r", length);
}

结果相同:能读取数据,但FFmpeg终止后无法退出fread()。


解答

你的假设错误点

  1. 关闭读端无法唤醒阻塞的read():当read()已经处于阻塞状态时,关闭当前进程的读端文件描述符不会让read()返回——read()持有的是文件描述符的引用,关闭其他引用不影响正在进行的系统调用。只有当管道的写端全部关闭时,read()才会返回0(EOF)。

  2. FFmpeg进程终止后,写端可能未完全关闭:在你的popen2_2实现中,父进程没有关闭管道写端的多余引用!当outfp不为NULL时,父进程只保留了p_stdout[READ],但p_stdout[WRITE]并没有关闭——这意味着管道的写端还有父进程的一个引用存在,即使FFmpeg子进程终止,写端也没有完全关闭,所以read()会一直阻塞。

正确的解决步骤

  1. 修复popen2_2中的管道引用泄漏
    在popen2_2函数中,当outfp不为NULL时,必须关闭p_stdout[WRITE],因为这个写端只属于子进程,父进程不需要持有:

    if (outfp == NULL)
    {
        close(p_stdout[WRITE]);
        close(p_stdout[READ]);
    }
    else
    {
        *outfp = p_stdout[READ];
        close(p_stdout[WRITE]); // 新增这一行,关闭父进程持有的写端引用
    }
    
  2. 修正read()的循环逻辑
    原代码的循环条件错误,read()返回0表示EOF,返回-1表示错误(比如被信号中断),正确的处理逻辑应该是:

    while(1)
    {
        dbgfprintf("Waiting for input...");
        length = read(ffmpeg_sink, pbuffer, 1024);
        if (length == 0) {
            // EOF,子进程已终止且写端关闭
            dbgfprintf("Got EOF, exiting read loop..");
            break;
        } else if (length == -1) {
            // 处理错误,EINTR表示被信号中断,可重试;其他错误直接退出
            if (errno == EINTR) continue;
            perror("read failed");
            break;
        }
        fprintf(stderr, "Read [ %d ]...\r", length);
    }
    
  3. 可选:用信号或非阻塞IO优化响应速度
    如果需要在子进程终止时立刻唤醒阻塞的read(),可以:

    • 给读端文件描述符设置非阻塞标志,结合select/poll监听可读事件
    • 在main函数检测到子进程终止后,调用pthread_kill给读取线程发送信号,让read()因信号中断返回,再处理退出逻辑

内容的提问来源于stack exchange,提问作者ZKJohan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 09:35:17