FFmpeg - 通过管道输入原始帧 - FFmpeg不检测管道关闭
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg - 通过管道输入原始帧 - FFmpeg不检测管道关闭相关的知识,希望对你有一定的参考价值。
我试图在Windows中使用C ++中的这些示例。 Phyton Example C# Example
我有一个应用程序,生成应使用FFmpeg编码的原始帧。原始帧通过IPC管道传输到FFmpegs STDIN。这是按预期工作的,FFmpeg甚至显示当前可用的帧数。
当我们完成发送帧时,问题就出现了。当我关闭管道的写端时,我希望FFmpeg检测到它,完成并输出视频。但这不会发生。 FFmpeg保持打开状态,似乎等待更多数据。
我在Visual Studio中做了一个小测试项目。
#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没有关闭,仍然期待输入。
如链接示例所示,这应该是一种有效的方法。
现在试着一个星期......
在创建管道时,它会生成两个句柄,一个用于读取,另一个用于写入:
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);
}
我终于放弃了。不知道这个问题是什么。切换使用套接字和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是客户端并尝试连接到服务器。
以上是关于FFmpeg - 通过管道输入原始帧 - FFmpeg不检测管道关闭的主要内容,如果未能解决你的问题,请参考以下文章