使用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
相关产品推荐
相关产品推荐

