背景

最近,我们收到一个用户反馈:

"在语音房录制的声音文件,有文件时长,但播放时没有声音。奇怪的是,前两天还能正常录制,现在突然不行了。用户在聊天室开麦说话是正常的,但录制的PCM文件却是无声的。"

经过排查,发现问题出在系统默认麦克风被意外切换,而我们的录音功能没有显式指定设备ID,导致使用了错误的麦克风。

本文将详细分析问题原因,并提供完整的解决方案,帮助开发者避免类似坑点。

问题分析

1. 为什么PCM文件有时长但没声音?

PCM文件有时长:

音频录制流程正常,AudioContext 和 MediaStream 没有报错,数据流持续写入文件,因此文件大小和时长正常。

但文件无声:

实际采集的音频数据全为 0(静音),原因可能有:

系统默认麦克风失效(如设备损坏、驱动异常)。

浏览器使用了错误的输入设备(未显式指定 deviceId,跟随系统默认设备)。

权限或缓存问题(浏览器缓存了旧设备ID,但该设备已不可用)。

2. 为什么聊天室能正常开麦,但录音不行?

聊天室:

用户手动选择了可用的麦克风(如外接声卡),因此通话正常。

录音功能:

代码没有强制指定 deviceId,导致浏览器自动选择系统默认麦克风(可能是一个无效设备)。

技术排查过程

1. 验证音频输入设备

首先,我们让用户运行以下代码,检查可用的麦克风设备:

(async () => {

await navigator.mediaDevices.getUserMedia({ audio: true });

const devices = await navigator.mediaDevices.enumerateDevices();

const mics = devices.filter(d => d.kind === 'audioinput');

console.log("可用麦克风:", mics.map(m => ({

id: m.deviceId,

label: m.label || '未知设备',

isDefault: m.deviceId === 'default'

})));

})();

输出示例:

[

{ "id": "default", "label": "麦克风 (Realtek Audio)", "isDefault": true },

{ "id": "communications", "label": "耳机麦克风 (USB Audio)", "isDefault": false }

]

发现问题:

系统默认麦克风 (default) 是坏的,但用户手动选择了 USB Audio 设备进行聊天。

2. 检查实际录制的设备

我们让用户运行以下代码,确认录音时使用的设备:

const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

const actualDeviceId = stream.getAudioTracks()[0].getSettings().deviceId;

console.log("实际录制设备ID:", actualDeviceId);

结果:

录音功能使用了 default 设备(坏的麦克风),而聊天室使用的是 USB Audio。

3. 根本原因

浏览器默认行为:

如果不指定 deviceId,getUserMedia() 会使用系统默认麦克风(default)。

用户环境变化:

可能是 Windows 更新、驱动问题或第三方软件(如 Zoom)修改了默认设备。

解决方案

1. 显式指定麦克风设备

在录音时,强制使用用户之前选择的设备(如聊天室使用的麦克风):

// 存储用户选择的设备ID(在聊天室开麦时调用)

function saveUserMicPreference(deviceId) {

localStorage.setItem('userPreferredMic', deviceId);

}

// 录音时优先使用用户的选择

async function startRecording() {

const preferredDeviceId = localStorage.getItem('userPreferredMic');

const stream = await navigator.mediaDevices.getUserMedia({

audio: preferredDeviceId ? {

deviceId: { exact: preferredDeviceId } // 强制锁定设备

} : {

// 回退逻辑:让用户手动选择

echoCancellation: false,

noiseSuppression: false,

autoGainControl: false

}

});

// 验证实际使用的设备

const actualDevice = stream.getAudioTracks()[0].getSettings();

console.log("实际录制设备:", actualDevice.label || actualDevice.deviceId);

}

2. 设备变更监听

监听设备插拔事件,确保设备ID仍然有效:

navigator.mediaDevices.ondevicechange = async () => {

const devices = await navigator.mediaDevices.enumerateDevices();

const savedDeviceId = localStorage.getItem('userPreferredMic');

// 如果设备已断开,清除存储

if (savedDeviceId && !devices.some(d => d.deviceId === savedDeviceId)) {

localStorage.removeItem('userPreferredMic');

alert('您之前使用的麦克风已断开,请重新选择!');

}

};

3. 提供设备选择UI

让用户手动选择麦克风,避免依赖默认设备:

总结

问题根源

浏览器默认使用系统麦克风,而系统麦克风可能失效。

用户手动选择的设备未被录音功能继承,导致无声。

最佳实践

✅ 显式指定 deviceId,不要依赖默认设备。

✅ 持久化用户选择的麦克风(如 localStorage)。

✅ 监听设备变更,避免使用已断开的设备。

✅ 提供设备选择UI,让用户手动切换。

后续优化

增加静音检测:在录制时实时分析音频数据,如果长时间静音,提示用户检查麦克风。

日志记录:上报实际使用的设备ID,便于排查问题。