13388942201 发表于 2025-2-18 17:20:08

求助 用C#写了个录屏软件 合成音视频问题

用C#写了个录屏软件 最后音视频合并这块直接难住了 看了一圈 简单的是安装ffmpeg 实现关键是折腾了几天 装了fmpeg 也没实现 代码我直接发出来 留给那个大神来解决吧。现在的问题是点击合成后 进度一直卡在0%能不用ffmpeg 更好,可以大大减少问价大小。
using System;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Windows.Forms;
using Accord.Video.FFMPEG;
using NAudio.CoreAudioApi;
using NAudio.Wave;
using System.Collections.Generic;
using MediaToolkit;
using MediaToolkit.Model;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using System.Threading.Tasks;
using System.Linq;

namespace ScreenRecorder
{
    public partial class Form1 : Form
    {
      private VideoFileWriter writer;
      private Thread captureThread;
      private Thread timerThread;
      private Thread frameInsertThread;
      private Thread recordingThread;
      private Thread audioRecordingThread;
      private bool isRecording = false;
      private string outputFilePath;
      private Stopwatch stopwatch;
      private bool isWriterClosed = true;
      private CancellationTokenSource cancellationTokenSource;
      private readonly object writerLock = new object();
      private int targetFrameRate = 30;
      private int actualFrameCount = 0;
      private Bitmap lastFrame;
      private int framesToAdd = 0;
      private long lastFrameTimestamp = 0;

      private WasapiLoopbackCapture loopbackCapture; // 用于捕获电脑声音
      private WasapiCapture microphoneCapture; // 用于捕获麦克风声音
      private WaveFileWriter loopbackWriter;
      private WaveFileWriter microphoneWriter;
      private List<MMDevice> outputDevices = new List<MMDevice>();
      private List<MMDevice> inputDevices = new List<MMDevice>();

      public Form1()
      {
            InitializeComponent();
            string folderPath = Path.Combine(Application.StartupPath, "录像");
            if (!Directory.Exists(folderPath))
            {
                Directory.CreateDirectory(folderPath);
            }

            comboQuality.Items.AddRange(new string[] { "低", "中", "高" });
            comboQuality.SelectedIndex = 1;

            comboFrameRate.Items.AddRange(new string[] { "15", "20", "25", "30", "60" });
            comboFrameRate.SelectedIndex = 3;

            // 添加音频品质选项
            comboAudioQuality.Items.AddRange(new string[] { "低", "中", "高" });
            comboAudioQuality.SelectedIndex = 0;

            lblRecordingTime.Text = "录制时间: 00:00:00";
            this.Text = "屏幕录制器";
            // 枚举音频设备
            EnumerateAudioDevices();
      }

      private void EnumerateAudioDevices()
      {
            MMDeviceEnumerator enumerator = new MMDeviceEnumerator();

            // 枚举音频输出设备
            foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active))
            {
                outputDevices.Add(device);
                cmbOutputDevices.Items.Add(device.FriendlyName);
            }

            if (outputDevices.Count == 0)
            {
                cmbOutputDevices.Items.Add("没有找到相应的设备");
                cmbOutputDevices.Enabled = false;
                cmbOutputDevices.SelectedIndex = 0; // 设置默认选中提示项
                chkEnableOutput.Enabled = false;
            }
            else
            {
                cmbOutputDevices.SelectedIndex = 0;
                chkEnableOutput.Checked = true;
            }

