Search code examples
cshellffmpeg

How to grab ffmpeg's output as binary and write it to a file on the fly such that video players can play it in real time?


I want to stream a RTSP-streaming device to a video player such as VLC but the catch is that, in between, the binary data needs to go through a custom high-speed serial link. I control what goes in this link from a C++ program.

I was happily surprised to see that the following line allowed me to watch the RTSP stream by just opening "out.bin" from VLC which was a good lead for fast and efficient binary transmission of the stream:

ffmpeg -i "rtsp://admin:[email protected]:554/h264Preview_01_main" -c:v copy -c:a copy -f mpegts out.bin

I already wondered how ffmpeg manages to allow VLC to read that file, while itself writing to it at the same time. Turns out I was right to wonder, see below.

I told myself I could make this command pipe its output to the standard output, and then in turn pipe the standard output to a file that I can read, (later, slice it, transmit the chunks and reconstruct it) and then write to an output file. However, this does not work:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define BUFSIZE 188 //MPEG-TS packet size

int main()
{
    char *cmd = (char*)"ffmpeg -i \"rtsp://admin:[email protected]:554/h264Preview_01_main\" -c:v copy -c:a copy -f mpegts pipe:1 -loglevel quiet";
    char buf[BUFSIZE];
    FILE *ptr, *file;

    file = fopen("./out.bin", "w");

    if (!file)
    {
        printf("Failed to open output file for writing, aborting");
       abort();
    }

    if ((ptr = popen(cmd, "r")) != NULL) {
       printf("Writing RTSP stream to file...");

       while (!kbhit())
       {
            if(fread(&buf, sizeof(char), BUFSIZE, ptr) != 0)
            {
               fwrite(buf, sizeof(char), BUFSIZE, file);
            }
            else
            {
                printf("No data\n");
            }
       }
       pclose(ptr);
    }
    else
    {
        printf("Failed to open pipe from ffmpeg command, aborting");
    }

    printf("End of program");

    fclose(file);
    return 0;
}

Since VLC says "your input can't be opened" - whereas this works just fine:

ffmpeg -i "rtsp://admin:[email protected]:554/h264Preview_01_main" -c:v copy -c:a copy -f mpegts pipe:1 -loglevel quiet > out.bin

This is what ends up in the file after I close the program, versus the result of the command immediately above: enter image description here

The file is always ~2kB regardless of how long I run the program: "No data" is shown repeatedly in the console output.

Why doesn't it work? If it is not just a bug, how can I grab the stream as binary at some point, and write it at the end to a file that VLC can read?

Update

New code after applying Craig Estey's fix to my stupid mistake. The end result is that the MPEG-TS frames don't seem to shift anymore but the file writing stops partway into one of the first few frames (the console only shows a few ">" symbols and then stays silent, c.f. code).

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#define BUFSIZE 188                     // MPEG-TS packet size

int
main()
{
    char *cmd = (char *) "ffmpeg -i \"rtsp://127.0.0.1:8554/test.sdp\" -c:v copy -c:a copy -f mpegts pipe:1 -loglevel quiet";
    char buf[BUFSIZE];
    FILE *ptr,
    *file;

    file = fopen("./out.ts", "w");

    if (!file) {
        printf("Failed to open output file for writing, aborting");
        abort();
    }

    if ((ptr = popen(cmd, "r")) != NULL) {
        printf("Writing RTSP stream to file...");

        while(!kbhit()) {
            ssize_t rlen = fread(&buf, sizeof(char), BUFSIZE, ptr);
            if(rlen != 0)
            {
                printf(">");
                fwrite(buf, sizeof(char), rlen, file);
                fflush(file);
            }
        }
        pclose(ptr);
    }
    else {
        printf("Failed to open pipe from ffmpeg command, aborting");
    }

    printf("End of program");

    fclose(file);
    return 0;
}

This can be tested on any computer with VLC and a webcam: open VLC, open capture device, capture mode directshow, (switch "play" for "stream"), next, display locally, select RTSP, Add, path=/test.sdp, next, transcoding=H264+MP3 (TS), replace rtsp://:8554/ with rtsp://127.0.0.1:8554/ in the generated command line, stream.

To test that streaming is ok, you can just open a command terminal and enter "ffmpeg -i "rtsp://127.0.0.1:8554/test.sdp" -c:v copy -c:a copy -f mpegts pipe:1 -loglevel quiet", the terminal should fill up with binary data.

To test the program, just compile, run, and open out.ts after the program has run.


Solution

  • Since #include <conio.h> is Windows only, we may conclude that you are using Windows.
    (The rest of the code looks like Linux, so MinGW compiler is probably used...)

    In Linux all files are binary files, but in Windows, the default file format is text file.
    I guess that the execution ends on the first EOF character (the issue may also be related to "new line" characters).


    We have to open the files as binary files:

    • Replace file = fopen("./out.bin", "w"); with:

       file = fopen("./out.bin", "wb");
      
    • Replace ptr = popen(cmd, "r") with:

       ptr = popen(cmd, "rb")
      

    Corrected code:

    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    
    #define BUFSIZE 188                     // MPEG-TS packet size
    
    int main()
    {
        const char *cmd = "ffmpeg -i \"rtsp://127.0.0.1:8554/test.sdp\" -c:v copy -c:a copy -f mpegts pipe:1 -loglevel quiet";
        char buf[BUFSIZE];
        FILE *ptr,
        *file;
    
        file = fopen("./out.ts", "wb");  //Open file as binary file
    
        if (!file) {
            printf("Failed to open output file for writing, aborting");
            abort();
        }
    
        if ((ptr = popen(cmd, "rb")) != NULL) {  //Open pipe as binary file
            printf("Writing RTSP stream to file...");
    
            while(!kbhit()) {
                ssize_t rlen = fread(&buf, sizeof(char), BUFSIZE, ptr);
                if(rlen != 0)
                {
                    printf(">");
                    fwrite(buf, sizeof(char), rlen, file);
                    fflush(file);
                }
            }
            pclose(ptr);
        }
        else {
            printf("Failed to open pipe from ffmpeg command, aborting");
        }
    
        printf("End of program");
    
        fclose(file);
        return 0;
    }