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

WinUI3项目中基于NAudio+ASIO4ALL的录音代码失效,请求修复方案

WinUI3项目中基于NAudio+ASIO4ALL的录音代码失效,请求修复方案

我仔细看了你的问题描述和代码,核心问题出在ASIO音频回调的过时API使用、非交错音频数据处理逻辑,还有WinUI3环境下的细节适配上。下面给你一步步梳理修复方案,确保能正常录制声音:

问题根源分析

  1. 原代码里的GetAsInterleavedSamples()是NAudio的过时方法,ASIO的音频数据本质是非交错格式(每个通道单独存储数据),直接调用这个方法容易出现数据读取错误;
  2. 没有确保音频文件写入格式和ASIO设备的实际格式完全匹配(采样率、通道数、位深度不匹配会导致空文件或噪音);
  3. WinUI3的文件选择器需要手动绑定窗口句柄才能正常弹出,原代码缺失这一步。

完整修复代码

补全并修改后的C#代码(MainWindow.xaml.cs)

using System;
using System.Collections.Generic;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using NAudio.Wave;
using System.Threading.Tasks;
using Windows.Storage.Pickers;
using Windows.Storage;

namespace WinUI_3_AsioRecordingPanel
{
    public sealed partial class MainWindow : Window
    {
        private AsioOut _asioOut;
        private WaveFileWriter _waveWriter;
        private StorageFile _outputFile;
        private WaveFormat _asioWaveFormat;

        public MainWindow()
        {
            this.InitializeComponent();
            LoadAsioDevices();
        }

        // 加载系统中所有可用的ASIO设备
        private void LoadAsioDevices()
        {
            var asioDeviceNames = AsioOut.GetDriverNames();
            foreach (var name in asioDeviceNames)
            {
                comboBoxAsioDevice.Items.Add(name);
            }
            if (asioDeviceNames.Length > 0)
            {
                comboBoxAsioDevice.SelectedIndex = 0;
            }
        }

        // 切换ASIO设备时重新初始化资源
        private void comboBoxAsioDevice_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            // 释放旧设备资源,防止内存泄漏
            if (_asioOut != null)
            {
                _asioOut.Stop();
                _asioOut.Dispose();
                _asioOut = null;
            }

            var selectedDevice = comboBoxAsioDevice.SelectedItem as string;
            if (string.IsNullOrEmpty(selectedDevice)) return;

            _asioOut = new AsioOut(selectedDevice);
            // ASIO默认输出32位浮点样本,严格匹配格式
            _asioWaveFormat = WaveFormat.CreateIeeeFloatWaveFormat(_asioOut.SampleRate, _asioOut.DriverInputChannelCount);
            // 注册音频数据回调事件
            _asioOut.AudioAvailable += OnAsioOutAudioAvailable;
        }

        // 选择录音输出文件
        private async void fileButton_Click(object sender, RoutedEventArgs e)
        {
            var savePicker = new FileSavePicker();
            // WinUI3必须绑定窗口句柄才能正常弹出文件选择器
            var windowHandle = WinRT.Interop.WindowNative.GetWindowHandle(this);
            WinRT.Interop.InitializeWithWindow.Initialize(savePicker, windowHandle);
            
            savePicker.SuggestedStartLocation = PickerLocationId.Desktop;
            savePicker.FileTypeChoices.Add("WAV 音频文件", new List<string> { ".wav" });
            savePicker.SuggestedFileName = "ASIO_录音文件";

            _outputFile = await savePicker.PickSaveFileAsync();
            if (_outputFile != null)
            {
                OnButtonStart.IsEnabled = true;
            }
        }

        // 开始录音
        private void OnButtonStart_Click(object sender, RoutedEventArgs e)
        {
            if (_asioOut == null || _outputFile == null) return;

            int channelCount;
            int channelOffset;
            // 校验输入的通道数和偏移量有效性
            if (!int.TryParse(textBoxChannelCount.Text, out channelCount) || 
                !int.TryParse(textBoxChannelOffset.Text, out channelOffset))
            {
                // 这里可以加个提示弹窗,比如用ContentDialog提示输入无效
                return;
            }

            // 初始化音频文件写入器,格式严格匹配ASIO设备
            _waveWriter = new WaveFileWriter(_outputFile.Path, _asioWaveFormat);
            // ASIO是全双工驱动,用Play方法启动录音流程
            _asioOut.InitRecordAndPlayback(null, channelCount, channelOffset);
            _asioOut.Play();

            OnButtonStart.IsEnabled = false;
            OnButtonStop.IsEnabled = true;
        }

        // 停止录音
        private void OnButtonStop_Click(object sender, RoutedEventArgs e)
        {
            if (_asioOut != null)
            {
                _asioOut.Stop();
                // 确保缓存数据完全写入文件,避免文件损坏
                _waveWriter?.Flush();
                _waveWriter?.Dispose();
                _waveWriter = null;
            }

            OnButtonStart.IsEnabled = true;
            OnButtonStop.IsEnabled = false;
        }

        // 打开ASIO4ALL控制面板
        private void OnButtonControlPanel_Click(object sender, RoutedEventArgs e)
        {
            _asioOut?.ShowControlPanel();
        }

        // 修复后的ASIO音频数据回调处理
        private void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
        {
            if (_waveWriter == null) return;

            // 将ASIO的非交错音频数据转换为交错格式(适配标准WAV文件)
            var interleavedSamples = new float[e.SamplesPerBuffer * e.InputChannelCount];
            int bufferOffset = 0;

            // 遍历每个输入通道,合并成交错缓冲区
            for (int channel = 0; channel < e.InputChannelCount; channel++)
            {
                var channelBuffer = e.InputBuffers[channel];
                for (int sample = 0; sample < e.SamplesPerBuffer; sample++)
                {
                    interleavedSamples[bufferOffset + channel] = channelBuffer[sample];
                    bufferOffset += e.InputChannelCount;
                }
            }

            // 写入处理后的音频数据到文件
            _waveWriter.WriteSamples(interleavedSamples, 0, interleavedSamples.Length);
        }
    }
}

关键修改点说明

  1. 替换过时API,正确处理非交错音频
    抛弃了过时的GetAsInterleavedSamples(),手动实现非交错到交错的音频数据转换——这是ASIO录音的核心,因为ASIO驱动的音频数据是按通道独立存储的,必须合并成交错格式才能写入标准WAV文件。

  2. 严格匹配音频格式
    WaveFormat.CreateIeeeFloatWaveFormat初始化文件写入器,因为ASIO设备默认输出32位浮点样本,格式不匹配会导致录到空文件或噪音。

  3. 适配WinUI3的文件选择器
    补充了窗口句柄绑定代码,解决WinUI3中文件选择器无法弹出的问题。

  4. 完善资源释放逻辑
    在停止录音时确保WaveFileWriter的缓存数据完全写入文件,避免文件损坏;切换设备时及时释放旧的ASIO设备资源,防止内存泄漏。

额外注意事项

  • 打开ASIO4ALL控制面板,确保你要录音的输入设备(比如麦克风、线路输入)已经被激活,并且设置为默认输入;
  • 测试时尽量用低延迟的ASIO配置,避免音频卡顿;
  • 如果还是录不到声音,可以检查textBoxChannelCount的数值是否超过ASIO设备的实际输入通道数,比如ASIO4ALL显示有2个输入通道,就不要填3。

内容来源于stack exchange

火山引擎 最新活动