博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WEB前端语音对讲实现方案以及示例
阅读量:2154 次
发布时间:2019-05-01

本文共 8085 字,大约阅读时间需要 26 分钟。

WEB前端语音对讲实现方案以及示例

需求

司机需要通过车载终端设备与WEB平台进行语音对讲,目前已实现WebSocket服务端,需要实现客户端

说到websocket想比大家不会陌生,如果陌生的话也没关系,一句话概括

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信

实现

我们需要在web端实现双向对讲,首先创建html文件,这个只是用来做个demo,比较简单:

    
test

难点

第一个是实时输出音频数据,第二是实时解析输入的音频流并进行播放

1.实时输出音频数据

这里要介绍一下Navigator.getUserMedia()方法,

该方法提醒用户需要使用的设备类型,音频(true或者false)和视频(true或者false),比如麦克风。
如下例,我们使用音频输入,获取mediaStream,构建相关业务对象:

function init(rec){	record = red;}if (!navigator.getUserMedia) {	alert('浏览器不支持音频输入');}else{	navigator.getUserMedia(            { audio: true },              function (mediaStream) {                  init(new Recorder(mediaStream));            },function(error){				console.log(error)			}	)}//录音对象var Recorder = function(stream) {    var sampleBits = 16;//输出采样数位 8, 16    var sampleRate = 8000;//输出采样率    var context = new AudioContext();    var audioInput = context.createMediaStreamSource(stream);    var recorder = context.createScriptProcessor(4096, 1, 1);    var audioData = {        size: 0          //录音文件长度        , buffer: []    //录音缓存        , inputSampleRate: sampleRate    //输入采样率        , inputSampleBits: 16      //输入采样数位 8, 16        , outputSampleRate: sampleRate            , oututSampleBits: sampleBits              , clear: function() {            this.buffer = [];            this.size = 0;        }        , input: function (data) {            this.buffer.push(new Float32Array(data));            this.size += data.length;        }        , compress: function () { //合并压缩            //合并            var data = new Float32Array(this.size);            var offset = 0;            for (var i = 0; i < this.buffer.length; i++) {                data.set(this.buffer[i], offset);                offset += this.buffer[i].length;            }            //压缩            var compression = parseInt(this.inputSampleRate / this.outputSampleRate);            var length = data.length / compression;            var result = new Float32Array(length);            var index = 0, j = 0;            while (index < length) {                result[index] = data[j];                j += compression;                index++;            }            return result;        }, encodePCM: function(){//这里不对采集到的数据进行其他格式处理,如有需要均交给服务器端处理。			var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate);            var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits);            var bytes = this.compress();			var dataLength = bytes.length * (sampleBits / 8);            var buffer = new ArrayBuffer(dataLength);			var data = new DataView(buffer);			var offset = 0;			for (var i = 0; i < bytes.length; i++, offset += 2) {            	var s = Math.max(-1, Math.min(1, bytes[i]));            	data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);            }			return new Blob([data]);		}	};	this.start = function () {        audioInput.connect(recorder);        recorder.connect(context.destination);    }     this.stop = function () {        recorder.disconnect();    }     this.getBlob = function () {        return audioData.encodePCM();    }     this.clear = function() {        audioData.clear();    }     recorder.onaudioprocess = function (e) {        audioData.input(e.inputBuffer.getChannelData(0));    }  };

至此我们可以拿到麦克风输入的音频数据,但是要实现语音对讲,还需要进行一些处理,将数据实时输出到服务器,我这边使用了定时器,每隔500毫秒发送一次语音数据给服务器,代码如下:

