这个错误:
```
soundfile.LibsndfileError: Error opening 'E:\\python\\ai_denoise\\asteroid-master\\deploy_result\\infer_sample_save\\g6ptz_no_nr\\chunk_0000.wav': Format not recognised.
```
✅ **核心含义**:`soundfile` 成功加载了 `libsndfile.dll`(即前一个 `OSError: cannot load library` 已解决),
❌ **但无法识别你提供的 `.wav` 文件的格式** —— 即该文件**不是标准、可解析的 WAV 格式**。
> 🔍 注意:`.wav` 后缀 ≠ WAV 格式!很多工具(尤其是语音处理 pipeline)会:
> - 生成**损坏/截断的 WAV**(如写入未完成就中断);
> - 保存为**非 PCM 编码的 WAV**(如 ADPCM、IMA-ADPCM、GSM、MP3-in-WAV 等);
> - 使用**不被 libsndfile 支持的扩展格式**(如 RF64、WAVE_FORMAT_EXTENSIBLE 带特殊子格式);
> - 写入**无有效 `fmt` chunk 或 `data` chunk 的伪 WAV**(常见于流式写入 bug)。
---
### ✅ 一、快速诊断:确认文件是否真的是合法 WAV
#### ✅ 方法 1:用 `ffprobe`(推荐,最准)
安装 [FFmpeg](https://ffmpeg.org/download.html) 后运行:
```bash
ffprobe -v quiet -show_entries stream=codec_name,codec_type,bits_per_sample,sample_rate -of default "E:\python\ai_denoise\asteroid-master\deploy_result\infer_sample_save\g6ptz_no_nr\chunk_0000.wav"
```
✅ 正常 WAV 应输出类似:
```
codec_name=pcm_s16le
codec_type=audio
bits_per_sample=16
sample_rate=16000
```
❌ 若报错 `Invalid data found when processing input` 或 `codec_name=unknown` → 文件已损坏或非标准。
#### ✅ 方法 2:用 Python 检查 WAV 头(无需外部工具)
```python
# check_wav_header.py
import struct
def inspect_wav_header(filepath):
try:
with open(filepath, "rb") as f:
# 读取 RIFF 头 (12 bytes)
riff = f.read(12)
if len(riff) < 12:
print("❌ File too short (<12 bytes)")
return
if riff[:4] != b'RIFF':
print("❌ Not a RIFF file (missing 'RIFF' magic)")
return
if riff[8:12] != b'WAVE':
print("❌ Not a WAVE file (missing 'WAVE' magic)")
return
# 读取 fmt chunk (至少 24 bytes from start)
f.seek(12)
while True:
chunk_hdr = f.read(8)
if len(chunk_hdr) < 8:
print("❌ Incomplete chunk header")
return
chunk_id = chunk_hdr[:4]
chunk_size = struct.unpack('<I', chunk_hdr[4:8])[0]
if chunk_id == b'fmt ':
print(f"✅ Found 'fmt ' chunk, size = {chunk_size}")
fmt_data = f.read(chunk_size)
if len(fmt_data) < 16:
print("❌ fmt chunk too short (<16 bytes)")
return
# 解析 format tag (2 bytes), channels (2), rate (4), ...
audio_format = struct.unpack('<H', fmt_data[0:2])[0]
channels = struct.unpack('<H', fmt_data[2:4])[0]
sample_rate = struct.unpack('<I', fmt_data[4:8])[0]
print(f" AudioFormat={audio_format} (1=PCM, 6=ADPCM, 17=IMA-ADPCM, 85=MP3)")
print(f" Channels={channels}, SampleRate={sample_rate}")
break
elif chunk_id == b'data':
print("❌ 'fmt ' chunk missing before 'data'")
return
else:
# 跳过未知 chunk
f.seek(chunk_size, 1)
except Exception as e:
print("❌ Error:", e)
if __name__ == "__main__":
inspect_wav_header(r"E:\python\ai_denoise\asteroid-master\deploy_result\infer_sample_save\g6ptz_no_nr\chunk_0000.wav")
```
📌 关键看 `AudioFormat=` 输出:
| `AudioFormat` 值 | 含义 | `libsndfile` 是否支持 |
|----------------|------|------------------------|
| `1` | PCM (uncompressed) | ✅ 全面支持 |
| `6`, `17`, `20`, `49`, `65` | ADPCM variants | ⚠️ 部分支持(旧版 libsndfile 可能拒绝) |
| `85` | MP3 | ❌ 不支持(`libsndfile` 仅支持容器,不带解码器) |
| `65534` (`0xFFFE`) | WAVE_FORMAT_EXTENSIBLE | ⚠️ 需 libsndfile ≥ 1.1.0,且 subtype 必须是 PCM |
> 💡 如果输出 `AudioFormat=65534`,说明是「扩展 WAV」,需检查其 `SubFormat` GUID(是否为 PCM)。
---
### ✅ 二、常见原因 & 对应解决方案
#### ✅ 场景 1:文件被截断 / 写入未完成(最常见!)
- **现象**:文件大小异常小(如 `< 44 bytes`),或 `ffprobe` 报 `Truncation`;
- **原因**:Asteroid 推理脚本崩溃、磁盘满、权限不足导致 `sf.write()` 未写完就退出;
- **修复**:
```python
# 在你的推理脚本中,确保 write 完整 + flush
import soundfile as sf
sf.write(path, data, sr, format="WAV", subtype="PCM_16") # 显式指定 subtype
# ✅ 更安全:用 with 语句(自动 flush/close)
with sf.SoundFile(path, mode='w', samplerate=sr, channels=data.shape[1], subtype='PCM_16') as f:
f.write(data)
```
#### ✅ 场景 2:使用了 `libsndfile` 不支持的编码(如 MP3-in-WAV、ADPCM)
- **现象**:`ffprobe` 显示 `codec_name=mp3` 或 `AudioFormat=85`;
- **原因**:上游预处理工具(如 sox、ffmpeg)导出时用了 `-acodec mp3`;
- **修复**:强制转为标准 PCM WAV:
```bash
# 用 ffmpeg 重编码(推荐)
ffmpeg -i "chunk_0000.wav" -ar 16000 -ac 1 -acodec pcm_s16le "chunk_0000_fixed.wav"
```
或用 Python(需 `pydub`):
```python
from pydub import AudioSegment
seg = AudioSegment.from_file("chunk_0000.wav")
seg.export("chunk_0000_fixed.wav", format="wav", codec="pcm_s16le")
```
#### ✅ 场景 3:采样率/位深不匹配 Asteroid 要求
- Asteroid 模型通常训练于 `16kHz` / `16-bit PCM`;
- 若文件是 `44.1kHz`、`24-bit`、`float32`,某些 `libsndfile` 版本会拒绝;
- **修复**:显式指定 subtype(关键!):
```python
sf.write(
path,
data,
samplerate=16000,
subtype="PCM_16" # ← 强制写为标准 16-bit PCM
)
```
#### ✅ 场景 4:Windows 路径中的反斜杠 `\` 导致字符串解析错误(少见但存在)
- **现象**:路径含 `\n`, `\t`, `\r` 被误解析(如 `"chunk_0000.wav"` 实际是 `"chunk_0000.wav"` 带隐藏字符);
- **修复**:统一用原始字符串或正斜杠:
```python
# ✅ 推荐写法
path = r"E:\python\ai_denoise\asteroid-master\deploy_result\infer_sample_save\g6ptz_no_nr\chunk_0000.wav"
# 或
path = "E:/python/ai_denoise/asteroid-master/deploy_result/infer_sample_save/g6ptz_no_nr/chunk_0000.wav"
```
#### ✅ 场景 5:文件被其他进程占用(如 Windows 资源管理器预览窗格)
- **现象**:文件存在但打不开,重启后偶尔正常;
- **修复**:
- 关闭所有资源管理器窗口;
- 运行 `Resource Monitor` → 查找 `chunk_0000.wav` 的句柄 → 结束占用进程;
- 或在代码中加重试逻辑:
```python
import time
for i in range(3):
try:
data, sr = sf.read(path)
break
except soundfile.LibsndfileError:
if i == 2: raise
time.sleep(0.1)
```
---
### ✅ 三、终极兼容方案:用 `torchaudio` 替代 `soundfile`(推荐 Asteroid 用户)
Asteroid ≥ 0.7.0 支持 `torchaudio` 后端,它:
- ✅ 自带 FFmpeg 解码器,支持 MP3/FLAC/OPUS 等全部格式;
- ✅ 对损坏/非标 WAV 更宽容(自动 fallback 到 ffmpeg);
- ✅ 无需 `libsndfile.dll`,彻底规避格式问题。
#### ✅ 启用方式(在推理脚本最开头):
```python
import os
os.environ["ASTEROID_AUDIO_BACKEND"] = "torchaudio" # ← 必须在 import asteroid 前!
# 然后导入
from asteroid import models
import torch
```
✅ 确保已安装 `torchaudio`(与 PyTorch 版本匹配):
```bash
# CPU 版
conda install pytorch torchvision torchaudio cpuonly -c pytorch
# CUDA 版(如 CUDA 11.8)
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
```
> ✅ `torchaudio.load()` 默认行为:
> - 自动检测格式;
> - 自动 resample(可关);
> - 返回 `Tensor`(无需 `np.array` 转换);
> - 支持 `channels_first=True`(Asteroid 偏好)。
---
### ✅ 四、验证修复是否成功
运行以下脚本:
```python
# verify_wav.py
import soundfile as sf
import numpy as np
path = r"E:\python\ai_denoise\asteroid-master\deploy_result\infer_sample_save\g6ptz_no_nr\chunk_0000.wav"
try:
data, sr = sf.read(path)
print("✅ soundfile OK!")
print(f" shape={data.shape}, dtype={data.dtype}, sr={sr}")
print(f" min/max = {data.min():.3f} / {data.max():.3f}")
except Exception as e:
print("❌ soundfile failed:", e)
# 尝试 torchaudio fallback
try:
import torchaudio
waveform, sr = torchaudio.load(path)
print("✅ torchaudio OK!")
print(f" shape={waveform.shape}, dtype={waveform.dtype}, sr={sr}")
except Exception as e2:
print("❌ torchaudio also failed:", e2)
```
---
### 🧩 附:Asteroid 用户特别提示
- Asteroid 的 `save_wav` 工具函数默认用 `soundfile`,但你可以全局替换:
```python
from asteroid.utils import tensors_to_wav
# 修改其内部逻辑,或直接用 torchaudio.save()
import torchaudio
torchaudio.save(path, torch.tensor(data).t(), sr)
```
- 如果你用的是 `asteroid.separate` CLI,可通过环境变量启用 torchaudio:
```bash
ASTEROID_AUDIO_BACKEND=torchaudio python -m asteroid.separate ...
```
---