Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/windows/16.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# FFmpeg跳过渲染帧_C#_Windows_Ffmpeg - Fatal编程技术网

C# FFmpeg跳过渲染帧

C# FFmpeg跳过渲染帧,c#,windows,ffmpeg,C#,Windows,Ffmpeg,当我从视频中提取帧时,我注意到ffmpeg无法完成某些图像的渲染。问题最终是两幅jpeg图像之间的字节“填充”。如果我的缓冲区大小是4096,并且如果在该缓冲区中有来自上一个图像和下一个图像的字节,并且它们之间没有任何字节数的分隔,则下一个图像无法正确渲染。为什么呢 -i path -f image2pipe -c:v mjpeg -q:v 2 -vf fps=25 pipe:1 渲染帧: 代码示例: public void ExtractFrames() { string FFm

当我从视频中提取帧时,我注意到
ffmpeg
无法完成某些图像的渲染。问题最终是两幅
jpeg
图像之间的字节“填充”。如果我的缓冲区大小是
4096
,并且如果在该缓冲区中有来自上一个图像和下一个图像的字节,并且它们之间没有任何字节数的分隔,则下一个图像无法正确渲染。为什么呢

-i path -f image2pipe -c:v mjpeg -q:v 2 -vf fps=25 pipe:1

渲染帧:

代码示例:

public void ExtractFrames()
{
    string FFmpegPath = "Path...";
    string Arguments = $"-i { VideoPath } -f image2pipe -c:v mjpeg -q:v 2 -vf fps=25/1 pipe:1";
    using (Process cmd = GetProcess(FFmpegPath, Arguments))
    {
        cmd.Start();
        FileStream fStream = cmd.StandardOutput.BaseStream as FileStream;

        bool Add = false;
        int i = 0, n = 0, BufferSize = 4096;
        byte[] buffer = new byte[BufferSize + 1];

        MemoryStream mStream = new MemoryStream();

        while (true)
        {
            if (i.Equals(BufferSize))
            {
                i = 0;
                buffer[0] = buffer[BufferSize];
                if (fStream.Read(buffer, 1, BufferSize) == 0)
                    break;
            }

            if (buffer[i].Equals(255) && buffer[i + 1].Equals(216))
            {
                Add = true;
            }

            if (buffer[i].Equals(255) && buffer[i + 1].Equals(217))
            {
                n++;
                Add = false;
                mStream.Write(new byte[] { 255, 217 }, 0, 2);
                File.WriteAllBytes($@"C:\Path...\{n}.jpg", mStream.ToArray());
                mStream = new MemoryStream();
            }

            if (Add)
                mStream.WriteByte(buffer[i]);

            i++;
        }
        cmd.WaitForExit();
        cmd.Close();
    }
}

private Process GetProcess(string FileName, string Arguments)
{
    return new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = FileName,
            Arguments = Arguments,
            UseShellExecute = false,
            RedirectStandardOutput = true,
            CreateNoWindow = false,
        }
    };
}

长度为60秒或更高的视频样本(>480p)应用于测试目的。

此问题在全球范围内发生,供Adobe网站参考:

答案就在这里-默认渲染输出是未压缩的, 它产生如此高的数据率,即使是相当结实的计算机也永远不会 能够流畅地回放

这里的事情很简单:渲染的数据速率很高,即使使用的是低质量的数据。这种情况下的最大缓冲区大小实际上是,
4096
。如果在该缓冲区中有来自上一个和下一个图像的字节,并且没有用逗号分隔,则FFmpeg无法决定要渲染的帧,因此它跳过该帧,因为它会使该帧变暗,而不是随机建议刷新哪个帧


如果用逗号分隔字节,可以帮助FFmpeg绑定前一帧和下一帧图像的字节,从而更容易区分要渲染的帧,从而不会跳过帧

如果存储了该文件,那么告诉FFmpeg将该视频文件转换为JPEG可能会更容易

(1)读取视频文件并输出帧JPEG(不涉及管道或内存/文件流):

(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号电话号码?标准输出通常提供32kb的最大数据包大小(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
注意:修改上述代码时,请确保将
过程
创建保持在函数
ExtractFrames()
本身中,如果使用某些外部函数返回
过程
,则此操作将无效。不要设置为:
使用(processcmd=GetProcess(FFmpegPath,参数))

祝你好运。让我知道进展如何


(注:请原谅“太多”的代码注释,这是为了未来读者的利益,他们可能理解也可能不理解这段代码是如何正确处理缓冲区问题的)

写入文件时是否会发生这种情况?是否会发生在
-i path-f image2-c:v mjpeg-q:v 2-vf fps=25 out%d.jpg
和/或
-i path-f image2pipe-c:v mjpeg-q:v 2-vf fps=25管道:1 | ffmpeg-f image2pipe-i--c复制管道%d.jpg
?但如何解决问题?我需要做什么来压缩视频并达到我需要的效果?如何用逗号分隔字节?最简单的方法是-byte[]bytes=strings.Select(s=>byte.Parse(s)).ToArray();如果我将缓冲区大小降低到1024或512,结果会更糟。它只在我的缓冲区为1时按预期工作,但速度很慢。我不明白…我没有任何字符串要分析。请使用此链接作为提示:编辑:在缓冲区中添加缺少的变量,如
pos\u
,我没有从自己的测试程序中复制这些变量。现在应该可以了。所有这些都很好,但是您的缓冲区大小是
1
,我不希望这样。如果我的缓冲区为
1
且视频长度为2小时,则需要2小时才能完成,但如果我的缓冲区为
4096
,或将其增加,则需要30分钟。所以,我的问题是如何保持缓冲区大小,但防止“打嗝”。嗨。我做了一个编辑。既然我把你的代码修改搞砸了,也许给你看我自己的工作示例更容易些。然后你可以申请你的。“缓冲区大小为
1
”是正确读取每个字节值所必需的。它并不慢,只是比复制4096个单独和匿名字节要好。您必须一次读取一个字节(无速度差)。最后,没有
cmd.WaitForExit()
因此您可以强制执行
cmd.Close
cmd.Dispose
,如While循环所示。如果缓冲区大小为4096比特率为24000kbit/s,则使用1字节作为缓冲区ffmpeg比特率为~6000kbit/s。这不再重要了,我最终制作了ffmpeg包装器。你在我的代码中测试过这两个选项吗?为了避免额外的包装,您可以单独使用步骤1并完成,如果您需要管道的方式,那么步骤2就是您所需要的。并没有“使用1字节作为缓冲区”,只是代码告诉STD输出在while循环的每个周期报告(读取)1字节的值。它与进程本身运行的任何程序(如FFmpeg)或某些比特率设置无关。一个STD out数据包最多可容纳32kb,因此对于
6000
kbps,您将获得188个数据包。半路
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