begin.onclick = function() {	var ws = new WebSocket("ws://192.168.168.4:6604");     ws.onopen = function() {        console.log('握手成功');        //业务命令构建		var data = {		  "cmd": "jtv",//发送命令		  "id": "018665897939",//发送设备id		  "type": 1,//对讲类型		  "channel": 0//语音通道		}        ws.send(JSON.stringify(data));    };    timeInte=setInterval(function(){		if(gRecorder&&ws.readyState==1){//ws进入连接状态,则每隔500毫秒发送一包数据			record.start();			ws.send(record.getBlob()); 			record.clear();	//每次发送完成则清理掉旧数据				}	},500);}

注意:该功能前置条件是已经实现WebSocket服务器端。

2.实时数据解析,播放

接上以上示例,我们已经实现了WebSocket,服务器端会实时返回二进制数据到前端,我们需要对数据进行解析和处理,转码成WAV格式进行播放,废话不多说,先上代码:

ws.onmessage = function(e) {        receive(e.data);    };

由于websocket属于长连接,所以我们这边再也不需要进行传统的轮询等复杂操作,只需要实现onmessage即可实时获取服务器发送过来的数据,服务器首先会返回一串字符串,对我们发送的命令进行回馈,OK之后才会开始发送音频数据给我们,ws支持字符串和二进制数据发送,音频数据自然属于后者,所以我们需要对数据进行解析,这里我们用到了 ,介绍一下它吧,AudioContext接口是一个音频上下文对象,表示由音频模块连接而成的音频处理对象,AudioContext可以控制它所包含的节点的创建,以及音频处理、解码操作的执行。

接下来我们进行二进制数据的解析和播放:

function receive(data) {		if( typeof e == 'string' && JSON.parse(e).message=='OK'){			console.log('OK');		}else{			var buffer = (new Response(data)).arrayBuffer();			buffer.then(function(buf){				var audioContext = new ( window.AudioContext || window.webkitAudioContext )();				var fileResult =addWavHeader(buf, '8000', '16', '1');//解析数据转码wav				audioContext.decodeAudioData(fileResult, function(buffer) {				   _visualize(audioContext,buffer);//播放				});			});		}	}

DataView 视图是一个可以从 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。

//处理音频流,转码wavvar addWavHeader = function(samples,sampleRateTmp,sampleBits,channelCount){    var dataLength = samples.byteLength;    var buffer = new ArrayBuffer(44 + dataLength);    var view = new DataView(buffer);    function writeString(view, offset, string){        for (var i = 0; i < string.length; i++){            view.setUint8(offset + i, string.charCodeAt(i));        }    }    var offset = 0;    /* 资源交换文件标识符 */    writeString(view, offset, 'RIFF'); offset += 4;    /* 下个地址开始到文件尾总字节数,即文件大小-8 */    view.setUint32(offset, /*32*/ 36 + dataLength, true); offset += 4;    /* WAV文件标志 */    writeString(view, offset, 'WAVE'); offset += 4;    /* 波形格式标志 */    writeString(view, offset, 'fmt '); offset += 4;    /* 过滤字节,一般为 0x10 = 16 */    view.setUint32(offset, 16, true); offset += 4;    /* 格式类别 (PCM形式采样数据) */    view.setUint16(offset, 1, true); offset += 2;    /* 通道数 */    view.setUint16(offset, channelCount, true); offset += 2;     /* 采样率,每秒样本数,表示每个通道的播放速度 */    view.setUint32(offset, sampleRateTmp, true); offset += 4;    /* 波形数据传输率 (每秒平均字节数) 通道数×每秒数据位数×每样本数据位/8 */    view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset +=4;    /* 快数据调整数 采样一次占用字节数 通道数×每样本的数据位数/8 */    view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;    /* 每样本数据位数 */    view.setUint16(offset, sampleBits, true); offset += 2;    /* 数据标识符 */    writeString(view, offset, 'data'); offset += 4;    /* 采样数据总数,即数据总大小-44 */    view.setUint32(offset, dataLength, true); offset += 4;    function floatTo32BitPCM(output, offset, input){        input = new Int32Array(input);        for (var i = 0; i < input.length; i++, offset+=4){            output.setInt32(offset,input[i],true);        }    }   function floatTo16BitPCM(output, offset, input){        input = new Int16Array(input);        for (var i = 0; i < input.length; i++, offset+=2){            output.setInt16(offset,input[i],true);        }    }    function floatTo8BitPCM(output, offset, input){        input = new Int8Array(input);        for (var i = 0; i < input.length; i++, offset++){            output.setInt8(offset,input[i],true);        }    }    if(sampleBits == 16){        floatTo16BitPCM(view, 44, samples);    }else if(sampleBits == 8){        floatTo8BitPCM(view, 44, samples);    }else{        floatTo32BitPCM(view, 44, samples);    }    return view.buffer;  }//播放音频  var _visualize = function(audioContext, buffer) {    var audioBufferSouceNode = audioContext.createBufferSource(),        analyser = audioContext.createAnalyser(),        that = this;    //将信号源连接到分析仪    audioBufferSouceNode.connect(analyser);    //将分析仪连接到目的地(扬声器),否则我们将听不到声音    analyser.connect(audioContext.destination);    //然后将缓冲区分配给缓冲区源节点    audioBufferSouceNode.buffer = buffer;    //发挥作用    if (!audioBufferSouceNode.start) {        audioBufferSouceNode.start = audioBufferSouceNode.noteOn //在旧浏览器中使用noteOn方法        audioBufferSouceNode.stop = audioBufferSouceNode.noteOff //在旧浏览器中使用noteOff方法    };    //如果有的话,停止前一个声音    if (this.animationId !== null) {        cancelAnimationFrame(this.animationId);    }    audioBufferSouceNode.start(0);    audo.source = audioBufferSouceNode;    audo.audioContext = audioContext;}

到此,我们就实现了双向对讲全部功能。

本篇文章也参考以下资料:

转载地址:http://lqtwb.baihongyu.com/

你可能感兴趣的文章
linux系统 阿里云源
查看>>
国内外helm源记录
查看>>
牛客网题目1:最大数
查看>>
散落人间知识点记录one
查看>>
Leetcode C++ 随手刷 547.朋友圈
查看>>
手抄笔记:深入理解linux内核-1
查看>>
内存堆与栈
查看>>
Leetcode C++《每日一题》20200621 124.二叉树的最大路径和
查看>>
Leetcode C++《每日一题》20200622 面试题 16.18. 模式匹配
查看>>
Leetcode C++《每日一题》20200625 139. 单词拆分
查看>>
Leetcode C++《每日一题》20200626 338. 比特位计数
查看>>
Leetcode C++ 《拓扑排序-1》20200626 207.课程表
查看>>
Go语言学习Part1:包、变量和函数
查看>>
Go语言学习Part2:流程控制语句:for、if、else、switch 和 defer
查看>>
Go语言学习Part3:struct、slice和映射
查看>>
Go语言学习Part4-1:方法和接口
查看>>
Leetcode Go 《精选TOP面试题》20200628 69.x的平方根
查看>>
leetcode 130. Surrounded Regions
查看>>
【托业】【全真题库】TEST2-语法题
查看>>
博客文格式优化
查看>>