如果文件已存储,那么告诉FFmpeg将该视频文件转换为jpeg可能更容易。
(一)
读取视频文件并输出帧JPEG(不涉及管道或内存/文件流):
string str_MyProg = "C:/FFmpeg/bin/ffmpeg.exe";
string VideoPath = "C:/someFolder/test_vid.mp4";
string save_folder = "C:/someOutputFolder/";
//# Setup the arguments to directly output a sequence of images (frames)
string str_CommandArgs = "-i " + VideoPath + " -vf fps=25/1 " + save_folder + "n_%03d.jpg"; //the n_%03d replaces "n++" count
System.Diagnostics.ProcessStartInfo cmd_StartInfo = new System.Diagnostics.ProcessStartInfo(str_MyProg, str_CommandArgs);
cmd_StartInfo.RedirectStandardError = false; //set false
cmd_StartInfo.RedirectStandardOutput = false; //set false
cmd_StartInfo.UseShellExecute = true; //set true
cmd_StartInfo.CreateNoWindow = true; //don't need the black window
//Create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process cmd = new System.Diagnostics.Process();
cmd.StartInfo = cmd_StartInfo;
cmd.Start();
//# Started process. Check output folder for images...
(二)
管道方法:
使用管道时,FFmpeg会像广播一样将输出流传回。如果到达最后一个视频帧,则相同的最后一帧“图像”将被无限重复。你必须
手动告知
什么时候停止发送到你的应用程序(在这种情况下没有“退出”代码)。
代码中的这一行将指定在停止之前如何提取任何帧:
int frames_expected_Total = 0; //is... (frame_rate x Duration) = total expected frames
您可以将限额计算为:
input-Duration / output-FPS
或作为
output-FPS * input-Duration
.
示例:视频持续时间为4.88秒,因此
25 * 4.88 =
此视频限制122帧。
“如果我的缓冲区大小是
4096个
... 然后下一个图像没有正确渲染。
为什么呢?”
因为缓冲区是
太小了
要保存完整的图像。。。
缓冲区大小
公式为:
int BufferSize = ( video_Width * video_Height );
因为最终压缩的jpeg文件将小于这个数量,所以它保证
BufferSize
它可以保存任何完整的帧而不出错。出于兴趣,你从哪里得到的
4096个
号码来自?标准输出通常给出最大分组大小为32 kb(
32768个
字节)。
溶液(测试)
:
这是一个完整的
工作
示例解决“小故障”图像问题,检查代码注释。。。
using System;
using System.IO;
using System.Net;
using System.Drawing;
using System.Diagnostics;
using System.Collections.Generic;
namespace FFmpeg_Vid_to_JPEG //replace with your own project "namespace"
{
class Program
{
public static void Main(string[] args)
{
//# testing the Extract function...
ExtractFrames();
}
public static void ExtractFrames()
{
//# define paths for PROCESS
string FFmpegPath = "C:/FFmpeg/bin/ffmpeg.exe";
string VideoPath = "C:/someFolder/test_vid.mp4";
//# FFmpeg arguments for PROCESS
string str_myCommandArgs = "-i " + VideoPath + " -f image2pipe -c:v mjpeg -q:v 2 -vf fps=25/1 pipe:1";
//# define paths for SAVE folder & filename
string save_folder = "C:/someOutputFolder/";
string save_filename = ""; //update name later on, during SAVE commands
MemoryStream mStream = new MemoryStream(); //create once, recycle same for each frame
////// # also create these extra variables...
bool got_current_JPG_End = false; //flag to begin extraction of image bytes within stream
int pos_in_Buffer = 0; //pos in buffer(when checking for Jpeg Start/End bytes)
int this_jpeg_len = 0; // holds bytes of single jpeg image to save... correct length avoids cropping effect
int pos_jpeg_start = 0; int pos_jpeg_end = 0; //marks the start/end pos of one image within total stream
int jpeg_count = 0; //count of exported Jpeg files (replaces the "n++" count)
int frames_expected_Total = 0; //number of frames to get before stopping
//# use input video's width x height as buffer size //eg: size 921600 = 1280 W x 720H
int BufferSize = 921600;
byte[] buffer = new byte[BufferSize + 1];
// Create a process, assign its ProcessStartInfo and start it
ProcessStartInfo cmd_StartInfo = new ProcessStartInfo(FFmpegPath, str_myCommandArgs);
cmd_StartInfo.RedirectStandardError = true;
cmd_StartInfo.RedirectStandardOutput = true; //set true to redirect the process stdout to the Process.StandardOutput StreamReader
cmd_StartInfo.UseShellExecute = false;
cmd_StartInfo.CreateNoWindow = true; //do not create the black window
Process cmd = new System.Diagnostics.Process();
cmd.StartInfo = cmd_StartInfo;
cmd.Start();
if (cmd.Start())
{
//# holds FFmpeg output bytes stream...
var ffmpeg_Output = cmd.StandardOutput.BaseStream; //replaces: fStream = cmd.StandardOutput.BaseStream as FileStream;
cmd.BeginErrorReadLine(); //# begin receiving FFmpeg output bytes stream
//# get (read) first two bytes in stream, so can check for Jpegs' SOI (xFF xD8)
//# each "Read" auto moves forward by read "amount"...
ffmpeg_Output.Read(buffer, 0, 1);
ffmpeg_Output.Read(buffer, 1, 1);
pos_in_Buffer = this_jpeg_len = 2; //update reading pos
//# we know first jpeg's SOI is always at buffer pos: [0] and [1]
pos_jpeg_start = 0; got_current_JPG_End = false;
//# testing amount... Duration 4.88 sec, FPS 25 --> (25 x 4.88) = 122 frames
frames_expected_Total = 122; //122; //number of Jpegs to get before stopping.
while(true)
{
//# For Pipe video you must exit stream manually
if ( jpeg_count == (frames_expected_Total + 1) )
{
cmd.Close(); cmd.Dispose(); //exit the process
break; //exit if got required number of frame Jpegs
}
//# otherwise read as usual
ffmpeg_Output.Read(buffer, pos_in_Buffer, 1);
this_jpeg_len +=1; //add 1 to expected jpeg bytes length
//# find JPEG start (SOI is bytes 0xFF 0xD8)
if ( (buffer[pos_in_Buffer] == 0xD8) && (buffer[pos_in_Buffer-1] == 0xFF) )
{
if (got_current_JPG_End == true)
{
pos_jpeg_start = (pos_in_Buffer-1);
got_current_JPG_End = false;
}
}
//# find JPEG ending (EOI is bytes 0xFF 0xD9) then SAVE FILE
if ( (buffer[pos_in_Buffer] == 0xD9) && (buffer[pos_in_Buffer-1] == 0xFF) )
{
if (got_current_JPG_End == false)
{
pos_jpeg_end = pos_in_Buffer; got_current_JPG_End = true;
//# update saved filename
save_filename = save_folder + "n_" + (jpeg_count).ToString() + ".jpg";
try
{
//# If the Jpeg save folder doesn't exist, create it.
if ( !Directory.Exists( save_folder ) ) { Directory.CreateDirectory( save_folder ); }
}
catch (Exception)
{
//# handle any folder create errors here.
}
mStream.Write(buffer, pos_jpeg_start, this_jpeg_len); //
//# save to disk...
File.WriteAllBytes(@save_filename, mStream.ToArray());
//recycle MemoryStream, avoids creating multiple = new MemoryStream();
mStream.SetLength(0); mStream.Position = 0;
//# reset for next pic
jpeg_count +=1; this_jpeg_len=0;
pos_in_Buffer = -1; //allows it to become 0 position at incrementation part
}
}
pos_in_Buffer += 1; //increment to store next byte in stdOut stream
} //# end While
}
else
{
// Handler code here for "Process is not running" situation
}
} //end ExtractFrame function
} //end class
} //end program
注意:修改上述代码时,请确保
Process
函数内的创建
ExtractFrames()
本身,这将
不工作
如果使用某个外部函数返回
过程
. 不要设置为:
using (Process cmd = GetProcess(FFmpegPath, Arguments))
.
祝你好运。告诉我进展如何。
(注:请原谅“过多”的代码注释,这是为了将来的读者的利益,他们可能理解或可能不理解此代码在缓冲区问题上的正确工作方式)。