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 麦克风声卡检测
}
}