Calling FFMPEG with Boost.Process

506 Views Asked by At

I'm trying to call FFMPEG from my C++ process to stream video from an IP camera. The FFMPEG command I use is ffmpeg.exe -rtsp_transport tcp -i rtsp://10.0.1.21/ONVIF/MediaInput?profile=1_def_profile4 -f image2pipe -pix_fmt rgb24 -vcodec rawvideo -r 15 -. I've verified this command in the command prompt and it does start a video stream and dumps the frames to stdout. I've also written similar code in Python and it works.

This is the code I'm using to call FFMPEG with the arguments from the previous paragraph in C++ and read the individual frames from stdout.

bool build_ffmpeg_arguments(const std::string &uri, std::vector<std::string> &args)
{
    args.push_back("-rtsp_transport");
    args.push_back("tcp");
    args.push_back("-i");
    args.push_back(uri);
    args.push_back("-f");
    args.push_back("image2pipe");
    args.push_back("-pix_fmt");
    args.push_back("rgb24");
    args.push_back("-vcodec");
    args.push_back("rawvideo");
    args.push_back("-r");
    args.push_back("15");
    args.push_back("-");

    return true;
}


boost::process::child start_ffmpeg(const std::string &uri,
                                   const std::string &ffmpeg_path = "c:\\Tools\\ffmpeg.exe")
{
    std::vector<std::string> args;
    build_ffmpeg_arguments(uri, args);

    boost::process::context ctx;
    ctx.stdout_behavior = boost::process::capture_stream();
    ctx.stderr_behavior = boost::process::capture_stream();

    return boost::process::launch(ffmpeg_path, args, ctx);
}

bool read_frame(boost::process::pistream &is, int frame_size, std::vector<char> &frame_bytes)
{
    char *buffer = new char[frame_size];

    frame_bytes.clear();
    is.read(buffer, frame_size);
    int bytes_read = is.gcount();
    frame_bytes.assign(buffer, buffer + bytes_read);

//    std::cout << "Is Bad: " << is.bad() << std::endl;
//    std::cout << "Is EOF: " << is.eof() << std::endl;
//    std::cout << "gcount: " << bytes_read << std::endl;

    delete[] buffer;
    if(is.bad() || is.eof() || bytes_read < frame_size)
    {
        //We read in gunk, skip this time.
        is.clear();
        return false;
    }
    else
    {
        return true;
    }
}


//This is where the code is invoked.
BOOST_AUTO_TEST_CASE(test_ffmpeg_stream)
{
    std::string uri = "rtsp://10.0.1.21/ONVIF/MediaInput?profile=1_def_profile4";
    int width = 320;
    int height = 240;
    int bpp = 3;
    int bytes_expected = width * height * 3;
    boost::process::child c = start_ffmpeg(uri);
    boost::process::pistream &is = c.get_stdout();
    boost::process::pistream &err = c.get_stderr();

    std::vector<char> buffer;
    bool result = read_frame(is, bytes_expected, buffer);
    //BOOST_CHECK_EQUAL(true, result);
    std::cout << "Buffer size: " << buffer.size() << std::endl;

    std::string line;
    while (std::getline(err, line))
        std::cout << line << std::endl;
}

The output from stderr suggests that the parameters could be passed in wrong.

ffmpeg version 2.8.3 Copyright (c) 2000-2015 the FFmpeg developers
  built with gcc 5.2.0 (GCC)
  configuration: --enable-gpl --enable-version3 --disable-w32threads --enable-av
isynth --enable-bzlib --enable-fontconfig --enable-frei0r --enable-gnutls --enab
le-iconv --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --
enable-libdcadec --enable-libfreetype --enable-libgme --enable-libgsm --enable-l
ibilbc --enable-libmodplug --enable-libmp3lame --enable-libopencore-amrnb --enab
le-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-librtmp --en
able-libschroedinger --enable-libsoxr --enable-libspeex --enable-libtheora --ena
ble-libtwolame --enable-libvidstab --enable-libvo-aacenc --enable-libvo-amrwbenc
 --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enabl
e-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-lzma --ena
ble-decklink --enable-zlib
  libavutil      54. 31.100 / 54. 31.100
  libavcodec     56. 60.100 / 56. 60.100
  libavformat    56. 40.101 / 56. 40.101
  libavdevice    56.  4.100 / 56.  4.100
  libavfilter     5. 40.101 /  5. 40.101
  libswscale      3.  1.101 /  3.  1.101
  libswresample   1.  2.101 /  1.  2.101
  libpostproc    53.  3.100 / 53.  3.100
rtsp://10.0.1.21/ONVIF/MediaInput?profile=1_def_profile4: Unknown error

Is there a way of showing the full command line with arguments that boost::process::launch is calling? Is there anything obvious that I'm doing wrong with boost::process?

Update:

Suspecting that it could be the command line arguments being passed in wrong, I've created a dummy executable that prints out the command line arguments it receives. It's a drop-in replacement for ffmpeg.exe purely so that I can see what command lines are being passed. The command line I'm getting is -rtsp_transport tcp -i rtsp://10.0.1.21/ONVIF/MediaInput?profile=1_def_profile4 -f image2pipe -pix_fmt rgb24 -vcodec rawvideo -r 15 -. Manually calling ffmpeg with that command line works as expected. Yet somehow it doesn't work when launched via boost::process.

** Solution **

It looks like I need to set the the environment field of the context. No idea why that fixes the problem, but it does.

boost::process::context ctx;
ctx.environment = boost::process::self::get_environment();
ctx.stdout_behavior = boost::process::capture_stream();
ctx.stderr_behavior = boost::process::capture_stream();
0

There are 0 best solutions below