0

I'm creating a C-program under Linux, and I want to catch FFMpeg's output-data and forward it. To do so, I'm calling FFMpeg via (an implementation of) popen2. It all is working fine: I'm able to get FFMpeg's data out of the pipe via read(). Things start to get awkward if FFMpeg stops working. read() seems to be blocking, which is the expected behaviour, but it never exits when the pipe is closed.

I learned that, for read() to break and detect an EOF, the pipe should be closed on both ends, so when I detect that FFMpeg is crashed or killed, I close the reading-end of my pipe. I'm assuming that, since FFMpeg is closed, it disconnects its end of the pipe.

My assumption seems to be wrong: when I close the reading-end of the pipe by using close(), the program still seems to be hanging in read().

Would somebody be able to explain me what's wrong with my assumption and point me in the right direction so that I can properly detect when the program that I'm piping to or from (in this case FFMpeg) has stopped sending data?

The relevant code:

#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);
}

This code is able to start the program, receive it's data, detect the execvp'd program crash/kill, but it never gets out of read() when the execvp'd program is killed.

I've also tried to read the pipe as a filestream;

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

..which gives the same result: it reads the data but it doesn't get out of fread().

Any help would be highly appreciated!

Thanks in advance!

ZKJohan
  • 11
  • 2
  • 1
    The child still has `p_stdin[READ]` and `p_stdout[WRITE]` open when it execs the command. Perhaps you should close them after the `dup2` calls. – Ian Abbott Oct 18 '22 at 16:36
  • Your current while loop is ineffective. There are 3 possible outcomes: error (`read` returns a negative number), data received (`read` returns a positive number), and end-of-stream (`read` returns zero, but possibly only once and after that it's an error with negative return). You handle zero by exiting the inner `while` loop but that `while(1)` runs forever, and you have no error-checking at all. – Ben Voigt Oct 18 '22 at 16:44
  • Also there's no reason to call `malloc` with a small constant. Just use an automatic array declaration (and possibly store a pointer in a global variable). – Ben Voigt Oct 18 '22 at 16:45
  • 1
    You also need to read [**Why is “while( !feof(file) )” always wrong?**](https://stackoverflow.com/questions/5431941/why-is-while-feoffile-always-wrong) – Andrew Henle Oct 18 '22 at 17:02

1 Answers1

0

Thanks all for the comments. Indeed, the while-loops are ineffective, but I've isolated this code from a bigger project so that I could troubleshoot this issue more effectively. That's also why the malloc() is still there, and indeed it is very pointless.

As Ian stated, it turns out to be as simple as closing the p_stdin[READ] and p_stdout[WRITE]. It all makes sense now: I figured that I didn't need to close them since the execvp'd program is using them but that's the whole thing: the execvp'd program (child) is using them, and not me (the parent).

popen2_2() now looks like this:

{

    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
    {
        close(p_stdin[READ]);
        *infp = p_stdin[WRITE];
    }
    
    if (outfp == NULL)
    {
        close(p_stdout[WRITE]);
        close(p_stdout[READ]);
    }
    else
    {
        *outfp = p_stdout[READ];
        close(p_stdout[WRITE]);
    }
    
    return pid;
} 

I can happily continue coding now! Thanks all :)

ZKJohan
  • 11
  • 2