webscoket+webrtc实现语音通话

码农六六 2024-06-13 10:33:06 阅读 66

1.项目方案

前端采用webrtc创建音频上下文,后创建音频源输入和音频处理器,连接音频输入与处理器,处理器再连接到音频输出(扬声器),再通过事件获取音频数据,把音频数据转换成字节数据通过webscoket发送给后端。

注意

1.前端使用的创建音频源api createScriptProcessor  onaudioprocess  已经开始废弃使用,但是浏览器依然适配。

2.因为前端使用websocket实时传输录音数据,后端开发需要多线程接受处理数据,给每个数据包提供index坐标,然后处理后保存,再通过单线程发送,可以降低延迟

2.前端代码

// AudioManager.jsexport default class AudioManager { /** * 构造函数 * @param {string} url - WebSocket服务器的地址 * @param {function} onMessageCallback - 当WebSocket接收到消息时的回调函数 */ constructor(url, onMessageCallback) { this.url = url; // WebSocket服务器的完整URL this.websocket = null; // WebSocket连接实例 this.audioContext = null; // 音频上下文 this.audioStream = null; // 流媒体对象 this.audioProcessor = null; // 音频处理器 this.onMessageCallback = onMessageCallback; // WebSocket消息的回调函数 } /** * 初始化WebSocket连接并设置消息监听器 */ initWs() { console.log(this.url,';this.url'); // 创建WebSocket实例 this.websocket = new WebSocket(this.url); // 设置WebSocket接收消息时的回调函数 this.websocket.onmessage = (e) => { if (this.onMessageCallback) { // 调用通过构造函数传入的回调函数处理接收到的消息 this.onMessageCallback(e.data); } }; // 请求用户的麦克风权限并开始处理音频流 this.queryHttp(); } /** * 停止录音并关闭所有资源 */ stopRecording() { // 关闭所有音频轨道 if (this.audioStream) { this.audioStream.getTracks().forEach((track) => track.stop()); } // 断开音频处理器的连接 if (this.audioProcessor) { this.audioProcessor.disconnect(); } // 关闭音频上下文 if (this.audioContext) { this.audioContext.close(); } // 关闭WebSocket连接 if (this.websocket) { this.websocket.close(); } } /** * 请求麦克风资源,并在成功后处理音频流 */ queryHttp() { navigator.mediaDevices .getUserMedia({ audio: { echoCancellation: true, // 开启回声消除 noiseSuppression: true, // 开启噪声抑制 autoGainControl: true, // 开启自动增益控制 }, }) .then((stream) => { // 处理成功获取的音频流 this.handleStream(stream); }) .catch((error) => { // 处理获取音频流失败的情况 console.error("Error accessing microphone:", error); }); } /** * 处理音频流,连接音频输入和处理器,并设置音频处理事件 * @param {MediaStream} stream - 从麦克风获取的音频流 */ handleStream(stream) { this.audioContext = new AudioContext({ sampleRate: 16000, // 设置采样率 latencyHint: "interactive", // 延迟模式为交互式 channels: 1, // 单声道 frameRate: 60, // 帧率 sampleType: "int16", // 采样类型 numberOfOutputs: 1, // 输出数量 }); // 创建音频源输入 let audioInput = this.audioContext.createMediaStreamSource(stream); // 创建音频处理器 this.audioProcessor = this.audioContext.createScriptProcessor(4096, 1, 1); // 设置音频处理事件 this.audioProcessor.onaudioprocess = (event) => { // 获取音频数据 const inputData = event.inputBuffer.getChannelData(0); // 转换音频数据为字节数据 const byteData = this.convertToByteData(inputData); // 通过WebSocket发送字节数据 this.websocket.send(byteData); }; // 连接音频输入与处理器,处理器再连接到音频输出(扬声器) audioInput.connect(this.audioProcessor); this.audioProcessor.connect(this.audioContext.destination); // 保存音频流引用 this.audioStream = stream; } /** * 将浮点数组的音频数据转换为字节数据 * @param {Float32Array} inputData - 浮点数组格式的原始音频数据 * @return {Uint8Array} 字节数据 */ convertToByteData(inputData) { // 创建Int16Array,由于原始的音频数据是Float32Array类型,需要转换 const intData = new Int16Array(inputData.map((item) => item * 32767)); // 创建Uint8Array来保存字节数据 const byteData = new Uint8Array(intData.length * 2); // 将Int16Array的数据转换为字节数据并填充到Uint8Array intData.forEach((value, index) => { byteData[index * 2] = value & 0xff; // 存储低位字节 byteData[index * 2 + 1] = (value >> 8) & 0xff; // 存储高位字节 }); return byteData; }}/*使用方法<template> <audio style="display: none" ref="audio" controls="controls" autoplay> <source :src="audioUrl" type="audio/wav" /> </audio></template> <script>import AudioManager from './AudioManager.js';export default { data() { return { audioManager: null, }; }, methods: { handleWsMessage(data) { let message = JSON.parse(data); this.$refs.audio.src = message.url; }, startRecording() { this.audioManager = new AudioManager( 'your-server-url', this.handleWsMessage // 回调函数用于接受通话后获取的信息这里我用于播放接受的音频 ); this.audioManager.initWs(); }, },};</script> */



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。