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.
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):
With obviously replaced/simplified commands for the producer/consumer processes:
#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
Just to show, here's how you could manually do the IO pump.
#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..