You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用NAudio AsioOut获取多声道音频样本写入文件失败求助

问题:NAudio ASIO录制多声道到独立文件生成零字节文件

问题描述

参考旧版NAudio解决方案尝试将ASIO设备不同声道的音频样本保存到独立WAV文件,但仅生成零字节文件,无任何报错。需调整代码实现正确录制。

原代码

private WaveFileWriter[] writer = null;
private AsioOut asioOut;
private string[] fileName = null;
private int recordChannelCount;

private void Start()
{
    // allow change device
    if (asioOut != null &&
        (asioOut.DriverName != comboBoxAsioDevice.Text ||
        asioOut.ChannelOffset != GetUserSpecifiedChannelOffset()))
    {
        asioOut.AudioAvailable -= OnAsioOutAudioAvailable;
        asioOut.Dispose();
        asioOut = null;
    }

    recordChannelCount = GetUserSpecifiedChannelCount();

    // create device if necessary
    if (asioOut == null)
    {
        asioOut = new AsioOut(comboBoxAsioDevice.Text);
        asioOut.InputChannelOffset = GetUserSpecifiedChannelOffset();
        asioOut.InitRecordAndPlayback(null, recordChannelCount, 44100);
        asioOut.AudioAvailable += OnAsioOutAudioAvailable;
    }

    fileName = new string[recordChannelCount];
    writer = new WaveFileWriter[recordChannelCount];

    for (int i = 0; i < recordChannelCount; i++)
    {
        fileName[i] = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"Record{i+1}" + ".wav");
        writer[i] = new WaveFileWriter(fileName[i], new WaveFormat(44100, recordChannelCount));
    }
    
    asioOut.Play();
    timer1.Enabled = true;
    SetButtonStates();
}

void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
#pragma warning disable 618
    float[] samples = new float[2 * 44100 * GetUserSpecifiedChannelCount()];
    samples = e.GetAsInterleavedSamples();
#pragma warning restore 618
    
    int offset = 0;
    for (int i = 0; i < writer.Length; i++)
    {
        //writer[i].WriteSamples(samples, 0, samples.Length); //works but writes same thing
        writer[i].WriteSamples(samples, offset, 1); //Not working
        offset += 1;
    }
}

错误分析与修改方案

1. WaveFormat配置错误

每个独立文件对应单声道音频,但原代码创建WaveFileWriter时使用了多声道格式(recordChannelCount),导致文件头错误,无法正确写入数据。
修改:将创建WaveFormat的参数改为单声道:

writer[i] = new WaveFileWriter(fileName[i], new WaveFormat(44100, 1));

2. 交错样本处理逻辑错误

原代码仅向每个文件写入1个样本,完全没有遍历所有音频数据。ASIO返回的是交错样本数组,格式为:声道0样本0, 声道1样本0, 声道2样本0, ..., 声道0样本1, 声道1样本1, ...。需要按声道提取所有对应位置的样本。
修改:重写OnAsioOutAudioAvailable方法的样本处理逻辑:

void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
    // 直接使用返回的交错样本数组,无需预先创建大数组
    float[] samples = e.GetAsInterleavedSamples();
    int channelCount = recordChannelCount;
    int frameCount = samples.Length / channelCount;

    for (int channel = 0; channel < channelCount; channel++)
    {
        // 提取当前声道的所有样本
        for (int frame = 0; frame < frameCount; frame++)
        {
            float sample = samples[frame * channelCount + channel];
            writer[channel].WriteSample(sample);
        }
    }
}

或者使用更高效的批量写入(避免循环写单个样本):

void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
    float[] samples = e.GetAsInterleavedSamples();
    int channelCount = recordChannelCount;
    int frameCount = samples.Length / channelCount;

    for (int channel = 0; channel < channelCount; channel++)
    {
        float[] channelSamples = new float[frameCount];
        for (int frame = 0; frame < frameCount; frame++)
        {
            channelSamples[frame] = samples[frame * channelCount + channel];
        }
        writer[channel].WriteSamples(channelSamples, 0, frameCount);
    }
}

3. 资源未正确释放

录制停止时,未对WaveFileWriter执行Flush()和Dispose()操作,导致文件内容未写入磁盘,出现零字节文件。需添加停止录制的逻辑:

private void StopRecording()
{
    asioOut.Stop();
    asioOut.AudioAvailable -= OnAsioOutAudioAvailable;
    
    // 确保每个写入器都完成写入并释放资源
    if (writer != null)
    {
        foreach (var w in writer)
        {
            w?.Flush();
            w?.Dispose();
        }
        writer = null;
    }
    
    asioOut.Dispose();
    asioOut = null;
    timer1.Enabled = false;
    SetButtonStates();
}

4. 过时方法的替代(可选)

GetAsInterleavedSamples()已过时,建议使用NAudio最新版本的e.InputBuffers直接访问非交错的声道缓冲区,无需自己拆分交错数据:

void OnAsioOutAudioAvailable(object sender, AsioAudioAvailableEventArgs e)
{
    for (int channel = 0; channel < e.InputBuffers.Length; channel++)
    {
        // InputBuffers直接对应每个声道的非交错样本数组
        writer[channel].WriteSamples(e.InputBuffers[channel], 0, e.SamplesPerBuffer);
    }
    // 必须调用RecordSampleBuffers处理输入缓冲区
    e.RecordSampleBuffers();
}

使用此方式时,需确保InitRecordAndPlayback的配置正确,且NAudio版本为较新的稳定版。

最终说明

修改以上几点后,即可正确将每个声道的音频录制到独立的WAV文件中。录制完成后务必调用停止逻辑释放资源,避免文件损坏。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:51:00