namespace XHWK.WKTool.system { using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using AForge.Video.DirectShow; using Common.system; using VisioForge.Shared.NAudio.CoreAudioApi; using VisioForge.Shared.NAudio.Wave; /// /// ffmpeg帮助类 创建人:赵耀 创建时间::2020年8月22日 需要安装\SSCR\Setup Screen Capturer Recorder v0.12.10.exe /// 本地调试需要配置环境变量 将ffmpeg.exe位置配置到环境变量的path中 /// // ReSharper disable once InconsistentNaming public class FFMpeg { #region 变量 public Process myProcess; /// /// 是否输出录课录屏日志 /// public bool OutputVideoLog = FileToolsCommon.GetConfigValue("OutputVideoLog") != "0"; /// /// ffmpeg输出日志文件地址 每个文件2MB左右 /// private string _logPath = ""; /// /// 是否可以录制扬声器 /// private bool _isRecordSpeaker = true; /// /// 是否可以录制麦克风 /// private bool _isRecordMicrophone = true; private readonly string _ffmpegPath = FileToolsCommon.GetFileAbsolutePath(@"/ffmpeg.exe"); #endregion 变量 /// /// 录制屏幕 /// /// 文件存储路径 /// 大小 /// 录制音频 /// 录制麦克风 /// 错误信息 /// 麦克风名 /// public bool StartRecordingVideo ( string filePath, Size size, bool recordingSound, bool recordingMicrophone, out string errMessage, string microphoneName = null ) { while (myProcess != null) { Thread.Sleep(100); } errMessage = null; //路径 FileToolsCommon.GetDirectoryName(filePath); string extension = FileToolsCommon.GetIoExtension(filePath); //扩展名 //文件保存路径 string pathName = GetRsFilePathName(filePath); Process[] killProcessArray = Process.GetProcessesByName("ffmpeg"); foreach (Process killProcess in killProcessArray) { killProcess.Kill(); } myProcess = new Process(); _logPath = CreateffmpegLog(); if (string.IsNullOrWhiteSpace(microphoneName)) { microphoneName = GetMicrophoneName(); } #region 检测麦克风扬声器 string audioPath = FileToolsCommon.GetFileAbsolutePath("/temp/audio/"); FileToolsCommon.CreateDirectory(audioPath); string audioSpeakerPath = audioPath + "adoS" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".m"; //FileToolsCommon.GetFileAbsolutePath("adoS.m"); string audioMicrophonePath = audioPath + "adoM" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".m"; //FileToolsCommon.GetFileAbsolutePath("adoM.m"); try { FileToolsCommon.DeleteFile(audioSpeakerPath); FileToolsCommon.DeleteFile(audioMicrophonePath); } catch (Exception) { // ignored } //是否录制音频 if (recordingSound) { if (StartRecordSpeakerAudio(audioSpeakerPath)) { _isRecordSpeaker = true; } else { //无法录制扬声器音频 _isRecordSpeaker = false; } StopRecordAudio(2); Thread.Sleep(100); } //是否录制麦克风 if (recordingMicrophone) { if (StartRecordAudio(audioMicrophonePath)) { _isRecordMicrophone = true; } else { //无法录制麦克风 _isRecordMicrophone = false; } StopRecordAudio(1); } #endregion 检测麦克风扬声器 myProcess.StartInfo.FileName = _ffmpegPath; //ffmpeg.exe的绝对路径 string speakerStr = ""; string microphoneStr = ""; if (_isRecordSpeaker && recordingSound) { speakerStr = "-f dshow -i audio=\"virtual-audio-capturer\" "; } if (_isRecordMicrophone && recordingMicrophone) { if (!string.IsNullOrWhiteSpace(microphoneName)) { microphoneStr = "-f dshow -i audio=\"" + microphoneName + "\" -filter_complex amix=inputs=2:duration=first:dropout_transition=2 "; } } switch (extension.ToUpper()) { case ".MP4": myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac " + pathName; break; case ".AVI": case ".FLV": myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac -f " + extension.ToLower().Replace(".", "") + " " + pathName; break; default: myProcess.StartInfo.Arguments = speakerStr + microphoneStr + " -f gdigrab -framerate 6 -offset_x 0 -offset_y 0 -video_size " + size.Width + "x" + size.Height + " -i desktop -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 -vcodec libx264 -preset:v ultrafast -tune:v zerolatency -acodec aac " + pathName; break; } if (OutputVideoLog) { LogHelper.Loginfo.Info("【录屏】:" + myProcess.StartInfo.Arguments); } FileToolsCommon.AppendText(_logPath, "【录屏】:" + myProcess.StartInfo.Arguments + "\r\n"); myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 myProcess.ErrorDataReceived += Output; try { myProcess.Start(); myProcess.BeginErrorReadLine(); } catch (Exception ex) { if (ex.Message.Contains("访问")) { errMessage = "访问被拒绝,请关闭杀毒软件或新任此软件后重试!"; } else { errMessage = ex.Message + " 请关闭杀毒软件或信任此软件后重试!"; } LogHelper.Logerror.Error("【录音】(StartRecordingAudio)" + errMessage, ex); myProcess.Dispose(); myProcess = null; return false; } return true; } /// /// 视频剪辑 /// /// 视频路径 /// 开始时间 格式 HH:mm:ss /// 结束时间 格式 HH:mm:ss /// 保存文件路径 public void ClipVideo ( string videoFilePath, string startTime, string endTime, string saveFilePath ) { 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 = _ffmpegPath; //ffmpeg.exe的绝对路径 //ffmpeg -ss 开始时间 -to 结束时间 -accurate_seek -i mp4输入路径 -c:v libx264 -c:a aac -strict experimental -vf scale = 宽:高 -b 500k 输出路径.mp4 myProcess.StartInfo.Arguments = "-i " + videoFilePath + " -vcodec copy -acodec copy -ss " + startTime + " -to " + endTime + " " + saveFilePath; //-filter:a "volume=0.5" myProcess.StartInfo.UseShellExecute = false; //不使用操作系统外壳程序启动 myProcess.StartInfo.RedirectStandardError = true; //重定向标准错误输出 myProcess.StartInfo.CreateNoWindow = true; //不显示程序窗口 myProcess.StartInfo.RedirectStandardInput = true; //用于模拟该进程控制台的输入 //外部程序(这里是FFMPEG)输出流时候产生的事件,这里是把流的处理过程转移到下面的方法中,详细请查阅MSDN myProcess.ErrorDataReceived += Output; myProcess.Start(); //启动线程 myProcess.BeginErrorReadLine(); //开始异步读取 myProcess.WaitForExit(); //阻塞等待进程结束 myProcess.Close(); //关闭进程 myProcess.Dispose(); //释放资源 #region 进程是否已经释放 bool isRunning = true; while (isRunning) { isRunning = false; Process[] processArray = Process.GetProcessesByName("ffmpeg"); if (processArray.Length > 0) { isRunning = true; Thread.Sleep(100); } } #endregion 进程是否已经释放 myProcess = null; } /// /// 获取文件完整路径 录屏 /// /// 文件路径 /// private string GetRsFilePathName(string filePath) { //路径 string path = FileToolsCommon.GetDirectoryName(filePath); //Path.GetFileName(FilePath);//获取文件名 string extension = FileToolsCommon.GetIoExtension(filePath); //扩展名 string tempFilePath = path + "_temppath/"; //创建临时目录 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; } /// /// 创建日志文件 /// /// private 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, "【" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss fff") + "】:"); FileToolsCommon.AppendText(_logPath, output.Data + "\r\n"); } } #region 麦克风声卡检测 #region 获取麦克风 /// /// 获取麦克风名称 /// /// public string GetMicrophoneName() { return GetAudioInDevices(); } /// /// AForge获取麦克风 /// /// public static string GetAudioInDevices() { List devicesList = new List(); try { // ReSharper disable once CollectionNeverUpdated.Local FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice); foreach (FilterInfo device in videoDevices) { devicesList.Add(device.Name); } } catch (ApplicationException) { //Console.WriteLine("No local capture devices"); } if (devicesList.Count > 1) { return devicesList[1]; } return ""; } /// /// AForge获取麦克风列表 /// /// public static List GetAudioInDevicesList() { List devicesList = new List(); try { // ReSharper disable once CollectionNeverUpdated.Local FilterInfoCollection videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice); foreach (FilterInfo device in videoDevices) { if (device.Name == "virtual-audio-capturer") { continue; } devicesList.Add(device.Name); } } catch (ApplicationException) { //Console.WriteLine("No local capture devices"); } return devicesList; } /// /// 获取声音输入设备名称 不完整 /// /// public static string GetInDeviceName() { List devices = new List(); int waveInDevices = WaveIn.DeviceCount; for (int i = 0; i < waveInDevices; i++) { devices.Add(WaveIn.GetCapabilities(i)); } var devs = devices.Select(item => item.ProductName).ToList(); if (devs.Count > 0) { byte[] buffer = Encoding.UTF8.GetBytes(devs[0]); string microphoneName = Encoding.UTF8.GetString(buffer); // 统一使用UTF-8 return microphoneName; } else { return ""; } } /// /// 获取声音输入设备完整名称 /// /// public static string GetInDeviceFull() { List devices = 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)) { devices.Add(device.FriendlyName); break; } } catch (Exception) { // ignored } } } if (devices.Count > 0) { byte[] buffer = Encoding.UTF8.GetBytes(devices[0]); string microphoneName = Encoding.UTF8.GetString(buffer); // 统一使用UTF-8 return microphoneName; } else { return ""; } } /// /// 获取声音输入设备完整名称列表 /// /// public List GetInDeviceListFull() { List devices = 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)) { devices.Add(device.FriendlyName); break; } } catch (Exception) { // ignored } } } return devices; } #endregion 获取麦克风 #region 录音 /// /// 开始录制麦克风 /// public bool StartAudio(string audioFile, string deviceName = "") { try { int deviceIndex = 0; _waveIn = new WaveInEvent(); List inDeviceList = GetInDeviceListFull(); if (inDeviceList.Exists(x => deviceName.Contains(x))) { deviceIndex = inDeviceList.FindIndex(x => deviceName.Contains(x)); } _waveIn.DeviceNumber = deviceIndex; //waveIn.DeviceNumber = 1; //生成音频文件的对象 WaveFileWriter writer = new WaveFileWriter(audioFile, _waveIn.WaveFormat); //开始录音,写数据 _waveIn.DataAvailable += (s, a) => { writer.Write ( a.Buffer, 0, a.BytesRecorded ); }; return true; } catch (Exception ex) { LogHelper.Logerror.Error("【麦克风】麦克风不可用:" + ex.Message, ex); return false; } } #endregion 录音 #region 检测是否可用 /// /// 录制麦克风的声音 /// private WaveInEvent _waveIn; //new WaveInEvent(); /// /// 开始录制麦克风 /// public bool StartRecordAudio ( string audioFile, string deviceName = "", bool isStopDelFile = true ) { try { int deviceIndex = 0; _waveIn = new WaveInEvent(); List inDeviceList = GetInDeviceListFull(); if (inDeviceList.Exists(x => deviceName.Contains(x))) { deviceIndex = inDeviceList.FindIndex(x => deviceName.Contains(x)); } _waveIn.DeviceNumber = deviceIndex; //waveIn.DeviceNumber = 1; //生成音频文件的对象 WaveFileWriter writer = new WaveFileWriter(audioFile, _waveIn.WaveFormat); //开始录音,写数据 _waveIn.DataAvailable += (s, a) => { writer.Write ( a.Buffer, 0, a.BytesRecorded ); short s1 = BitConverter.ToInt16(a.Buffer, 0); //这样采样比较少,反正是int16型的 int s2 = Math.Abs(s1 / 50); Console.WriteLine(@"声音大小:" + s2); }; //结束录音 _waveIn.RecordingStopped += (s, a) => { writer.Dispose(); writer = null; _waveIn.Dispose(); _waveIn = null; try { if (isStopDelFile) { FileToolsCommon.DeleteFile(audioFile); } } catch (Exception) { // ignored } }; _waveIn.StartRecording(); return true; } catch (Exception ex) { LogHelper.Logerror.Error("【麦克风】麦克风不可用:" + ex.Message, ex); return false; } } /// /// 结束录制音频 /// /// 1麦克风 2扬声器 public void StopRecordAudio(int type) { try { if (type == 1) { if (_waveIn != null) { try { _waveIn.StopRecording(); } catch (Exception) { _waveIn = null; } } } else if (type == 2) { if (_capture != null) { try { _capture.StopRecording(); } catch (Exception) { _capture = null; } } } } catch (Exception ex) { LogHelper.Logerror.Error("【麦克风】麦克风不可用:" + ex.Message, ex); } } /// /// 录制扬声器的声音 /// private WasapiLoopbackCapture _capture; //new WasapiLoopbackCapture(); /// /// 开始录制扬声器 /// public bool StartRecordSpeakerAudio(string audioFile) { try { _capture = new WasapiLoopbackCapture(); //生成音频文件的对象 WaveFileWriter writer = new WaveFileWriter(audioFile, _capture.WaveFormat); _capture.DataAvailable += (s, a) => { writer.Write ( a.Buffer, 0, a.BytesRecorded ); }; //结束录音 _capture.RecordingStopped += (s, a) => { writer.Dispose(); writer = null; _capture.Dispose(); _capture = null; try { FileToolsCommon.DeleteFile(audioFile); } catch (Exception) { // ignored } }; _capture.StartRecording(); return true; } catch (Exception ex) { LogHelper.Logerror.Error("【扬声器】扬声器不可用:" + ex.Message, ex); return false; } } #endregion 检测是否可用 #endregion 麦克风声卡检测 } }