using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using VisioForge.Shared.NAudio.CoreAudioApi; using VisioForge.Shared.NAudio.Wave; namespace Common.system { /// /// ffmpeg帮助类 /// 创建人:赵耀 /// 创建时间::2020年8月22日 /// 需要安装\ffmpeg\bin\Setup Screen Capturer Recorder v0.12.10.exe /// 本地调试需要配置环境变量 将ffmpeg.exe位置配置到环境变量的path中 /// public class FFMpeg { public Process myProcess = null; /// /// ffmpeg输出日志文件地址 每个文件2MB左右 /// string LogPath = ""; /// /// 录制屏幕 /// /// 文件存储路径 public void StartRecordingVideo(string FilePath) { while (myProcess != null) { Thread.Sleep(100); } //路径 string Path = FileToolsCommon.GetDirectoryName(FilePath); string Extension = FileToolsCommon.GetIOExtension(FilePath);//扩展名 //文件保存路径 string PathName = GetRSFilePathName(FilePath); Process[] KillProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in KillProcessArray) { KillProcess.Kill(); } myProcess = new Process(); LogPath = CreateffmpegLog(); string MicrophoneName = null; string audioTestPath = FileToolsCommon.GetFileAbsolutePath("ado.m"); try { FileToolsCommon.DeleteFile(audioTestPath); } catch (Exception) { } if (StartRecordAudio(audioTestPath)) { MicrophoneName = GetMicrophone(); } StopRecordAudio(); myProcess.StartInfo.FileName = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg/bin/ffmpeg.exe"); //ffmpeg.exe的绝对路径 switch (Extension.ToUpper()) { case "MP4": if (string.IsNullOrWhiteSpace(MicrophoneName)) { myProcess.StartInfo.Arguments = "-f dshow -i video=\"screen-capture-recorder\" -f dshow -i audio=\"virtual-audio-capturer\" -vcodec libx264 -acodec libmp3lame -r 15 -crf 22 -f avi " + PathName; //ffmpeg的参数 } else { myProcess.StartInfo.Arguments = "-f dshow -i audio=\"virtual-audio-capturer\" -f dshow -i audio=\"" + MicrophoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 -f dshow -i video=\"screen-capture-recorder\" -pix_fmt yuv420p -vcodec h264 -preset:v ultrafast -tune:v zerolatency -acodec aac -ar 44100 -ac 2 " + PathName; //ffmpeg的参数 } break; case "AVI": case "FLV": if (string.IsNullOrWhiteSpace(MicrophoneName)) { myProcess.StartInfo.Arguments = "-f dshow -i video=\"screen-capture-recorder\" -f dshow -i audio=\"virtual-audio-capturer\" -vcodec libx264 -acodec libmp3lame -r 15 -crf 22 -f " + Extension.ToLower() + " " + PathName; //ffmpeg的参数 } else { myProcess.StartInfo.Arguments = "-f dshow -i audio=\"virtual-audio-capturer\" -f dshow -i audio=\"" + MicrophoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 -f dshow -i video=\"screen-capture-recorder\" -pix_fmt yuv420p -vcodec h264 -preset:v ultrafast -tune:v zerolatency -acodec aac -ar 44100 -ac 2 -f " + Extension.ToLower() + " " + PathName; //ffmpeg的参数 } break; default: if (string.IsNullOrWhiteSpace(MicrophoneName)) { myProcess.StartInfo.Arguments = "-f dshow -i video=\"screen-capture-recorder\" -f dshow -i audio=\"virtual-audio-capturer\" -vcodec libx264 -acodec libmp3lame -r 15 -crf 22 -f avi " + PathName; //ffmpeg的参数 } else { myProcess.StartInfo.Arguments = "-f dshow -i audio=\"virtual-audio-capturer\" -f dshow -i audio=\"" + MicrophoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 -f dshow -i video=\"screen-capture-recorder\" -pix_fmt yuv420p -vcodec h264 -preset:v ultrafast -tune:v zerolatency -acodec aac -ar 44100 -ac 2 " + PathName; //ffmpeg的参数 } break; } myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 myProcess.ErrorDataReceived += new DataReceivedEventHandler(Output); myProcess.Start(); myProcess.BeginErrorReadLine(); } /// /// 录制音频 /// /// MP3音频位置 public void StartRecordingAudio(string Mp3Path) { while (myProcess != null) { Thread.Sleep(100); } //路径 string Path = FileToolsCommon.GetDirectoryName(Mp3Path); //文件保存路径 string PathName = GetFilePathName(Mp3Path); Process[] KillProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in KillProcessArray) { KillProcess.Kill(); } myProcess = new Process(); LogPath = CreateffmpegLog(); this.myProcess.StartInfo.FileName = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg/bin/ffmpeg.exe"); //ffmpeg.exe的绝对路径 string MicrophoneName = null; string audioTestPath = FileToolsCommon.GetFileAbsolutePath("ado.m"); try { FileToolsCommon.DeleteFile(audioTestPath); } catch (Exception) { } if (StartRecordAudio(audioTestPath)) { MicrophoneName = GetMicrophone(); } StopRecordAudio(); if (string.IsNullOrWhiteSpace(MicrophoneName)) { myProcess.StartInfo.Arguments = "-f dshow -i audio=\"virtual-audio-capturer\" " + PathName; } else { myProcess.StartInfo.Arguments = "-f dshow -i audio=\"virtual-audio-capturer\" -f dshow -i audio=\"" + MicrophoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 " + PathName; } myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN myProcess.ErrorDataReceived += new DataReceivedEventHandler(Output); myProcess.Start(); //启动线程 myProcess.BeginErrorReadLine(); //开始异步读取 } /// /// 暂停录制 /// public bool SuspendFFmpeg() { if (myProcess != null) { myProcess.StandardInput.WriteLine("q");//在这个进程的控制台中模拟输入q,用于暂停录制 myProcess.Close();//关闭进程 myProcess.Dispose();//释放资源 } #region 进程是否已经释放 bool IsRunning = true; while (IsRunning) { IsRunning = false; Process[] ProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in ProcessArray) { IsRunning = true; Thread.Sleep(100); } } #endregion myProcess = null; return true; //myProcess.Kill(); } /// /// 停止录制并合成文件 /// /// 文件存储路径 必须与开始时的路径完全一致 public bool StopFFmpeg(string FilePath) { //文件完整路径 if (myProcess != null) { myProcess.StandardInput.WriteLine("q");//在这个进程的控制台中模拟输入q,用于暂停录制 myProcess.Close();//关闭进程 myProcess.Dispose();//释放资源 } #region 进程是否已经释放 bool IsRunning = true; while (IsRunning) { IsRunning = false; Process[] ProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in ProcessArray) { IsRunning = true; Thread.Sleep(100); } } #endregion myProcess = null; Thread t1 = new Thread(MergeVideoOrAudio); t1.Start(FilePath); return true; //文件合成 } /// /// 图片音频合成视频 /// /// 图片列表位置 命名为1.png 2.png /// MP3音频位置 /// 视频保存位置 /// 每秒帧率 /// 视频宽度 /// 视频高度 public void SynthesisVideo(string ImageListPath, string Mp3Path, string VideoSavePath, int Frequency, int VideoWidth, int VideoHeight) { //new Thread(new ThreadStart(new Action(() => //{ //}))).Start(); while (myProcess != null) { Thread.Sleep(100); } string Extension = FileToolsCommon.GetIOExtension(VideoSavePath);//扩展名 Process[] KillProcessArray = Process.GetProcessesByName("ffmpeg"); //Debug.WriteLine(KillProcessArray.Length.ToString()); foreach (var KillProcess in KillProcessArray) { KillProcess.Kill(); } myProcess = new Process(); LogPath = CreateffmpegLog(); this.myProcess.StartInfo.FileName = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg/bin/ffmpeg.exe"); //ffmpeg.exe的绝对路径 switch (Extension.ToUpper()) { case "MP4": myProcess.StartInfo.Arguments = @"-y -r " + Frequency + " -i " + ImageListPath + @"%d.png -i " + Mp3Path + @" -s " + VideoWidth + "x" + VideoHeight + " -vcodec mpeg4 " + VideoSavePath; break; case "AVI": myProcess.StartInfo.Arguments = @"-y -r " + Frequency + " -i " + ImageListPath + @"%d.png -i " + Mp3Path + @" -s " + VideoWidth + "x" + VideoHeight + " -f AVI " + VideoSavePath; break; case "FLV": myProcess.StartInfo.Arguments = @"-y -r " + Frequency + " -i " + ImageListPath + @"%d.png -i " + Mp3Path + @" -s " + VideoWidth + "x" + VideoHeight + " -f FLV " + VideoSavePath; break; default: myProcess.StartInfo.Arguments = @"-y -r " + Frequency + " -i " + ImageListPath + @"%d.png -i " + Mp3Path + @" -s " + VideoWidth + "x" + VideoHeight + " -vcodec mpeg4 " + VideoSavePath; break; } myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN myProcess.ErrorDataReceived += new DataReceivedEventHandler(Output); myProcess.Start(); //启动线程 myProcess.BeginErrorReadLine(); //开始异步读取 myProcess.WaitForExit(); //阻塞等待进程结束 myProcess.Close(); //关闭进程 myProcess.Dispose(); //释放资源 #region 进程是否已经释放 bool IsRunning = true; while (IsRunning) { IsRunning = false; Process[] ProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in ProcessArray) { IsRunning = true; Thread.Sleep(100); } } #endregion myProcess = null; //生成视频后删除图片和音频保存列表 //FileToolsCommon.DeleteDirectory(ImageListPath); //FileToolsCommon.DeleteDirectory(FileToolsCommon.GetDirectoryName(Mp3Path)); Thread.Sleep(100); FileToolsCommon.DeleteDirectory(FileToolsCommon.GetDirectoryName(VideoSavePath) + "temp"); //Dispatcher.Invoke(() => //{ //}); //// 允许不同线程间的调用 //Control.CheckForIllegalCrossThreadCalls = false; } /// /// 视频音频合成 /// /// 存储路径 public void MergeVideoOrAudio(object FilePathobj) { //new Thread(new ThreadStart(new Action(() => //{ //Dispatcher.Invoke(() => //{ //}); //}))).Start(); while (myProcess != null) { Thread.Sleep(100); } //路径 string FilePath = (string)FilePathobj; string Path = FileToolsCommon.GetDirectoryName(FilePath); string Extension = FileToolsCommon.GetIOExtension(FilePath);//扩展名 //Process[] KillProcessArray = Process.GetProcessesByName("ffmpeg"); //foreach (var KillProcess in KillProcessArray) //{ // KillProcess.Kill(); //} myProcess = new Process(); LogPath = CreateffmpegLog(); this.myProcess.StartInfo.FileName = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg/bin/ffmpeg.exe"); //ffmpeg.exe的绝对路径 if (Extension.ToUpper() == ".MP3") { myProcess.StartInfo.Arguments = "-f concat -safe 0 -i " + Path + "temp/filelist.d -c copy " + FilePath; } else { myProcess.StartInfo.Arguments = "-f concat -safe 0 -i " + Path + "temprs/filelist.d -c copy " + FilePath; } myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN myProcess.ErrorDataReceived += new DataReceivedEventHandler(Output); myProcess.Start(); //启动线程 myProcess.BeginErrorReadLine(); //开始异步读取 myProcess.WaitForExit(); //阻塞等待进程结束 myProcess.Close(); //关闭进程 myProcess.Dispose(); //释放资源 #region 进程是否已经释放 bool IsRunning = true; while (IsRunning) { IsRunning = false; Process[] ProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in ProcessArray) { IsRunning = true; Thread.Sleep(100); } } #endregion myProcess = null; if (Extension.ToUpper() == ".MP3") { FileToolsCommon.DeleteDirectory(Path + "temp/"); } else FileToolsCommon.DeleteDirectory(Path + "temprs/"); } /// /// 获取文件完整路径 音频 /// /// 文件路径 /// string GetFilePathName(string FilePath) { //路径 string Path = FileToolsCommon.GetDirectoryName(FilePath); //Path.GetFileName(FilePath);//获取文件名 string Extension = FileToolsCommon.GetIOExtension(FilePath);//扩展名 string tempFilePath = Path + "temp/"; //创建临时目录 FileToolsCommon.CreateDirectory(tempFilePath); //创建记录文件 if (!FileToolsCommon.IsExistFile(tempFilePath + "filelist.d")) { FileToolsCommon.CreateFile(tempFilePath + "filelist.d"); } int num = 1; string CompleteFilePath = tempFilePath + num + Extension; while (FileToolsCommon.IsExistFile(CompleteFilePath)) { num++; CompleteFilePath = tempFilePath + num + Extension; } FileToolsCommon.AppendText(tempFilePath + "filelist.d", "file ./" + num + Extension + "\r\n"); return CompleteFilePath; } /// /// 获取文件完整路径 录屏 /// /// 文件路径 /// string GetRSFilePathName(string FilePath) { //路径 string Path = FileToolsCommon.GetDirectoryName(FilePath); //Path.GetFileName(FilePath);//获取文件名 string Extension = FileToolsCommon.GetIOExtension(FilePath);//扩展名 string tempFilePath = Path + "temprs/"; //创建临时目录 FileToolsCommon.CreateDirectory(tempFilePath); //创建记录文件 if (!FileToolsCommon.IsExistFile(tempFilePath + "filelist.d")) { FileToolsCommon.CreateFile(tempFilePath + "filelist.d"); } int num = 1; string CompleteFilePath = tempFilePath + num + Extension; while (FileToolsCommon.IsExistFile(CompleteFilePath)) { num++; CompleteFilePath = tempFilePath + num + Extension; } FileToolsCommon.AppendText(tempFilePath + "filelist.d", "file ./" + num + Extension + "\r\n"); return CompleteFilePath; } /// /// 生成缩略图 /// /// 视频地址 /// 图片地址 public void GenerateThumbnails(string VideoPath, string ImagePath) { while (myProcess != null) { Thread.Sleep(100); } Process[] KillProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in KillProcessArray) { KillProcess.Kill(); } myProcess = new Process(); LogPath = CreateffmpegLog(); this.myProcess.StartInfo.FileName = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg/bin/ffmpeg.exe"); //ffmpeg.exe的绝对路径 //myProcess.StartInfo.Arguments = "-i \"" + VideoPath + "\" -ss 1 -vframes 1 -r 1 -ac 1 -ab 2 -s " + thubWidth + "*" + thubHeight + " -f image2 \"" + ImagePath + "\""; myProcess.StartInfo.Arguments = "-i \"" + VideoPath + "\" -ss 1 -vframes 1 -r 1 -ac 1 -ab 2 -f image2 \"" + ImagePath + "\""; myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN myProcess.ErrorDataReceived += new DataReceivedEventHandler(Output); myProcess.Start(); //启动线程 myProcess.BeginErrorReadLine(); //开始异步读取 myProcess.WaitForExit(); //阻塞等待进程结束 myProcess.Close(); //关闭进程 myProcess.Dispose(); //释放资源 #region 进程是否已经释放 bool IsRunning = true; while (IsRunning) { IsRunning = false; Process[] ProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (var KillProcess in ProcessArray) { IsRunning = true; Thread.Sleep(100); } } #endregion myProcess = null; } /// /// 创建日志文件 /// /// string CreateffmpegLog() { string LogFileName = DateTime.Now.ToString("yyyyMMdd"); string LogFilePath = FileToolsCommon.GetFileAbsolutePath("/Log/FFMpegLog/"); FileToolsCommon.CreateDirectory(LogFilePath); string LogName = LogFilePath + LogFileName + ".log"; try { if (!FileToolsCommon.IsExistFile(LogName)) { FileToolsCommon.CreateFile(LogName); FileToolsCommon.AppendText(LogName, "\r\n****************************************************************************************************" + "****************************************************************************************************\r\n"); return LogName; } else { int num = 0; while (FileToolsCommon.GetFileSizeByMB(LogName) > 2) { num++; LogName = LogFilePath + LogFileName + "_" + num + ".log"; FileToolsCommon.CreateFile(LogName); } FileToolsCommon.AppendText(LogName, "\r\n****************************************************************************************************" + "****************************************************************************************************\r\n"); return LogName; } } catch (Exception) { return LogName; } } /// /// 输出结果 /// /// /// private void Output(object sendProcess, DataReceivedEventArgs output) { if (!String.IsNullOrEmpty(output.Data)) { FileToolsCommon.AppendText(LogPath, output.Data); } } //private void P_ErrorDataReceived(object sender, DataReceivedEventArgs e) //{ // //+= new DataReceivedEventHandler((s, message) => { Console.WriteLine(message.Data); }) // using (StreamWriter fs = new StreamWriter("E:\\项目\\测试\\Wpf测试\\bin\\Debug\\ffmpeg\\log.txt", true)) // { // fs.WriteLine(e.Data); // Debug.WriteLine(output.Data.ToString()); // } //} /// /// 获取麦克风 /// string GetMicrophone() { List devs = new List(); MMDeviceEnumerator enumberator = new MMDeviceEnumerator(); MMDeviceCollection deviceCollection = enumberator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All); for (int waveInDevice = 0; waveInDevice < WaveIn.DeviceCount; waveInDevice++) { WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice); foreach (MMDevice device in deviceCollection) { try { if (device.FriendlyName.StartsWith(deviceInfo.ProductName)) { devs.Add(device.FriendlyName); break; } } catch (Exception) { } } } if (devs.Count > 0) { return devs[0]; } else { return ""; } } /// /// 获取麦克风列表 /// public List GetMicrophoneList() { List devs = new List(); MMDeviceEnumerator enumberator = new MMDeviceEnumerator(); MMDeviceCollection deviceCollection = enumberator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.All); for (int waveInDevice = 0; waveInDevice < WaveIn.DeviceCount; waveInDevice++) { WaveInCapabilities deviceInfo = WaveIn.GetCapabilities(waveInDevice); foreach (MMDevice device in deviceCollection) { try { if (device.FriendlyName.StartsWith(deviceInfo.ProductName)) { devs.Add(device.FriendlyName); break; } } catch (Exception) { } } } return devs; } //录制麦克风的声音 WaveInEvent waveIn = null; //new WaveInEvent(); /// /// 开始录制 /// public bool StartRecordAudio(string audioFile) { try { waveIn = new WaveInEvent(); //生成音频文件的对象 WaveFileWriter writer = new WaveFileWriter(audioFile, waveIn.WaveFormat); //开始录音,写数据 waveIn.DataAvailable += (s, a) => { writer.Write(a.Buffer, 0, a.BytesRecorded); }; //结束录音 waveIn.RecordingStopped += (s, a) => { writer.Dispose(); writer = null; waveIn.Dispose(); }; waveIn.StartRecording(); return true; } catch (Exception ex) { LogHelper.WriteErrLog("【麦克风】麦克风不可用:" + ex.Message, ex); return false; } } //结束录制 public void StopRecordAudio() { try { waveIn.StopRecording(); } catch (Exception ex) { LogHelper.WriteErrLog("【麦克风】麦克风不可用:" + ex.Message, ex); } } } }