            // 枚举音频输入设备
            foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active))
            {
                inputDevices.Add(device);
                cmbInputDevices.Items.Add(device.FriendlyName);
            }

            if (inputDevices.Count == 0)
            {
                cmbInputDevices.Items.Add("没有找到相应的设备");
                cmbInputDevices.Enabled = false;
                cmbInputDevices.SelectedIndex = 0; // 设置默认选中提示项
                chkEnableInput.Enabled = false;
            }
            else
            {
                cmbInputDevices.SelectedIndex = 0;
                chkEnableInput.Checked = true;
            }
      }

      private void btnStart_Click(object sender, EventArgs e)
      {
            if (!isRecording)
            {
                try
                {
                  cancellationTokenSource = new CancellationTokenSource();
                  targetFrameRate = int.Parse(comboFrameRate.SelectedItem.ToString());
                  int bitrate = GetBitrate(comboQuality.SelectedItem.ToString());

                  int captureWidth = Screen.PrimaryScreen.Bounds.Width;
                  int captureHeight = Screen.PrimaryScreen.Bounds.Height;

                  string fileName = $"Loopback_{DateTime.Now:yyyyMMdd_HHmmss}.mp4";
                  outputFilePath = Path.Combine(Application.StartupPath, "录像", fileName);

                  writer = new VideoFileWriter();
                  writer.Open(outputFilePath, captureWidth, captureHeight, targetFrameRate, VideoCodec.MPEG4, bitrate);
                  isWriterClosed = false;

                  isRecording = true;
                  stopwatch = Stopwatch.StartNew();

                  captureThread = new Thread(() => CaptureScreen(captureWidth, captureHeight));
                  timerThread = new Thread(UpdateTimer);
                  frameInsertThread = new Thread(InsertFrames);
                  recordingThread = new Thread(RecordVideo);
                  audioRecordingThread = new Thread(StartAudioRecording); // 创建音频录制线程

                  captureThread.Start();
                  timerThread.Start();
                  frameInsertThread.Start();
                  recordingThread.Start();
                  audioRecordingThread.Start(); // 启动音频录制线程
                }
                catch (Exception ex)
                {
                  MessageBox.Show($"开始录屏时出现错误: {ex.Message}");
                  isRecording = false;
                  isWriterClosed = true;
                  writer = null;
                }
            }
      }

      private void btnStop_Click(object sender, EventArgs e)
      {
            if (isRecording)
            {
                try
                {
                  cancellationTokenSource.Cancel();
                  captureThread.Join();
                  timerThread.Join();
                  frameInsertThread.Join();
                  recordingThread.Join();
                  audioRecordingThread.Join(); // 等待音频录制线程结束

                  if (writer != null && !isWriterClosed)
                  {
                        CompensateFrames();
                        writer.Close();
                        writer.Dispose();
                        writer = null;
                        isWriterClosed = true;
                  }

                  // 停止音频录制
                  StopAudioRecording();

                  MessageBox.Show("录屏已停止,文件保存为 " + outputFilePath);
                }
                catch (Exception ex)
                {
                  MessageBox.Show($"关闭视频文件写入器时出现错误: {ex.Message}");
                }
                finally
                {
                  isRecording = false;
                  lastFrame?.Dispose();
                  lastFrame = null;
                  actualFrameCount = 0;
                }
            }
      }

      private void StartAudioRecording()
      {
            // 确保在主线程上访问控件
            if (comboAudioQuality.InvokeRequired)
            {
                comboAudioQuality.Invoke(new Action(StartAudioRecording));
                return; // 退出当前方法,等待主线程完成
            }
            var audioQuality = comboAudioQuality.SelectedItem.ToString();
            var waveFormat = GetWaveFormat(audioQuality);

            if (chkEnableOutput.Checked)
            {
                // 确保在主线程上访问控件
                if (cmbOutputDevices.InvokeRequired)
                {
                  cmbOutputDevices.Invoke(new Action(() => StartAudioRecording()));
                  return; // 退出当前方法,等待主线程完成
                }

                // 处理音频输出录制
                var loopbackDevice = outputDevices;
                loopbackCapture = new WasapiLoopbackCapture(loopbackDevice);
                loopbackCapture.WaveFormat = waveFormat;

                string loopbackFileName = $"Loopback_{DateTime.Now:yyyyMMdd_HHmmss}.wav";
                string loopbackOutputPath = Path.Combine(Application.StartupPath, "录像", loopbackFileName);
                loopbackWriter = new WaveFileWriter(loopbackOutputPath, loopbackCapture.WaveFormat);
                loopbackCapture.DataAvailable += LoopbackCapture_DataAvailable;
                loopbackCapture.RecordingStopped += LoopbackCapture_RecordingStopped;
                loopbackCapture.StartRecording();
            }

            if (chkEnableInput.Checked)
            {
                // 确保在主线程上访问控件
                if (cmbInputDevices.InvokeRequired)
                {
                  cmbInputDevices.Invoke(new Action(() => StartAudioRecording()));
                  return; // 退出当前方法,等待主线程完成
                }

                // 处理音频输入录制
                var microphoneDevice = inputDevices;
                microphoneCapture = new WasapiCapture(microphoneDevice);
                microphoneCapture.WaveFormat = waveFormat;
                string microphoneOutputPath = Path.Combine(Application.StartupPath, "录像", $"Microphone_{DateTime.Now:yyyyMMdd_HHmmss}.wav");
                microphoneWriter = new WaveFileWriter(microphoneOutputPath, microphoneCapture.WaveFormat);
                microphoneCapture.DataAvailable += MicrophoneCapture_DataAvailable;
                microphoneCapture.RecordingStopped += MicrophoneCapture_RecordingStopped;
                microphoneCapture.StartRecording();
            }
      }

      private void StopAudioRecording()
      {
            if (loopbackCapture != null)
            {
                loopbackCapture.StopRecording();
                loopbackWriter?.Dispose();
                loopbackWriter = null;
                loopbackCapture?.Dispose();
                loopbackCapture = null;
            }

            if (microphoneCapture != null)
            {
                microphoneCapture.StopRecording();
                microphoneWriter?.Dispose();
                microphoneWriter = null;
                microphoneCapture?.Dispose();
                microphoneCapture = null;
            }
      }

      private void CaptureScreen(int width, int height)
      {
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                using (Bitmap bitmap = new Bitmap(width, height))
                {
                  using (Graphics g = Graphics.FromImage(bitmap))
                  {
                        g.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
                  }

                  lock (writerLock)
                  {
                        lastFrame?.Dispose();
                        lastFrame = (Bitmap)bitmap.Clone();
                        lastFrameTimestamp = stopwatch.ElapsedMilliseconds; // 记录当前帧的时间戳
                  }
                }
                Thread.Sleep(1000 / targetFrameRate);
            }
      }

      private void UpdateTimer()
      {
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                TimeSpan elapsedTime = stopwatch.Elapsed;
                lblRecordingTime.Invoke(new Action(() =>
                {
                  lblRecordingTime.Text = $"录制时间: {elapsedTime:hh\\:mm\\:ss}";
                }));
                Thread.Sleep(1000);
            }
      }

      private void InsertFrames()
      {
            while (!cancellationTokenSource.IsCancellationRequested)
            {
                lock (writerLock)
                {
                  if (lastFrame != null && !lastFrame.IsDisposed())
                  {
                        framesToAdd = targetFrameRate - actualFrameCount;
                  }
                }
                Thread.Sleep(1000);
            }
      }

      private void RecordVideo()
      {
            long frameDuration = 1000 / targetFrameRate; // 每帧的持续时间(毫秒)
            long nextFrameTime = 0;

            while (!cancellationTokenSource.IsCancellationRequested)
            {
                lock (writerLock)
                {
                  if (lastFrame != null && !lastFrame.IsDisposed())
                  {
                        long currentTime = stopwatch.ElapsedMilliseconds;

                        // 如果当前时间大于等于下一个帧的时间,则写入帧
                        if (currentTime >= nextFrameTime)
                        {
                            writer.WriteVideoFrame(lastFrame);
                            actualFrameCount++;
                            nextFrameTime += frameDuration; // 更新下一个帧的时间
                        }

                        // 如果帧数不足,进行补偿
                        if (framesToAdd > 0)
                        {
                            for (int i = 0; i < framesToAdd; i++)
                            {
                              writer.WriteVideoFrame(lastFrame);
                            }
                        }
                  }
                }
                Thread.Sleep(1); // 确保不会占用过多CPU资源
            }
      }

      private void CompensateFrames()
      {
            int expectedFrameCount = targetFrameRate;
            int framesToAdd = expectedFrameCount - actualFrameCount;

            if (framesToAdd > 0 && lastFrame != null && !lastFrame.IsDisposed())
            {
                lock (writerLock)
                {
                  for (int i = 0; i < framesToAdd; i++)
                  {
                        writer.WriteVideoFrame(lastFrame);
                  }
                }
            }
      }

      private void LoopbackCapture_DataAvailable(object sender, WaveInEventArgs e)
      {
            if (loopbackWriter != null)
            {
                loopbackWriter.Write(e.Buffer, 0, e.BytesRecorded);
            }
      }

      private void LoopbackCapture_RecordingStopped(object sender, StoppedEventArgs e)
      {
            loopbackWriter?.Dispose();
            loopbackWriter = null;
            loopbackCapture?.Dispose();
            loopbackCapture = null;
      }

      private void MicrophoneCapture_DataAvailable(object sender, WaveInEventArgs e)
      {
            if (microphoneWriter != null)
            {
                microphoneWriter.Write(e.Buffer, 0, e.BytesRecorded);
            }
      }

      private void MicrophoneCapture_RecordingStopped(object sender, StoppedEventArgs e)
      {
            microphoneWriter?.Dispose();
            microphoneWriter = null;
            microphoneCapture?.Dispose();
            microphoneCapture = null;
      }

      private void btnOpenFolder_Click(object sender, EventArgs e)
      {
            string folderPath = Path.GetDirectoryName(outputFilePath);
            Process.Start("explorer.exe", folderPath);
      }

      private int GetBitrate(string quality)
      {
            switch (quality)
            {
                case "低":
                  return 1500000;
                case "中":
                  return 5000000;
                case "高":
                  return 8000000;
                default:
                  return 1500000;
            }
      }

      private WaveFormat GetWaveFormat(string quality)
      {
            switch (quality)
            {
                case "低":
                  return new WaveFormat(8000, 1); // 8000 Hz 采样率,单声道
                case "中":
                  return new WaveFormat(16000, 2); // 16000 Hz 采样率,立体声
                case "高":
                  return new WaveFormat(44100, 2); // 44100 Hz 采样率,立体声
                default:
                  return new WaveFormat(8000, 1);
            }
      }

      private long totalDurationMs; // 用于存储视频总时长

      private long GetVideoDuration(string videoPath)
      {
            string ffmpegPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\ffmpega\bin\ffmpeg.exe";
            string arguments = $"-i \"{videoPath}\"";

            var processStartInfo = new ProcessStartInfo
            {
                FileName = ffmpegPath,
                Arguments = arguments,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                UseShellExecute = false,
                CreateNoWindow = true
            };

            using (var process = Process.Start(processStartInfo))
            {
                string output = process.StandardError.ReadToEnd();
                process.WaitForExit();

                // 解析输出以获取视频时长
                int durationIndex = output.IndexOf("Duration: ");
                if (durationIndex != -1)
                {
                  int startIndex = durationIndex + "Duration: ".Length;
                  int endIndex = output.IndexOf(",", startIndex);
                  if (endIndex != -1)
                  {
                        string time = output.Substring(startIndex, endIndex - startIndex).Trim();
                        if (TimeSpan.TryParse(time, out TimeSpan timeSpan))
                        {
                            return (long)timeSpan.TotalMilliseconds;
                        }
                  }
                }
            }

            return 0; // 如果无法获取时长,返回 0
      }


      private async void btnMerge_Click(object sender, EventArgs e)
      {
            string directoryPath = Path.Combine(Application.StartupPath, "录像");
            if (!Directory.Exists(directoryPath))
            {
                MessageBox.Show("录像文件夹不存在。");
                return;
            }

            string videoPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\Loopback_20250217_161914.mp4";
            string audioPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\Loopback_20250217_161914.aac";
            string tempAudioPath = Path.Combine(directoryPath, "temp_audio.aac");
            string outputPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\output.mp4";
            string ffmpegPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\ffmpega\bin\ffmpeg.exe";

            // 检查文件是否存在
            if (!File.Exists(videoPath))
            {
                MessageBox.Show("视频文件不存在,请检查路径。");
                return;
            }
            if (!File.Exists(audioPath))
            {
                MessageBox.Show("音频文件不存在,请检查路径。");
                return;
            }

            // 转换音频格式
         // ConvertAudioToAAC(audioPath, tempAudioPath);

            // 获取视频总时长
            totalDurationMs = GetVideoDuration(videoPath);
            if (totalDurationMs == 0)
            {
                MessageBox.Show("无法获取视频总时长,请检查视频文件。");
                return;
            }

            string arguments = $"-i \"{videoPath}\" -i \"{tempAudioPath}\" -c:v copy -c:a aac -strict experimental -progress pipe:1 -nostats \"{outputPath}\"";

            lblProgress.Text = "合成进度: 0%"; // 初始化进度标签

            await Task.Run(() =>
            {
                var processStartInfo = new ProcessStartInfo
                {
                  FileName = ffmpegPath,
                  Arguments = arguments,
                  RedirectStandardOutput = true,
                  RedirectStandardError = true,
                  UseShellExecute = false,
                  CreateNoWindow = true
                };

                try
                {
                  using (var process = Process.Start(processStartInfo))
                  {
                        bool hasStartedProgress = false;
                        while (!process.StandardOutput.EndOfStream)
                        {
                            string line = process.StandardOutput.ReadLine();
                            if (line.StartsWith("out_time_ms="))
                            {
                              string[] parts = line.Split('=');
                              if (parts.Length == 2 && long.TryParse(parts, out long outTimeMs))
                              {
                                    double progress = Math.Min(100, outTimeMs / (double)totalDurationMs * 100);
                                    lblProgress.Invoke(new Action(() =>
                                    {
                                        lblProgress.Text = $"合成进度: {progress:F2}%";
                                    }));
                                    hasStartedProgress = true;
                              }
                            }
                        }

                        if (!hasStartedProgress)
                        {
                            MessageBox.Show("未收到 FFmpeg 的进度信息,请检查 FFmpeg 配置或命令参数。");
                        }

                        string error = process.StandardError.ReadToEnd();
                        if (!string.IsNullOrEmpty(error))
                        {
                            MessageBox.Show($"FFmpeg 执行出错: {error}");
                        }

                        process.WaitForExit();
                  }
                }
                catch (Exception ex)
                {
                  MessageBox.Show($"启动 FFmpeg 进程时出现错误: {ex.Message}");
                }
                finally
                {
                  // 删除临时音频文件
                  if (File.Exists(tempAudioPath))
                  {
                        File.Delete(tempAudioPath);
                  }
                }
            });

            MessageBox.Show("视频合并完成!");
      }
      private void ConvertAudioToAAC(string inputAudioPath, string outputAudioPath)
      {
            string ffmpegPath = @"E:\apk\ScreenRecorder\bin\Debug\录像\ffmpega\bin\ffmpeg.exe";
            string arguments = $"-i \"{inputAudioPath}\" -c:a aac \"{outputAudioPath}\"";

            var processStartInfo = new ProcessStartInfo
            {
                FileName = ffmpegPath,
                Arguments = arguments,
                UseShellExecute = false,
                CreateNoWindow = true
            };

            try
            {
                using (var process = Process.Start(processStartInfo))
                {
                  process.WaitForExit();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show($"音频格式转换出错: {ex.Message}");
            }
      }

    }

    public static class BitmapExtensions
    {
      public static bool IsDisposed(this Bitmap bitmap)
      {
            try
            {
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                  return false;
                }
            }
            catch (ObjectDisposedException)
            {
                return true;
            }
      }
    }
}



ibcadmin 发表于 2025-2-19 15:40:56

顶一下{:2_27:}


页: [1]
查看完整版本: 求助 用C#写了个录屏软件 合成音视频问题