c# - 折れ線 - タイトルバー 非表示 windows10




FFmpegはレンダリングフレームをスキップします (2)

この問題は、アドビのサイトから引用したものです。

答えはすべてです。デフォルトのレンダリング出力は圧縮されていないため、非常に高速のデータレートが得られます。たとえ非常に強力なコンピュータであっても、スムーズに再生することはできません。

ここでのことは簡単です。低品質を使用している場合でも、あなたは高いデータレートをレンダリングしています。 その場合の最大バッファサイズは確かに4096です。 そのバッファ内に前後の画像からのバイト数があり、かつカンマで区切られていない場合、FFmpegはどのフレームをレンダリングするかを決定できません。

バイトをカンマで区切ると、FFmpegが前の画像と次の画像のバイトを区切るのに役立ちます。これにより、レンダリングするフレームを区別しやすくなり、フレームがスキップされなくなります。

ビデオからフレームを抽出している間に、 ffmpeg特定のイメージのレンダリングを終了しないことに気付きました。 問題は、2つの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([email protected]"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)はテスト目的で使用されるべきです。


ファイルが保存されている場合は、そのビデオファイルをJpegsに変換するようにFFmpegに指示する方が簡単な場合があります。

(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に指示する必要があります(この状況では "exit"コードはありません)。

このコード行は、停止する前にどのようにフレームを抽出するかを指定します。

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()自体の中に入れてください。外部関数を使用してProcessを返す場合は、これは機能しませんusing (Process cmd = GetProcess(FFmpegPath, Arguments))セットアップしないでください。

がんばろう。 どうなるか教えてください。

(PS: "多すぎる"コードのコメントを言い訳にしてください。これは将来の読者のためのものです。バッファの問題に対してこのコードが正しく機能するために何をしているのか理解できないかもしれません)。





ffmpeg