马上加入IBC程序猿 各种源码随意下,各种教程随便看! 注册 每日签到 加入编程讨论群

C#教程 ASP.NET教程 C#视频教程程序源码享受不尽 C#技术求助 ASP.NET技术求助

【源码下载】 社群合作 申请版主 程序开发 【远程协助】 每天乐一乐 每日签到 【承接外包项目】 面试-葵花宝典下载

官方一群:

官方二群:

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

[复制链接]
查看563 | 回复1 | 2025-2-18 17:20:08 | 显示全部楼层 |阅读模式
用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[cmbOutputDevices.SelectedIndex];
                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[cmbInputDevices.SelectedIndex];
                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[1], 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 | 显示全部楼层
顶一下


C#论坛 www.ibcibc.com IBC编程社区
C#
C#论坛
IBC编程社区
您需要登录后才可以回帖 登录 | 立即注册

*滑块验证:
img_loading
智能检测中
本版积分规则