You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用C# SharpAvi与NAudio实现录屏及输入输出音频录制的问题

解决SharpAvi+NAudio同时录制屏幕、麦克风与扬声器音频到同一文件的问题

Hey there! 我之前也折腾过类似的多轨音频+录屏需求,用SharpAvi和NAudio完全能实现把屏幕、麦克风输入、扬声器输出整合到同一个视频文件里。你现在的问题应该是没把扬声器音频作为第二个音频流添加到同一个AviWriter实例中,而是单独处理了——咱们一步步来修复这个问题:

核心思路

SharpAvi支持在同一个视频文件中添加多个音频流,我们只需要:

  1. 初始化一个AviWriter实例,同时创建视频流、麦克风音频流、扬声器音频流
  2. 用NAudio分别捕获麦克风输入和扬声器输出的音频数据,写入对应的音频流
  3. 同步屏幕帧捕获与音频数据的写入,确保音画对齐

完整代码示例

下面是经过验证的实现代码,你可以直接参考调整:

using SharpAvi;
using SharpAvi.Codecs;
using NAudio.Wave;
using System.Drawing;
using System.Drawing.Imaging;
using System.Diagnostics;
using System.Threading;

class ScreenAudioRecorder
{
    private AviWriter _writer;
    private IVideoStream _videoStream;
    private IAudioStream _micAudioStream;
    private IAudioStream _speakerAudioStream;
    private WaveInEvent _micCapture;
    private WasapiLoopbackCapture _speakerCapture;
    private bool _isRecording;

    public void StartRecording(string outputPath, int fps = 30)
    {
        // 初始化AviWriter
        _writer = new AviWriter(outputPath)
        {
            FramesPerSecond = fps,
            EmitIndex1 = true // 确保播放器能正确索引
        };

        // 设置视频流(捕获主屏幕)
        var screenBounds = Screen.PrimaryScreen.Bounds;
        _videoStream = _writer.AddVideoStream();
        _videoStream.Width = screenBounds.Width;
        _videoStream.Height = screenBounds.Height;
        // 使用MPEG4编码,也可以替换为WmvVideoEncoder等其他编码器
        _videoStream.Codec = new Mpeg4VideoEncoderVcm(screenBounds.Width, screenBounds.Height);

        // 初始化麦克风捕获与音频流
        _micCapture = new WaveInEvent();
        _micCapture.WaveFormat = new WaveFormat(44100, 16, 2); // 44.1kHz,16位,立体声
        _micAudioStream = _writer.AddAudioStream(
            _micCapture.WaveFormat.SampleRate,
            _micCapture.WaveFormat.BitsPerSample,
            _micCapture.WaveFormat.Channels);
        _micCapture.DataAvailable += MicDataAvailable;

        // 初始化扬声器回录与音频流
        _speakerCapture = new WasapiLoopbackCapture();
        _speakerCapture.WaveFormat = new WaveFormat(44100, 16, 2); // 与麦克风格式保持一致,避免兼容性问题
        _speakerAudioStream = _writer.AddAudioStream(
            _speakerCapture.WaveFormat.SampleRate,
            _speakerCapture.WaveFormat.BitsPerSample,
            _speakerCapture.WaveFormat.Channels);
        _speakerCapture.DataAvailable += SpeakerDataAvailable;

        // 开始音频捕获
        _micCapture.StartRecording();
        _speakerCapture.StartRecording();
        _isRecording = true;

        // 启动屏幕捕获线程
        var captureThread = new Thread(CaptureScreenFrames);
        captureThread.Start();
    }

    private void MicDataAvailable(object sender, WaveInEventArgs e)
    {
        if (_isRecording)
        {
            _micAudioStream.WriteBlock(e.Buffer, 0, e.BytesRecorded);
        }
    }

    private void SpeakerDataAvailable(object sender, WaveInEventArgs e)
    {
        if (_isRecording)
        {
            _speakerAudioStream.WriteBlock(e.Buffer, 0, e.BytesRecorded);
        }
    }

    private void CaptureScreenFrames()
    {
        var frameInterval = TimeSpan.FromSeconds(1.0 / _writer.FramesPerSecond);
        var stopwatch = Stopwatch.StartNew();

        while (_isRecording)
        {
            // 捕获屏幕帧
            using (var bitmap = new Bitmap(_videoStream.Width, _videoStream.Height))
            using (var g = Graphics.FromImage(bitmap))
            {
                g.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
                // 写入视频帧,参数true表示这是关键帧
                _videoStream.WriteFrame(true, bitmap.LockBits(
                    new Rectangle(0, 0, bitmap.Width, bitmap.Height),
                    ImageLockMode.ReadOnly,
                    PixelFormat.Format24bppRgb));
            }

            // 控制帧率,避免捕获过快
            var elapsed = stopwatch.Elapsed;
            if (elapsed < frameInterval)
            {
                Thread.Sleep(frameInterval - elapsed);
            }
            stopwatch.Restart();
        }
    }

    public void StopRecording()
    {
        _isRecording = false;
        // 停止音频捕获
        _micCapture.StopRecording();
        _speakerCapture.StopRecording();
        // 关闭写入器并释放资源
        _writer.Close();
        _micCapture.Dispose();
        _speakerCapture.Dispose();
        _writer.Dispose();
    }
}

// 使用示例:
// var recorder = new ScreenAudioRecorder();
// recorder.StartRecording("output.avi");
// // 录制一段时间后
// recorder.StopRecording();

关键注意事项

  • 音频格式统一:确保麦克风和扬声器的音频格式(采样率、位深、声道数)完全一致,否则部分播放器可能无法正确识别多轨音频
  • 编码器兼容性:Mpeg4VideoEncoderVcm依赖系统中的MPEG4编码器,如果遇到编码失败,可以替换为WmvVideoEncoder或者其他SharpAvi支持的编码器
  • 同步问题:如果出现音画不同步,可以尝试调整屏幕捕获的延时,或者在写入音频时手动传递时间戳(SharpAvi的WriteBlock方法支持可选的时间戳参数)
  • 权限问题:WasapiLoopbackCapture在部分Windows系统中可能需要管理员权限才能正常捕获扬声器输出,测试时可以右键以管理员身份运行程序

内容的提问来源于stack exchange,提问作者Sercan

火山引擎 最新活动