C# 利用FFMpeg插件合并视频

  1. 下载FFMpeg插件  
    下载地址:https://www.gyan.dev/ffmpeg/builds/ 
    解压后需要用到bin文件夹里面的ffmpeg.exe这个文件

     

     准好好之后就可以开始撸代码,这里我是用winform做的一个小程序作测试用。
    把ffmpeg.exe拷到项目目录下,属性>复制到输出目录 设置成始终复制。
    准备两个MP4格式的视频,放到项目目录下 ,属性>复制到输出目录 设置成始终复制。

     

     

  2. 利用C# System.Diagnostics.Process类调用插件,实现合并视频的需求
    合并视频的步骤:
    先把多个MP4类型的文件转换成.ts格式的文件
    ffmpeg命令:-i file1.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb  ts1.ts -y
                     -i file2.mp4 -vcodec copy -acodec copy -vbsf h264_mp4toannexb  ts2.ts -y
    然后再合并这些.ts文件输出合并后的mp4文件
    ffmpeg命令:-i concat:ts1.ts|ts2.ts|tsN.ts -acodec copy -vcodec copy -absf aac_adtstoasc output.mp4 -y
    -y的作用是在文件重名的时候,覆盖掉之前的文件,或者像我代码里面写的那样,合并完之后删除之前生成的ts文件。

    Output方法是输出插件执行命令产生的的日志


            /// <summary>
            /// 开始
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btn_start_Click(object sender, EventArgs e)
            {
                var directoryPath = AppDomain.CurrentDomain.BaseDirectory;//程序的根目录
                string tsOne = new Random().Next(0, 1000).ToString() + ".ts";
                string tstwo = new Random().Next(0, 1000).ToString() + ".ts";
                //生成临时文件
                string command = $"-i {Path.Combine(directoryPath, "Video\\360p.mp4")} -vcodec copy -acodec copy -vbsf h264_mp4toannexb  {Path.Combine(directoryPath, tsOne)} -y";
      
                command += $" -i {Path.Combine(directoryPath, "Video\\360p2.mp4")} -vcodec copy -acodec copy -vbsf h264_mp4toannexb  {Path.Combine(directoryPath, tstwo)} -y";
                this.Execute(command);
                //合并临时文件并输出新的视频
                string outputFile = Guid.NewGuid().ToString() + ".mp4";
                string megreCommand = $"-i concat:{Path.Combine(directoryPath,tsOne)}|{Path.Combine(directoryPath, tstwo)} -acodec copy -vcodec copy -absf aac_adtstoasc {Path.Combine(directoryPath, outputFile)} -y";
                //合并放到后面执行是因为 ts文件转换需要时间,如果三行命令一起执行的话可能会导致ts文件还未转换完毕就开始合并ts文件,这会导致报找不到ts文件的错
                this.Execute(megreCommand);
                //删除生成的ts文件
                Task.Run(() =>
                {
                    if (File.Exists(tsOne)) File.Delete(Path.Combine(directoryPath, tsOne));
                    if (File.Exists(tstwo)) File.Delete(Path.Combine(directoryPath, tstwo));
                });
    
            }
    

      

            private void Execute(string command)
            {
                Process p = new Process();//建立外部调用线程
                p.StartInfo.FileName =AppDomain.CurrentDomain.BaseDirectory+ "\\ffmpeg-4.3.2-2021-02-27-essentials_build\\bin\\ffmpeg.exe";//要调用外部程序的绝对路径
                p.StartInfo.Arguments = command;//程序参数,字符串形式输入
                p.StartInfo.UseShellExecute = false;//不使用操作系统外壳程序启动线程(一定为FALSE,详细的请看MSDN)
                p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中(这个一定要注意,FFMPEG的所有输出信息,都为错误输出流,用StandardOutput是捕获不到任何消息的..
                p.StartInfo.CreateNoWindow = true;//不需要创建窗口 
                p.ErrorDataReceived += new DataReceivedEventHandler(Output);//外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中
                p.Start();//启动线程
                p.BeginErrorReadLine();//开始异步读取
                p.WaitForExit();//阻塞等待进程结束
                p.Close();//关闭进程
                p.Dispose();
            }
          /// <summary>
            /// 输出消息
            /// </summary>
            /// <param name="sendProcess"></param>
            /// <param name="output"></param>
            private void Output(object sendProcess, DataReceivedEventArgs output)
            {
                if (!string.IsNullOrEmpty(output.Data))
                {
                    WriteLog.AddLog(output.Data);
                }
            }
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace MegreVideo
    {
        public static class WriteLog
        {
            private static BlockingCollection<LogItem> logList = new BlockingCollection<LogItem>();
    
            static WriteLog()
            {
                new Thread(WriteFunc).Start();
            }
    
            private static void WriteFunc()
            {
                foreach (var item in logList.GetConsumingEnumerable())
                {
                    try
                    {
                        string dirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LogFiles", item.Type);
                        if (!Directory.Exists(dirPath))
                            Directory.CreateDirectory(dirPath);
                        string logpath = Path.Combine(dirPath, $"{DateTime.Now.ToString("yyyy-MM-dd")}.txt");
                        if (!System.IO.File.Exists(logpath))
                        {
                            var file = System.IO.File.Create(logpath);
                            file.Close();
                        }
                        string content = string.Format("{0} {1} \r\n", item.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"), item.Msg);
                        content += "------------------------------------------------------------\r\n";
                        System.IO.File.AppendAllText(logpath, content);
    
                    }
                    catch (Exception ex)
                    {
                        System.Threading.Thread.Sleep(1000);
                        logList.Add(item);
                    }
                }
            }
    
            public static void AddLog(string str, string type = "log")
            {
                logList.Add(new LogItem { Msg = str, Type = type });
            }
    
        }
    
        public class LogItem
        {
            public LogItem()
            {
                _createTime = DateTime.Now;
            }
    
    
            private DateTime _createTime;
            public DateTime CreateTime
            {
                get { return _createTime; }
            }
    
            private string _Type;
            public string Type
            {
                get { return _Type; }
                set { _Type = value; ; }
            }
    
    
            private string _msg;
    
            public string Msg
            {
                get { return _msg; }
                set { _msg = value; }
            }
    
        }
    }

     

    执行后,输出了拼接的视频

     

     

  3. 目前只尝试了MP4格式的,其他格式的视频暂时未做尝试。但是不同分辨率的视频也是可以完美拼接的,亲测360p与1080p可以拼接到一起,需要注意的是concat 命令 是根据文件的先后顺序进行拼接的,地址在前的,拼接后视频就在前面。
    项目地址:https://github.com/Heming666/MegreVideo
posted @ 2021-04-01 16:35  多情伤己  阅读(2545)  评论(0)    收藏  举报