为什么使用 ffmpeg 实时创建视频文件时 ffmpeg.exe 占用 1GB 内存?
Why when using ffmpeg to create video file in real time the ffmpeg.exe take over 1GB of memory?
这是我的 class,我正在使用 ffmpeg.exe。
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using DannyGeneral;
namespace Manager
{
public class Ffmpeg
{
NamedPipeServerStream p;
String pipename = "mytestpipe";
System.Diagnostics.Process process;
string ffmpegFileName = "ffmpeg.exe";
string workingDirectory;
public Ffmpeg()
{
workingDirectory = Path.GetDirectoryName(Application.ExecutablePath);
Logger.Write("workingDirectory: " + workingDirectory);
if (!Directory.Exists(workingDirectory))
{
Directory.CreateDirectory(workingDirectory);
}
ffmpegFileName = Path.Combine(workingDirectory, ffmpegFileName);
Logger.Write("FfmpegFilename: " + ffmpegFileName);
}
public void Start(string pathFileName, int BitmapRate)
{
try
{
string outPath = pathFileName;
p = new NamedPipeServerStream(pipename, PipeDirection.Out, 1, PipeTransmissionMode.Byte);
ProcessStartInfo psi = new ProcessStartInfo();
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
psi.CreateNoWindow = false;
psi.FileName = ffmpegFileName;
psi.WorkingDirectory = workingDirectory;
psi.Arguments = @"-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r " + BitmapRate + " " + outPath;
process = Process.Start(psi);
process.EnableRaisingEvents = false;
psi.RedirectStandardError = true;
p.WaitForConnection();
}
catch (Exception err)
{
Logger.Write("Exception Error: " + err.ToString());
}
}
public void PushFrame(Bitmap bmp)
{
try
{
int length;
// Lock the bitmap's bits.
//bmp = new Bitmap(1920, 1080);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
//Rectangle rect = new Rectangle(0, 0, 1280, 720);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
bmp.PixelFormat);
int absStride = Math.Abs(bmpData.Stride);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
//length = 3 * bmp.Width * bmp.Height;
length = absStride * bmpData.Height;
byte[] rgbValues = new byte[length];
//Marshal.Copy(ptr, rgbValues, 0, length);
int j = bmp.Height - 1;
for (int i = 0; i < bmp.Height; i++)
{
IntPtr pointer = new IntPtr(bmpData.Scan0.ToInt32() + (bmpData.Stride * j));
System.Runtime.InteropServices.Marshal.Copy(pointer, rgbValues, absStride * (bmp.Height - i - 1), absStride);
j--;
}
p.Write(rgbValues, 0, length);
bmp.UnlockBits(bmpData);
}
catch(Exception err)
{
Logger.Write("Error: " + err.ToString());
}
}
public void Close()
{
p.Close();
}
}
}
ffmpegFileName 包含 ffmpeg.exe
然后我有另一种形式的计时器滴答事件:
ScreenShot shot = new ScreenShot();
public static int counter = 0;
private void timer1_Tick(object sender, EventArgs e)
{
counter++;
shot.GetScreenShot(@"e:\screenshots\", "screenshot");
if (counter == 1200)
{
timer1.Stop();
ScreenShot.fmpeg.Close();
this.Close();
ScreenShotsPlayer ssp = new ScreenShotsPlayer();
ssp.Show();
}
}
这个定时器间隔设置为100ms。
所以它每 100 毫秒截取一次屏幕截图。
在 class 截图中,我使用的是 Ffmpeg class:
在顶部:
public static Ffmpeg fmpeg;
在构造函数中:
fmpeg = new Ffmpeg();
fmpeg.Start(@"e:\screenshots\test.mp4", 25);
然后在 GetScreenShot 方法中调用 Ffmpeg PushFrame 方法:
public Bitmap GetScreenShot(string folder, string name)
{
_screenShot = new Bitmap(GetScreen());
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
string ingName = folder + name + Elgato_Video_Capture.counter.ToString("D6") + ".bmp";
_screenShot.Save(ingName,System.Drawing.Imaging.ImageFormat.Bmp);
fmpeg.PushFrame(_screenShot);
_screenShot.Dispose();
return _screenShot;
}
我不明白为什么一旦我启动定时器截取屏幕截图,ffmpeg.exe 就占用了这么多内存,而且硬盘工作速度约为 53MB/s,内存超过 90% 甚至更多1GB.
我正在以某种方式造成内存泄漏?
在屏幕截图中 class 当我为 Ffmpeg class 创建一个实例时, ffmpeg.exe 内存使用率非常低,硬盘使用率也很低。
ffmpeg.exe大约只占用了0.1MB的内存。
fmpeg.Start(@"e:\screenshots\test.mp4", 25);
但是当我单击按钮时启动计时器并每 100 毫秒截取一次屏幕截图并调用 PushFrame 方法 ffmpeg.exe 在几秒钟后非常快地使用超过 1GB 的内存。
编辑
我试过这个参数:
psi.Arguments = @"-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r 750k e:\screenshots\test.mp4";
内存使用量约为 ffmpeg.exe 的 1gb,cpu 使用量为 99-100%
硬盘为 0,但现在 cpu 使用率达到 100%,内存超过 1gb。
它没有创建我得到的视频文件 warnings/errors:
您似乎在无休止地编码 1920x1080 视频(参考:-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe
)。编码(以及解码)h264 高质量视频需要大量的 CPU 功率。我建议阅读更多有关 H.264 编码的内容。
这个问题不太可能回答,因为:
FFmpeg 片段非常隐藏在此代码实现中(基本上只是 运行 一个系统 shell 命令)。导出正在执行的完整 FFmpeg 命令将是一个好的开始。 ffmpeg 命令应该类似于 ffmpeg -f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r 750k e:\screenshots\test.mp4
.
FFmpeg 根据输入和输出流而变化。例如,如果您将选择不同的编码器(比如将:-c:v libx264
更改为:-c:v mpeg2video
),我可以保证显着的性能改进,因为 mpeg2video 是一种轻型编码器,但文件大小将增加约 20 倍更大.
祝你好运。
-E
这是我的 class,我正在使用 ffmpeg.exe。
using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using DannyGeneral;
namespace Manager
{
public class Ffmpeg
{
NamedPipeServerStream p;
String pipename = "mytestpipe";
System.Diagnostics.Process process;
string ffmpegFileName = "ffmpeg.exe";
string workingDirectory;
public Ffmpeg()
{
workingDirectory = Path.GetDirectoryName(Application.ExecutablePath);
Logger.Write("workingDirectory: " + workingDirectory);
if (!Directory.Exists(workingDirectory))
{
Directory.CreateDirectory(workingDirectory);
}
ffmpegFileName = Path.Combine(workingDirectory, ffmpegFileName);
Logger.Write("FfmpegFilename: " + ffmpegFileName);
}
public void Start(string pathFileName, int BitmapRate)
{
try
{
string outPath = pathFileName;
p = new NamedPipeServerStream(pipename, PipeDirection.Out, 1, PipeTransmissionMode.Byte);
ProcessStartInfo psi = new ProcessStartInfo();
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
psi.CreateNoWindow = false;
psi.FileName = ffmpegFileName;
psi.WorkingDirectory = workingDirectory;
psi.Arguments = @"-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r " + BitmapRate + " " + outPath;
process = Process.Start(psi);
process.EnableRaisingEvents = false;
psi.RedirectStandardError = true;
p.WaitForConnection();
}
catch (Exception err)
{
Logger.Write("Exception Error: " + err.ToString());
}
}
public void PushFrame(Bitmap bmp)
{
try
{
int length;
// Lock the bitmap's bits.
//bmp = new Bitmap(1920, 1080);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
//Rectangle rect = new Rectangle(0, 0, 1280, 720);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly,
bmp.PixelFormat);
int absStride = Math.Abs(bmpData.Stride);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
//length = 3 * bmp.Width * bmp.Height;
length = absStride * bmpData.Height;
byte[] rgbValues = new byte[length];
//Marshal.Copy(ptr, rgbValues, 0, length);
int j = bmp.Height - 1;
for (int i = 0; i < bmp.Height; i++)
{
IntPtr pointer = new IntPtr(bmpData.Scan0.ToInt32() + (bmpData.Stride * j));
System.Runtime.InteropServices.Marshal.Copy(pointer, rgbValues, absStride * (bmp.Height - i - 1), absStride);
j--;
}
p.Write(rgbValues, 0, length);
bmp.UnlockBits(bmpData);
}
catch(Exception err)
{
Logger.Write("Error: " + err.ToString());
}
}
public void Close()
{
p.Close();
}
}
}
ffmpegFileName 包含 ffmpeg.exe
然后我有另一种形式的计时器滴答事件:
ScreenShot shot = new ScreenShot();
public static int counter = 0;
private void timer1_Tick(object sender, EventArgs e)
{
counter++;
shot.GetScreenShot(@"e:\screenshots\", "screenshot");
if (counter == 1200)
{
timer1.Stop();
ScreenShot.fmpeg.Close();
this.Close();
ScreenShotsPlayer ssp = new ScreenShotsPlayer();
ssp.Show();
}
}
这个定时器间隔设置为100ms。 所以它每 100 毫秒截取一次屏幕截图。
在 class 截图中,我使用的是 Ffmpeg class: 在顶部:
public static Ffmpeg fmpeg;
在构造函数中:
fmpeg = new Ffmpeg();
fmpeg.Start(@"e:\screenshots\test.mp4", 25);
然后在 GetScreenShot 方法中调用 Ffmpeg PushFrame 方法:
public Bitmap GetScreenShot(string folder, string name)
{
_screenShot = new Bitmap(GetScreen());
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
string ingName = folder + name + Elgato_Video_Capture.counter.ToString("D6") + ".bmp";
_screenShot.Save(ingName,System.Drawing.Imaging.ImageFormat.Bmp);
fmpeg.PushFrame(_screenShot);
_screenShot.Dispose();
return _screenShot;
}
我不明白为什么一旦我启动定时器截取屏幕截图,ffmpeg.exe 就占用了这么多内存,而且硬盘工作速度约为 53MB/s,内存超过 90% 甚至更多1GB.
我正在以某种方式造成内存泄漏?
在屏幕截图中 class 当我为 Ffmpeg class 创建一个实例时, ffmpeg.exe 内存使用率非常低,硬盘使用率也很低。
ffmpeg.exe大约只占用了0.1MB的内存。
fmpeg.Start(@"e:\screenshots\test.mp4", 25);
但是当我单击按钮时启动计时器并每 100 毫秒截取一次屏幕截图并调用 PushFrame 方法 ffmpeg.exe 在几秒钟后非常快地使用超过 1GB 的内存。
编辑
我试过这个参数:
psi.Arguments = @"-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r 750k e:\screenshots\test.mp4";
内存使用量约为 ffmpeg.exe 的 1gb,cpu 使用量为 99-100% 硬盘为 0,但现在 cpu 使用率达到 100%,内存超过 1gb。
它没有创建我得到的视频文件 warnings/errors:
您似乎在无休止地编码 1920x1080 视频(参考:-f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe
)。编码(以及解码)h264 高质量视频需要大量的 CPU 功率。我建议阅读更多有关 H.264 编码的内容。
这个问题不太可能回答,因为:
FFmpeg 片段非常隐藏在此代码实现中(基本上只是 运行 一个系统 shell 命令)。导出正在执行的完整 FFmpeg 命令将是一个好的开始。 ffmpeg 命令应该类似于
ffmpeg -f rawvideo -pix_fmt bgra -video_size 1920x1080 -i \.\pipe\mytestpipe -c:v libx264 -crf 20 -r 750k e:\screenshots\test.mp4
.FFmpeg 根据输入和输出流而变化。例如,如果您将选择不同的编码器(比如将:
-c:v libx264
更改为:-c:v mpeg2video
),我可以保证显着的性能改进,因为 mpeg2video 是一种轻型编码器,但文件大小将增加约 20 倍更大.
祝你好运。 -E