【问题标题】:FFmpeg - feed raw frames via pipe - FFmpeg does not detect pipe closureFFmpeg - 通过管道馈送原始帧 - FFmpeg 未检测到管道关闭
【发布时间】:2018-02-02 19:39:20
【问题描述】:

我试图在 Windows 中遵循 C++ 中的这些示例。 Phyton ExampleC# Example

我有一个应用程序可以生成应使用 FFmpeg 编码的原始帧。 原始帧通过 IPC 管道传输到 FFmpegs STDIN。这按预期工作,FFmpeg 甚至显示当前可用的帧数。

当我们完成发送帧时会出现问题。当我关闭管道的写入端时,我希望 FFmpeg 能够检测到这一点,完成并输出视频。但这不会发生。 FFmpeg 保持打开状态,似乎在等待更多数据。

我在 VisualStudio 中做了一个小测试项目。

#include "stdafx.h" 
//// stdafx.h 
//#include "targetver.h"
//#include <stdio.h>
//#include <tchar.h>
//#include <iostream>

#include "Windows.h"
#include <cstdlib>

using namespace std;

bool WritePipe(void* WritePipe, const UINT8 *const Buffer, const UINT32 Length)
{
    if (WritePipe == nullptr || Buffer == nullptr || Length == 0)
    {
        cout << __FUNCTION__ << ": Some input is useless";
        return false;
    }

    // Write to pipe
    UINT32 BytesWritten = 0;
    UINT8 newline = '\n';
    bool bIsWritten = WriteFile(WritePipe, Buffer, Length, (::DWORD*)&BytesWritten, nullptr);
    cout << __FUNCTION__ << " Bytes written to pipe " << BytesWritten << endl;
    //bIsWritten = WriteFile(WritePipe, &newline, 1, (::DWORD*)&BytesWritten, nullptr); // Do we need this? Actually this should destroy the image.

    FlushFileBuffers(WritePipe); // Do we need this?

    return bIsWritten;
}

#define PIXEL 80 // must be multiple of 8. Otherwise we get warning: Bytes are not aligned

int main()
{
    HANDLE PipeWriteEnd = nullptr;
    HANDLE PipeReadEnd = nullptr;
    {
        // create us a pipe for inter process communication
        SECURITY_ATTRIBUTES Attr = { sizeof(SECURITY_ATTRIBUTES), NULL, true };
        if (!CreatePipe(&PipeReadEnd, &PipeWriteEnd, &Attr, 0))
        {
            cout << "Could not create pipes" << ::GetLastError() << endl;
            system("Pause");
            return 0;
        }
    }

    // Setup the variables needed for CreateProcess
    // initialize process attributes
    SECURITY_ATTRIBUTES Attr;
    Attr.nLength = sizeof(SECURITY_ATTRIBUTES);
    Attr.lpSecurityDescriptor = NULL;
    Attr.bInheritHandle = true;

    // initialize process creation flags
    UINT32 CreateFlags = NORMAL_PRIORITY_CLASS;
    CreateFlags |= CREATE_NEW_CONSOLE;

    // initialize window flags
    UINT32 dwFlags = 0;
    UINT16 ShowWindowFlags = SW_HIDE;

    if (PipeWriteEnd != nullptr || PipeReadEnd != nullptr)
    {
        dwFlags |= STARTF_USESTDHANDLES;
    }

    // initialize startup info
    STARTUPINFOA StartupInfo = {
        sizeof(STARTUPINFO),
        NULL, NULL, NULL,
        (::DWORD)CW_USEDEFAULT,
        (::DWORD)CW_USEDEFAULT,
        (::DWORD)CW_USEDEFAULT,
        (::DWORD)CW_USEDEFAULT,
        (::DWORD)0, (::DWORD)0, (::DWORD)0,
        (::DWORD)dwFlags,
        ShowWindowFlags,
        0, NULL,
        HANDLE(PipeReadEnd),
        HANDLE(nullptr),
        HANDLE(nullptr)
    };

    LPSTR ffmpegURL = "\"PATHTOFFMPEGEXE\" -y -loglevel verbose -f rawvideo -vcodec rawvideo -framerate 1 -video_size 80x80 -pixel_format rgb24 -i - -vcodec mjpeg -framerate 1/4 -an \"OUTPUTDIRECTORY\"";

    // Finally create the process
    PROCESS_INFORMATION ProcInfo;
    if (!CreateProcessA(NULL, ffmpegURL, &Attr, &Attr, true, (::DWORD)CreateFlags, NULL, NULL, &StartupInfo, &ProcInfo))
    {
        cout << "CreateProcess failed " << ::GetLastError() << endl;
    }
    //CloseHandle(ProcInfo.hThread);


        // Create images and write to pipe
#define MYARRAYSIZE (PIXEL*PIXEL*3) // each pixel has 3 bytes

    UINT8* Bitmap = new UINT8[MYARRAYSIZE];

    for (INT32 outerLoopIndex = 9; outerLoopIndex >= 0; --outerLoopIndex)   // frame loop
    {
        for (INT32 innerLoopIndex = MYARRAYSIZE - 1; innerLoopIndex >= 0; --innerLoopIndex) // create the pixels for each frame
        {
            Bitmap[innerLoopIndex] = (UINT8)(outerLoopIndex * 20); // some gray color
        }
        system("pause");
        if (!WritePipe(PipeWriteEnd, Bitmap, MYARRAYSIZE))
        {
            cout << "Failed writing to pipe" << endl;
        }
    }

    // Done sending images. Tell the other process. IS THIS NEEDED? HOW TO TELL FFmpeg WE ARE DONE?
    //UINT8 endOfFile = 0xFF; // EOF = -1 == 1111 1111 for uint8
    //if (!WritePipe(PipeWriteEnd, &endOfFile, 1))
    //{
    //  cout << "Failed writing to pipe" << endl;
    //}
    //FlushFileBuffers(PipeReadEnd); // Do we need this?

    delete Bitmap;


    system("pause");

    // clean stuff up
    FlushFileBuffers(PipeWriteEnd); // Do we need this?

    if (PipeWriteEnd != NULL && PipeWriteEnd != INVALID_HANDLE_VALUE)
    {
        CloseHandle(PipeWriteEnd);
    }

    // We do not want to destroy the read end of the pipe? Should not as that belongs to FFmpeg
    //if (PipeReadEnd != NULL && PipeReadEnd != INVALID_HANDLE_VALUE)
    //{
    //  ::CloseHandle(PipeReadEnd);
    //}


    return 0;

}

