C#录制音频
github上有个开源的RecordWin的demo,代码生成能直接运行,这点是真的好,有的demo下载下来真的各种dll要自己下载,要么折腾半天要么最后运行不了,各位大佬们走点心啊。
原理就是用aforge这个组件录屏不带声音的,然后用naudio这个组件录音,最后用ffmpeg.exe把视频和声音组合到一起就可以了,有一个缺陷就是录屏最后一定要正常关闭,否则不但不会合成音频文件,原有没声音的文件也提示损坏,暂没找到方法;
这个也是找的几个开源中相对最好的,开源还有一个kogle.record的这个,功能也能实现,但录的声音杂音很大,感觉都不能用,还有就是内存占用太高了,能飚到1G多,这就很夸张了。
RecordWin中需要音频的在设置里把类型从mp4改成avi就可以了。
RecordWin项目下载地址: https://github.com/yangjinming1062/RecordWin,再次感谢这位大佬
demo是wpf写的,改成winform不难,里面改动好像就绘制鼠标那点,直接贴上我的代码吧,我的是winform里写的,供参考:
using AForge.Video; using AForge.Video.FFMPEG; using Common; using NAudio.Wave; using System; using System.Collections.Generic; using System.Configuration; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Eams_Form { public class VideoHelper { Timer videoTimer; System.Timers.Timer videoClearTimer; private int frameRate = 5; // 采集视频的帧频 private int videoQuality = 5; private readonly VideoFileWriter VideoWriter = new VideoFileWriter(); private ScreenCaptureStream VideoStreamer; private WaveInEvent AudioStreamer; private WaveFileWriter AudioWriter; private string CurrentVideoPath; private string CurrentAudioPath; private Screen CurrentScreen; private DateTime beginTime; /// <summary> /// 已经录制帧数:统计帧数计算时长,每凑够一个帧率的数时长加1s并重置回0 /// </summary> private int FrameCount; #region 鼠标捕获 [DllImport("user32.dll")] private static extern bool GetCursorInfo(out CURSORINFO pci); [StructLayout(LayoutKind.Sequential)] private struct POINT { public int x; public int y; } [StructLayout(LayoutKind.Sequential)] private struct CURSORINFO { public int cbSize; public int flags; public IntPtr hCursor; public POINT ptScreenPos; } #endregion public VideoHelper() { videoTimer = new Timer(); videoTimer.Tick += new EventHandler(RecordEventSave); //录屏音频保存 //videoTimer.Tick += new EventHandler(RecordEventDel); //录屏音频删除 videoTimer.Interval = 59800; videoClearTimer = new System.Timers.Timer(); videoClearTimer.Elapsed += RecordEventDel; videoClearTimer.Start(); Task.Run(()=> { DeleteMp4AndWav(); }); } /// <summary> /// 删除MP4和Wav /// </summary> private void DeleteMp4AndWav() { try { DirectoryInfo dyInfo = new DirectoryInfo(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "VideoFile"); //获取文件夹下所有的文件 foreach (FileInfo feInfo in dyInfo.GetFiles()) { if (feInfo.Extension.ToLower() == ".mp4" || feInfo.Extension.ToLower() == ".wav") { feInfo.Delete(); } } } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("删除MP4和wav文件出错", ex); } } /// <summary> /// 定时录屏音频保存 /// </summary> /// <param name="source"></param> /// <param name="e"></param> private void RecordEventSave(object source, EventArgs e) { try { if (DateTime.Now.Minute % 5 == 0) { RecordStop(); RecordStart(); } } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("录屏定时异常:", ex); } } /// <summary> /// 录屏音频删除 /// </summary> /// <param name="source"></param> /// <param name="e"></param> private void RecordEventDel(object source, EventArgs e) { try { if (DateTime.Now.Minute % 12 == 0) { DirectoryInfo dyInfo = new DirectoryInfo(System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "VideoFile"); //获取文件夹下所有的文件 foreach (FileInfo feInfo in dyInfo.GetFiles()) { //判断文件日期是否是指定小时前的,是则删除 int deletetime = Convert.ToInt32(ConfigurationManager.AppSettings["deletevideo"]); if (feInfo.CreationTime < DateTime.Now.AddHours(deletetime)) feInfo.Delete(); } LogHelper.getLogHelper().WriteLog("删除" + ConfigurationManager.AppSettings["deletevideo"] + "小时之前的视频文件"); } } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("定时删除录屏数据异常:", ex); } } /// <summary> /// 录屏开始 /// </summary> public void RecordStart() { try { #region 初始化录屏组件 int RecordWidth = 0, RecordHeight = 0, RecordTop = 0, RecordLeft = 0; foreach (var s in System.Windows.Forms.Screen.AllScreens) { RecordWidth += Math.Abs(s.Bounds.Width); if (Math.Abs(s.Bounds.Height) > RecordHeight) RecordHeight = Math.Abs(s.Bounds.Height); RecordLeft = Math.Min(s.Bounds.X, RecordLeft); RecordTop = Math.Min(s.Bounds.Y, RecordTop); } CurrentScreen = Screen.PrimaryScreen; //重编码体积更小,但清晰度受影响,不录制声音时直接输出MP4不再ffmpeg处理 CurrentVideoPath = AppDomain.CurrentDomain.BaseDirectory + "VideoFile\\video_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss") + ".mp4"; CurrentAudioPath = CurrentVideoPath.Replace(".mp4", ".wav");//使音频文件和视频文件同名 lock (this) { VideoWriter.Open(CurrentVideoPath, RecordWidth, RecordHeight, frameRate, VideoCodec.MPEG4, CurrentScreen.Bounds.Width * CurrentScreen.Bounds.Height * videoQuality); } Rectangle rec = new Rectangle(RecordLeft, RecordTop, RecordWidth, RecordHeight); VideoStreamer = new ScreenCaptureStream(rec, 1000 / frameRate);//帧间隔需要和帧率关联,不然录的10秒视频文件不是10s VideoStreamer.NewFrame += VideoNewFrameHandle; beginTime = DateTime.Now; VideoStreamer.Start(); AudioStreamer = new WaveInEvent(); AudioStreamer.DataAvailable += AudioDataAvailableHandle; AudioWriter = new WaveFileWriter(CurrentAudioPath, AudioStreamer.WaveFormat); AudioStreamer.StartRecording(); SystemConfig.IsRecording = true; #endregion videoTimer.Start(); } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("开启录屏出错", ex); } } private void AudioDataAvailableHandle(object sender, WaveInEventArgs e) { try { if (SystemConfig.IsRecording) { AudioWriter.Write(e.Buffer, 0, e.BytesRecorded); } } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("录音回调函数出错", ex); } } private void VideoNewFrameHandle(object sender, NewFrameEventArgs e) { try { if (SystemConfig.IsRecording) { TimeSpan time = DateTime.Now - beginTime; CURSORINFO pci; pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); GetCursorInfo(out pci); try { System.Windows.Forms.Cursor cur = new System.Windows.Forms.Cursor(pci.hCursor);//在每一帧上手动绘制鼠标 cur.Draw(Graphics.FromImage(e.Frame), new Rectangle(System.Windows.Forms.Cursor.Position.X - 10 - CurrentScreen.Bounds.X, System.Windows.Forms.Cursor.Position.Y - 10 - CurrentScreen.Bounds.Y, cur.Size.Width, cur.Size.Height)); } catch { }//打开任务管理器时会导致异常 VideoWriter.WriteVideoFrame(e.Frame, time);//处理视频时长和实际物理时长不符,用帧间隔时长的办法指定每一帧的间隔 FrameCount += 1; if (FrameCount == frameRate) { FrameCount = 0; } } } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("录屏每帧回调函数出错", ex); } } internal void RecordStop() { try { SystemConfig.IsRecording = false; videoTimer.Stop(); try { VideoStreamer.Stop();//.Net Core时该方法异常,统一都加一个异常捕获 } catch { } VideoWriter.Close(); AudioStreamer.StopRecording(); AudioStreamer.Dispose(); AudioWriter.Close(); //有视频有声音的时候再进行ffmpeg合成 string tempVideo = CurrentVideoPath; string addAudio = $"-i \"{CurrentAudioPath}\""; string tempAudio = CurrentAudioPath; System.Threading.Tasks.Task.Factory.StartNew(() => { string outfile = AppDomain.CurrentDomain.BaseDirectory + "VideoFile\\video_" + DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss") + ".avi"; Functions.CMD($"ffmpeg -i {tempVideo} {addAudio} -acodec copy {outfile} -crf 12"); DeleteFile(tempVideo, tempAudio, true, true); }); } catch (Exception ex) { LogHelper.getLogHelper().WriteLog("保存并合成avi视频文件出错", ex); } } /// <summary> /// 600秒内每1秒钟尝试一次删除指定原始音视频文件,直至全部删除 /// (防止因占用等原因导致没有一次删除成功) /// </summary> private void DeleteFile(string tempVideo, string tempAudio, bool DelVideo = true, bool DelAudio = true) { for (int i = 0; i < 600; i++) { try { if (File.Exists(tempVideo) && DelVideo) File.Delete(tempVideo); if (File.Exists(tempAudio) && DelAudio) File.Delete(tempAudio); break; } catch { System.Threading.Thread.Sleep(1000); } } } } }