Search code examples
c++cboostffmpegpipe

Piping ffmpeg output into ffplay stdin with boost


I'm trying to pipe the output of an ffmpeg process into an ffplay process (Sort of like a playback). My problem is the following: If I copy the output character by character (by character I mean char) it works correctly, other than it consuming a whole lot of cpu power. However when I try to pipe chunks into it (by using a buffer), ffplay for some reason doesn't even recognize the input.

bp::ipstream iso;
bp::ipstream ise;
bp::opstream in;
    
bp::child ffmpeg(bp::search_path("ffmpeg"), bp::args({"-loglevel", "quiet", "-f", "pulse", "-i", "default", "-f", "wav", "-bitexact", "-nostdin", "-"}), bp::std_out > iso, bp::std_err > ise);
bp::child ffplay(bp::search_path("ffplay"), bp::args({"-loglevel", "verbose", "-nodisp", "-f", "wav", "-i", "-"}), bp::std_in < in, bp::std_out > bp::null);

Here are the 2 code snippets for comparison:

Here it is copying char by char

while(ffmpeg.running()) {
    char c;
    c = iso.get();
    in << c;
}

And here it is copying with the help of a buffer

char buffer[1024];
while(ffmpeg.running()) {
    iso.get(buffer, 1024);
    in << buffer;
}

I can provide ffplay output if necessary, however I didn't see any errors or things like that.


Solution

  • Instaed of doing the chore manually, you can just attach the same pipes:

    bp::child ffmpeg(
        pg, bp::search_path(prod.cmd), bp::args(prod.args),
        bp::std_out > iso,
        bp::std_err > ise
    );
    
    bp::child ffplay(
        pg, bp::search_path(cons.cmd), bp::args(cons.args),
        (bp::std_in < iso)
        //, (bp::std_out> bp::null)
    );
    

    That will do what you expect. THis also implies you don't choose ipstream/opstream, because the role is both:

    //bp::pstream iso, ise, in;
    bp::pipe iso, ise, in;
    

    Both work, but you probably don't need the complexity of the stream objects. In fact you can use the async versions, and it will still behave the same (but give you more options/control for async running):

    enter image description here

    Live Online Demo

    With obviously replaced/simplified commands for the producer/consumer processes:

    Live On Coliru

    #include <boost/process.hpp>
    
    namespace bp = boost::process;
    using namespace std::chrono_literals;
    using boost::system::error_code;
    
    int main() {
        //bp::pstream iso, ise, in;
        bp::pipe iso, ise, in;
        //boost::asio::io_context io;
        //bp::async_pipe iso(io), ise(io), in(io);
        bp::group pg;
    
        struct { std::string cmd; std::vector<std::string> args; } tasks[] = {
    #if 1
             { "bash", { "-c", "echo 'hello world'"} },
             { "rev", {} },
    #else
             { "ffmpeg", {"-loglevel", "quiet", "-f", "pulse", "-i", "default", "-f", "wav", /*"-bitexact",*/ "-nostdin", "-"} },
             { "ffplay", {"-loglevel", "verbose", "-nodisp", "-f", "wav", "-i", "-"} },
    #endif
        };
    
        auto& [prod, cons] = tasks;
    
        bp::child ffmpeg(
            pg, bp::search_path(prod.cmd), bp::args(prod.args),
            bp::std_out > iso,
            bp::std_err > ise
        );
    
        bp::child ffplay(
            pg, bp::search_path(cons.cmd), bp::args(cons.args),
            (bp::std_in < iso)
            //, (bp::std_out> bp::null)
        );
    
        if (!pg.wait_for(4s))
            pg.terminate();
    }
    

    Prints the reverse of "hello world" as expected:

    dlrow olleh
    

    Doing The Nasty

    Just to show, here's how you could manually do the IO pump.

    Live On Coliru

    #include <boost/process.hpp>
    #include <boost/asio.hpp>
    #include <iostream>
    
    namespace bp = boost::process;
    using namespace std::chrono_literals;
    using boost::system::error_code;
    
    int main() {
        //bp::pstream iso, ise, in;
        //bp::pipe iso, ise, in;
        boost::asio::io_context io;
        bp::async_pipe iso(io), ise(io), in(io);
        bp::group pg;
    
        struct { std::string cmd; std::vector<std::string> args; } tasks[] = {
    #if 1
             { "bash", { "-c", "echo -e 'hello world\\nYOHOHO\\nMOHOHO\\n'"} },
             { "xxd", {} },
    #else
             { "ffmpeg", {"-loglevel", "quiet", "-f", "pulse", "-i", "default", "-f", "wav", /*"-bitexact",*/ "-nostdin", "-"} },
             { "ffplay", {"-loglevel", "verbose", "-nodisp", "-f", "wav", "-i", "-"} },
    #endif
        };
    
        auto& [prod, cons] = tasks;
    
        bp::child ffmpeg(
            io, pg, bp::search_path(prod.cmd), bp::args(prod.args),
            bp::std_out > iso,
            bp::std_err > ise
        );
    
        bp::child ffplay(
            io, pg, bp::search_path(cons.cmd), bp::args(cons.args),
            (bp::std_in < in)
            //, (bp::std_out> bp::null)
        );
    
        std::function<void()> stream_pump;
        stream_pump = [&, buf=std::vector<char>(1024)]() mutable {
          boost::asio::async_read(iso, boost::asio::buffer(buf), [&](error_code ec, size_t const nread) {
            bool const eof = (ec == boost::asio::error::eof);
            std::cerr << "nread: " << nread << " (eof:" << eof << ")\n";
            if (ec && !eof)
              std::cerr << "async_read: " << ec.message() << " (" << nread << ")\n";
            else
              boost::asio::async_write(in, boost::asio::buffer(buf, nread),
                [=,&in,&stream_pump](error_code ec, size_t /*nwritten*/) {
                  if (ec || eof) {
                    std::cerr << "Closing in\n";
                    in.close();
                  }
                  else
                    stream_pump(); // continue the pump
              });
          });
        };
    
        stream_pump(); // prime the pump
    
        auto nhandlers = io.run_for(4s);
        std::cerr << "Handlers executed: " << nhandlers << "\n";
        ffmpeg.terminate();
    
        if (ffplay.running())
            ffplay.wait_for(1s);
    
        if (ffplay.running())
            pg.terminate();
    }
    

    Prints

    nread: 27 (eof:1)
    Closing in
    Handlers executed: 6
    

    And the consumer child process stdout contains

    00000000: 6865 6c6c 6f20 776f 726c 640a 594f 484f  hello world.YOHO
    00000010: 484f 0a4d 4f48 4f48 4f0a 0a              HO.MOHOHO..