这里是FFmpeg的输出

ffmpeg version 3.4.1 Copyright (c) 2000-2017 the FFmpeg developers
  built with gcc 7.2.0 (GCC)
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-bzlib --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-cuda --enable-cuvid --enable-d3d11va --enable-nvenc --enable-dxva2 --enable-avisynth --enable-libmfx
  libavutil      55. 78.100 / 55. 78.100
  libavcodec     57.107.100 / 57.107.100
  libavformat    57. 83.100 / 57. 83.100
  libavdevice    57. 10.100 / 57. 10.100
  libavfilter     6.107.100 /  6.107.100
  libswscale      4.  8.100 /  4.  8.100
  libswresample   2.  9.100 /  2.  9.100
  libpostproc    54.  7.100 / 54.  7.100
[rawvideo @ 00000221ff992120] max_analyze_duration 5000000 reached at 5000000 microseconds st:0
Input #0, rawvideo, from 'pipe:':
  Duration: N/A, start: 0.000000, bitrate: 153 kb/s
    Stream #0:0: Video: rawvideo, 1 reference frame (RGB[24] / 0x18424752), rgb24, 80x80, 153 kb/s, 1 fps, 1 tbr, 1 tbn, 1 tbc
Stream mapping:
  Stream #0:0 -> #0:0 (rawvideo (native) -> mjpeg (native))
[graph 0 input from stream 0:0 @ 00000221ff999c20] w:80 h:80 pixfmt:rgb24 tb:1/1 fr:1/1 sar:0/1 sws_param:flags=2
[auto_scaler_0 @ 00000221ffa071a0] w:iw h:ih flags:'bicubic' interl:0
[format @ 00000221ffa04e20] auto-inserting filter 'auto_scaler_0' between the filter 'Parsed_null_0' and the filter 'format'
[swscaler @ 00000221ffa0a780] deprecated pixel format used, make sure you did set range correctly
[auto_scaler_0 @ 00000221ffa071a0] w:80 h:80 fmt:rgb24 sar:0/1 -> w:80 h:80 fmt:yuvj444p sar:0/1 flags:0x4
Output #0, mp4, to 'c:/users/vr3/Documents/Guenni/sometest.mp4':
  Metadata:
    encoder         : Lavf57.83.100
    Stream #0:0: Video: mjpeg, 1 reference frame (mp4v / 0x7634706D), yuvj444p(pc), 80x80, q=2-31, 200 kb/s, 1 fps, 16384 tbn, 1 tbc
    Metadata:
      encoder         : Lavc57.107.100 mjpeg
    Side data:
      cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame=   10 fps=6.3 q=1.6 size=       0kB time=00:00:09.00 bitrate=   0.0kbits/s speed=5.63x

正如您在 te FFmpeg 输出的最后一行中看到的那样,图像出现了低谷。有 10 帧可供选择。但是关闭管道后,FFmpeg 并没有关闭,仍然期待输入。

如链接示例所示,这应该是一个有效的方法。

现在正在尝试一个星期...

【问题讨论】:

  • @rustyx 这有什么帮助?无论如何,我出于沮丧尝试了,但没有这样做。
  • 我觉得没问题 - 你试过发送几百帧吗?
  • @MarkSetchell 你试过了。不用找了。您也可以直接在命令窗口中尝试该命令行(使用合适的路径),将 -video_size 设置为较小的值,这样您就不必输入太多,只需将一些垃圾数据写入 STDIN。按下 ENTER 后,FFmpeg 仍会监听输入。好吧,这就是我希望它做的事情,因为在这种情况下 STDIN 管道没有关闭。但是在这种情况下,你会如何告诉 FFmpeg 我们已经完成了呢?多次点击 STRG+Z 会使 FFmpeg 退出并输出视频。 STRG+Z 是 EOF。我尝试通过程序发送 EOF 但没有结果。

