Ответ 1
Если файл сохранен, тогда проще было бы просто сообщить FFmpeg, чтобы преобразовать этот видеофайл в Jpegs.
(1) Чтение видеофайлов и выходных кадров Jpegs (без использования потоков или потоков памяти/файлов):
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...
(2) Метод труб:
При использовании труб FFmpeg будет передавать поток, как широковещательный. Если достигнут последний видеокадр, то такое же "изображение" последнего кадра будет повторяться бесконечно. Вы должны вручную сообщить 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 кбайта (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 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 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()
не работает, если вы используете какую-либо внешнюю функцию для возврата Process
. Не using (Process cmd = GetProcess(FFmpegPath, Arguments))
как: using (Process cmd = GetProcess(FFmpegPath, Arguments))
.
Удачи. Дайте мне знать, как это происходит.
(PS: Извините "слишком много" комментариев кода, это для будущих читателей, которые могут или не могут понять, что этот код делает для правильной работы в буфере).