本文主要介绍.NET Core中通过System.Diagnostics.Process后台进程调用FFmpeg将视频转成图片的方法。

1、FFmpeg下载

1)Windows 64位

下载地址https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-20190107-e9564f7-win64-static.zip

2)Windows 32位

下载地址https://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20190107-e9564f7-win32-static.zip

3)MacOS 64位

下载地址https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-20190107-e9564f7-macos64-static.zip

2、Linux下安装

其它系统中下载下来可以直接使用,Linux下需要安装一下,具体步骤如下,

安装依赖包

yum install  libtheora-devel  libvorbis-devel

FFmpeg编译安装

由于系统不同,会导致依赖也不尽相同,需根据报错信息逐渐安装依赖

wget http://ffmpeg.org/releases/ffmpeg-3.3.2.tar.bz2
tar jxvf ffmpeg-3.3.2.tar.bz2
cd ffmpeg-3.3.2

# 如下是个人需要的编译参数,可以根据实际情况增减

./configure --prefix=/usr/local/ffmpeg \
--enable-shared \
--enable-libfdk-aac \
--enable-gpl \
--enable-nonfree \
--enable-postproc \
--enable-avfilter \
--enable-pthreads \
--enable-libmp3lame \
--enable-libtheora \
--enable-libvorbis \
--enable-libx264 \
--enable-libxvid \
--enable-decoder=libx264 \
--enable-encoder=libx264

安装FFmpeg

make && make install

安装后,查看版本

./bin/ffmpeg -version

3、使用FFmpeg转换代码 

1)读取视频文件和输出帧Jpeg图片(不涉及管道或内存/文件流)

string str_MyProg = "C:/FFmpeg/bin/ffmpeg.exe";
string VideoPath = "C:/someFolder/test_vid.mp4";
string save_folder = "C:/someOutputFolder/";
//#设置参数以直接输出图像序列(帧)
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
//创建一个进程,分配它的ProcessStartInfo并启动它
System.Diagnostics.Process cmd = new System.Diagnostics.Process();
cmd.StartInfo = cmd_StartInfo;
cmd.Start();

2)管道方法

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)
        {
            ExtractFrames();
        }
        public static void ExtractFrames()
        {
            string FFmpegPath = "C:/FFmpeg/bin/ffmpeg.exe";
            string VideoPath = "C:/someFolder/test_vid.mp4";
            //# FFmpeg 使用命令参数
            string str_myCommandArgs = "-i " + VideoPath + " -f image2pipe -c:v mjpeg -q:v 2 -vf fps=25/1 pipe:1";
            //# 保存图片路径
            string save_folder = "C:/someOutputFolder/";
            string save_filename = "";
            MemoryStream mStream = new MemoryStream(); //创建一次,循环相同的每帧
            bool got_current_JPG_End = false; //标记开始提取流中的图像字节
            int pos_in_Buffer = 0; //缓冲区中的pos(在检查Jpeg开始/结束字节时)
            int this_jpeg_len = 0; // 保存单个jpeg图像字节以保存…长度正确,避免裁切效果
            int pos_jpeg_start = 0; int pos_jpeg_end = 0; //在整个流中标记一个图像的开始/结束pos
            int jpeg_count = 0; //导出Jpeg文件的计数(替换“n++”计数)
            int frames_expected_Total = 0; //停止前要获得的帧数
            //# 使用输入视频的宽度x高度作为缓冲区大小 //eg: size 921600 = 1280 W x 720H 
            int BufferSize = 921600;  
            byte[] buffer = new byte[BufferSize + 1];
            // 创建一个进程,分配它的ProcessStartInfo并启动它
            ProcessStartInfo cmd_StartInfo = new ProcessStartInfo(FFmpegPath, str_myCommandArgs);
            cmd_StartInfo.RedirectStandardError = true;
            cmd_StartInfo.RedirectStandardOutput = true; //设置为true将进程stdout重定向到进程。StandardOutput StreamReader
            cmd_StartInfo.UseShellExecute = false;
            cmd_StartInfo.CreateNoWindow = true; //不创建黑窗口
            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(); //# 开始接收FFmpeg输出字节流
                //# 获取(读取)流中的前两个字节,因此可以检查jpeg的SOI (xFF xD8)
                //#每个“读取”自动前进读取“数量”…
                ffmpeg_Output.Read(buffer, 0, 1);
                ffmpeg_Output.Read(buffer, 1, 1);
                pos_in_Buffer = this_jpeg_len = 2; //更新读取的pos
                //# 首先我们知道jpeg的SOI总是处于缓冲pos:[0]和[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)
                {
                    //# 对于管道视频,您必须手动退出流
                    if ( jpeg_count == (frames_expected_Total + 1) )
                    {
                        cmd.Close(); cmd.Dispose(); //退出进程
                        break; //如果获取到所需的帧jpeg数,则退出
                    }
                    //#否则正常读取 
                    ffmpeg_Output.Read(buffer, pos_in_Buffer, 1);
                    this_jpeg_len +=1; //add 1 to expected jpeg bytes length
                    //#查找JPEG start (SOI是字节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; 
                        }
                    }
                    //# 查找JPEG结束符(EOI是字节0xFF 0xD9),然后保存文件
                    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;
                            //# 更新保存的文件名
                            save_filename = save_folder + "n_" + (jpeg_count).ToString() + ".jpg";
                            try
                            {
                                //# 如果Jpeg保存文件夹不存在,则创建它。
                                if ( !Directory.Exists( save_folder ) ) { Directory.CreateDirectory( save_folder ); }
                            } 
                            catch (Exception)
                            { 
                                //# 处理任何文件夹创建错误在这里。
                            }
                            mStream.Write(buffer, pos_jpeg_start, this_jpeg_len); //
                            //# 保存到磁盘
                            File.WriteAllBytes(@save_filename, mStream.ToArray());
                            //recycle MemoryStream, avoids creating multiple = new MemoryStream();
                            mStream.SetLength(0); mStream.Position = 0;
                            //# 下一个pic重置
                            jpeg_count +=1; this_jpeg_len=0;
                            pos_in_Buffer = -1; //使其在递增部分变为0 pos
                        }
                    }
                    pos_in_Buffer += 1; //增量以在stdOut流中存储下一个字节
                } //# end While
            }
            else
            {
               // 这里的处理程序代码用于“进程未运行”的情况
            }
        } //end ExtractFrame function

    } //end class
} //end program