标签: c++ video cmd ffmpeg pipe


【解决方案1】:

当您创建管道时,它会生成两个句柄,一个用于读取,一个用于写入:

HANDLE readPipe;
HANDLE writePipe;

SECURITY_ATTRIBUTES sa  = { sizeof(sa) };
sa.lpSecurityDescriptor = nullptr;
sa.bInheritHandle       = TRUE;

CreatePipe(&readPipe, &writePipe, &sa, sizeof(buffer));

注意bInheritHandle = TRUE 标志。这允许您生成的子进程(ffmpeg)继承句柄,这是它访问您的管道所必需的(下面的CreateProcess 函数签名中有一个类似的参数,bInheritHandles,也设置为TRUE) .

然后启动子进程并将其stdin 映射到readPipe

STARTUPINFOA si = { sizeof(si) };
si.dwFlags      = STARTF_USESTDHANDLES;
si.hStdInput    = readPipe;
si.hStdOutput   = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdError    = GetStdHandle(STD_ERROR_HANDLE);

PROCESS_INFORMATION pi = {};

CreateProcessA(nullptr, cmd, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);

此时您可以开始通过WriteFile 将帧泵送到ffmpeg,它(如果被告知使用来自stdin 的输入)将编码掉。问题是当你完成并打电话时..

CloseHandle(readPipe);
CloseHandle(writePipe);

..ffmpeg 一直在等待更多数据,而不是关闭文件。

原因如下:

当您创建子进程时,它将继承您的所有句柄,而不仅仅是您在STARTUPINFO 结构中指定的那个(即readPipe)。所以它会继承writePipe句柄。对于从readPipe 读取以产生EOF,指示ffmpeg 完成并关闭,writePipe 必须首先关闭。但由于子进程拥有两个句柄的副本,即使在您结束时关闭这对句柄也不会在客户端触发 EOF,因为writePipe 仍然在那里保持活动状态。

解决方法是防止writePipe 被继承。像这样(在产生子进程之前):

SetHandleInformation(writePipe, HANDLE_FLAG_INHERIT, 0);

这是一个完整的工作源代码示例(输出一个简短的测试影片):

#include <windows.h>

char* cmd = "ffmpeg -f rawvideo -pix_fmt rgba -video_size 1920x1080 -r 30 -i pipe:0 -preset fast -y -pix_fmt yuv420p output.mp4";
int   buffer[1920 * 1080];

void main()
{
    HANDLE readPipe;
    HANDLE writePipe;

    SECURITY_ATTRIBUTES sa  = { sizeof(sa) };
    sa.lpSecurityDescriptor = nullptr;
    sa.bInheritHandle       = TRUE;

    CreatePipe(&readPipe, &writePipe, &sa, sizeof(buffer));

    SetHandleInformation(writePipe, HANDLE_FLAG_INHERIT, 0);

    STARTUPINFOA si = { sizeof(si) };
    si.dwFlags      = STARTF_USESTDHANDLES;
    si.hStdInput    = readPipe;
    si.hStdOutput   = GetStdHandle(STD_OUTPUT_HANDLE);
    si.hStdError    = GetStdHandle(STD_ERROR_HANDLE);

    PROCESS_INFORMATION pi = {};

    CreateProcessA(nullptr, cmd, nullptr, nullptr, TRUE, 0, nullptr, nullptr, &si, &pi);

    for (int frame = 0; frame < 1080; frame++)
    {
        for (int x = 0; x < 1920; x++)
        {
            buffer[frame * 1920 + x] = 0xffffff00 | (frame * 255 / 1079);
        }

        DWORD bytesWritten;
        WriteFile(writePipe, buffer, sizeof(buffer), &bytesWritten, nullptr);
    }

    CloseHandle(readPipe);
    CloseHandle(writePipe);
}

【讨论】:

  • 谢谢。没有尝试您的代码,但我仍然接受它作为答案。
【解决方案2】:

我终于放弃了。不知道是什么问题。改为使用套接字和 TCP。 TCP 连接的命令行下方。

LPSTR ffmpegURL = "\"PATHTOFFMPEGEXE\" -y -loglevel verbose -f rawvideo -vcodec rawvideo -framerate 1 -video_size 80x80 -pixel_format rgb24 -i tcp://127.0.0.1:PORT_NR -vcodec mjpeg -framerate 1/4 -an \"OUTPUTDIRECTORY\"";

在这种情况下,FFmpeg 是客户端并尝试连接到服务器。

【讨论】:

    猜你喜欢
    • 2011-08-15
    • 1970-01-01
    • 2018-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-06
    • 2015-11-16
    • 2020-01-30
    相关资源
    最近更新 更多