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

搭建网络电台如何实现实时麦克风输入与音乐播放混音及音量实时调节

实现方案

采用浏览器原生Web Audio API即可实现全部需求,无需引入IceCast等第三方工具或额外开源库。

实现逻辑

  • 创建Web Audio上下文作为音频处理中枢,分别接入麦克风输入流、背景音乐作为两个独立音频源
  • 为两个音频源分别绑定增益节点,独立控制两路音频的音量大小
  • 将两路增益节点的输出合并到同一个混音输出节点,生成的混合流直接传入MediaRecorder完成录制,录制结果就是已经完成混音的音频
  • 通过分析麦克风输入的实时音量实现自动闪避效果:当麦克风音量超过预设阈值时自动拉低背景音乐音量,音量低于阈值后延迟恢复原有背景音乐音量

修改后的完整代码

// 原有变量保留
var recordedChunks = [];
var mediaRecorder = null;
let slice = 100;                        
const slices = 20;                      
let sendfreq = slice * slices;          

// 新增音频处理相关变量
let audioContext = null;
let micSource = null;
let micGain = null;
let bgSource = null;
let bgGain = null;
let mixedStream = null;
let isDucking = false;
// 闪避参数可根据实际需求调整
const DUCK_THRESHOLD = -40; // 闪避触发音量阈值,单位db
const DUCK_AMOUNT = 0.2; // 闪避时音乐音量为原音量的比例
const DUCK_RECOVERY_DELAY = 1000; // 停止说话后恢复音量的延迟,单位ms
let recoveryTimer = null;

// 背景音乐元素,提前在HTML中添加:<audio id="bg-music" src="你的背景音乐地址" loop></audio>
const bgMusicElement = document.getElementById('bg-music');

/* get microphone button handle */
var microphoneButton = document.getElementById('console-toggle-microphone');
microphoneButton.setAttribute('on', 'no');

/* initialise mic streaming capability */
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(stream => {
    _stream = stream;
})
.catch(function(err) {
    show_error('Error: Microphone access has been denied probably!', err);
});

// 初始化音频上下文和混音链路
function initAudioContext() {
    if(audioContext) return;
    audioContext = new (window.AudioContext || window.webkitAudioContext)();
    // 麦克风链路初始化
    micSource = audioContext.createMediaStreamSource(_stream);
    micGain = audioContext.createGain();
    // 背景音乐链路初始化
    bgSource = audioContext.createMediaElementSource(bgMusicElement);
    bgGain = audioContext.createGain();
    bgGain.gain.value = 0.5; // 默认背景音乐音量,取值范围0~1
    // 混音输出节点初始化
    const mixedDestination = audioContext.createMediaStreamDestination();
    mixedStream = mixedDestination.stream;
    // 连接音频链路
    micSource.connect(micGain);
    micGain.connect(mixedDestination);
    bgSource.connect(bgGain);
    bgGain.connect(mixedDestination);
    // 混音输出到扬声器实现本地监听,不需要可删除下行
    mixedDestination.connect(audioContext.destination);
    // 启动麦克风音量检测实现闪避
    initMicVolumeDetection();
}

// 麦克风音量检测与闪避逻辑
function initMicVolumeDetection() {
    const analyser = audioContext.createAnalyser();
    analyser.fftSize = 256;
    const bufferLength = analyser.frequencyBinCount;
    const dataArray = new Uint8Array(bufferLength);
    micGain.connect(analyser);
    
    function checkVolume() {
        if(microphoneButton.getAttribute('on') !== 'yes') return;
        analyser.getByteFrequencyData(dataArray);
        let sum = 0;
        for(let i = 0; i < bufferLength; i++) {
            sum += dataArray[i];
        }
        const average = sum / bufferLength;
        const db = 20 * Math.log10(average / 255);
        if(db > DUCK_THRESHOLD) {
            // 触发闪避
            clearTimeout(recoveryTimer);
            if(!isDucking) {
                isDucking = true;
                bgGain.gain.linearRampToValueAtTime(DUCK_AMOUNT * 0.5, audioContext.currentTime + 0.1);
            }
        } else {
            // 延迟恢复音量
            if(isDucking) {
                clearTimeout(recoveryTimer);
                recoveryTimer = setTimeout(() => {
                    bgGain.gain.linearRampToValueAtTime(0.5, audioContext.currentTime + 0.3);
                    isDucking = false;
                }, DUCK_RECOVERY_DELAY);
            }
        }
        requestAnimationFrame(checkVolume);
    }
    checkVolume();
}

function toggle_mic() {
    if (microphoneButton.getAttribute('on') == 'yes')
    {
        clearInterval();
        microphoneButton.setAttribute('on', 'no');
        microphoneButton.innerHTML = 'start mic';
    }
    else if (microphoneButton.getAttribute('on') == 'no') 
    {
        // 用户交互时初始化音频上下文,符合浏览器自动播放策略要求
        initAudioContext();
        // 开麦自动播放背景音乐,不需要可删除下行
        bgMusicElement.play().catch(err => console.log('音乐播放失败,请检查资源地址', err));
        
        microphoneButton.setAttribute('on', 'yes');
        microphoneButton.innerHTML = 'stop mic';

        function record_and_send() {
            // 使用混音后的流替代原有麦克风流
            const recorder = new MediaRecorder(mixedStream);
            const chunks = [];
            recorder.ondataavailable = e => chunks.push(e.data);
            recorder.onstop = e => socket.emit('console-mic-chunks', chunks);
            setTimeout(()=> recorder.stop(), sendfreq);
            recorder.start();
        }
        setInterval(record_and_send, sendfreq); 
    }
}

功能扩展说明

  • 手动音量控制:可在UI中添加滑动条组件,将滑动条的取值(0~1)绑定到bgGain.gain.value即可实现手动实时调整背景音乐音量
  • 参数调整:代码中预留了闪避阈值、闪避幅度、恢复延迟等参数,可根据实际使用场景自行调整数值达到最优效果

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 21:36:04