永利皇宫402websocket探究其与话音

永利皇宫402 6
永利皇宫402

websocket索求其与语音、图片的手艺

2015/12/26 · JavaScript
· 3 评论 ·
websocket

原来的文章出处:
AlloyTeam   

说起websocket想比大家不会不熟悉,若是面生的话也没提到,一句话总结

“WebSocket protocol
是HTML5意气风发种新的合计。它达成了浏览器与服务器全双工通信”

WebSocket相比较古板那么些服务器推本领几乎好了太多,大家能够挥手向comet和长轮询那几个技艺说拜拜啦,庆幸大家生存在具有HTML5的时代~

那篇小说大家将分三有的探求websocket

第一是websocket的周围使用,其次是截然自身制作服务器端websocket,最后是重视介绍利用websocket制作的多少个demo,传输图片和在线语音聊天室,let’s
go

风姿罗曼蒂克、websocket习认为常用法

此地介绍两种自身以为大面积的websocket实现……(在乎:本文创设在node上下文意况

1、socket.io

先给demo

JavaScript

var http = require(‘http’); var io = require(‘socket.io’); var server =
http.createServer(function(req, res) { res.writeHeader(200,
{‘content-type’: ‘text/html;charset=”utf-8″‘}); res.end();
}).listen(8888); var socket =.io.listen(server);
socket.sockets.on(‘connection’, function(socket) { socket.emit(‘xxx’,
{options}); socket.on(‘xxx’, function(data) { // do someting }); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var http = require(‘http’);
var io = require(‘socket.io’);
 
var server = http.createServer(function(req, res) {
    res.writeHeader(200, {‘content-type’: ‘text/html;charset="utf-8"’});
    res.end();
}).listen(8888);
 
var socket =.io.listen(server);
 
socket.sockets.on(‘connection’, function(socket) {
    socket.emit(‘xxx’, {options});
 
    socket.on(‘xxx’, function(data) {
        // do someting
    });
});

信赖明白websocket的同窗不或然不晓得socket.io,因为socket.io太著名了,也很棒,它本身对过期、握手等都做了管理。作者质疑那也是实现websocket使用最多的措施。socket.io最最最美貌的一点正是高贵降级,当浏览器不扶植websocket时,它会在里面文雅降级为长轮询等,客商和开荒者是没有必要关注具体落到实处的,很便利。

唯独事情是有两面性的,socket.io因为它的一揽子也带动了坑的地点,最要害的正是肥胖,它的卷入也给多少拉动了很多的通信冗余,而且华贵降级那黄金年代亮点,也伴随浏览器标准化的拓宽稳步失去了硬汉

Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+

在这里地不是指责说socket.io不佳,已经被淘汰了,而是不经常候我们也得以思考部分其余的贯彻~

 

2、http模块

恰巧说了socket.io肥胖,那今后就来讲说便捷的,首先demo

JavaScript

var http = require(‘http’); var server = http.createServer();
server.on(‘upgrade’, function(req) { console.log(req.headers); });
server.listen(8888);

1
2
3
4
5
6
var http = require(‘http’);
var server = http.createServer();
server.on(‘upgrade’, function(req) {
console.log(req.headers);
});
server.listen(8888);

很简短的达成,其实socket.io内部对websocket也是这么达成的,可是后边帮我们封装了部分handle管理,这里我们也得以友善去丰裕,给出两张socket.io中的源码图

永利皇宫402 1

永利皇宫402 2

 

3、ws模块

后边有个例证会用到,这里就提一下,前面具体看~

 

二、本人完毕生龙活虎套server端websocket

刚巧说了三种普及的websocket达成格局,以后我们寻思,对于开辟者来讲

websocket相对于守旧http数据交互方式以来,扩大了服务器推送的平地风波,客商端选取到事件再开展对应管理,开辟起来分裂并非太大呀

那是因为那个模块已经帮大家将数码帧深入剖判此处的坑都填好了,第二部分大家将尝试本人创建生机勃勃套简便的服务器端websocket模块

谢谢次碳酸钴的钻研协理,本人在那间那有个别只是简短说下,若是对此有意思味好奇的请百度【web本领钻探所】

温馨姣好服务器端websocket重要有两点,二个是选用net模块选拔数据流,还也许有一个是比照官方的帧结构图深入分析数据,达成这两片段就早就做到了全副的最底层专门的学业

首先给贰个顾客端发送websocket握手报文的抓包内容

客商端代码十分轻便

JavaScript

ws = new WebSocket(“ws://127.0.0.1:8888”);

1
ws = new WebSocket("ws://127.0.0.1:8888");

永利皇宫402 3

劳务器端要针对性这些key验证,正是讲key加上贰个一定的字符串后做二遍sha1运算,将其结果转换为base64送重回

JavaScript

var crypto = require(‘crypto’); var WS =
‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’;
require(‘net’).createServer(function(o) { var key;
o.on(‘data’,function(e) { if(永利皇宫402,!key) { // 获取发送过来的KEY key =
e.toString().match(/Sec-WebSocket-Key: (.+)/)[1]; //
连接上WS这一个字符串,并做一回sha1运算,最终调换来Base64 key =
crypto.createHash(‘sha1’).update(key+WS).digest(‘base64’); //
输出重返给顾客端的数量,这几个字段都以必得的 o.write(‘HTTP/1.1 101
Switching Protocols\r\n’); o.write(‘Upgrade: websocket\r\n’);
o.write(‘Connection: Upgrade\r\n’); // 那个字段带上服务器管理后的KEY
o.write(‘Sec-WebSocket-Accept: ‘+key+’\r\n’); //
输出空行,使HTTP头结束 o.write(‘\r\n’); } }); }).listen(8888);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var crypto = require(‘crypto’);
var WS = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11’;
 
require(‘net’).createServer(function(o) {
var key;
o.on(‘data’,function(e) {
if(!key) {
// 获取发送过来的KEY
key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1];
// 连接上WS这个字符串,并做一次sha1运算,最后转换成Base64
key = crypto.createHash(‘sha1’).update(key+WS).digest(‘base64’);
// 输出返回给客户端的数据,这些字段都是必须的
o.write(‘HTTP/1.1 101 Switching Protocols\r\n’);
o.write(‘Upgrade: websocket\r\n’);
o.write(‘Connection: Upgrade\r\n’);
// 这个字段带上服务器处理后的KEY
o.write(‘Sec-WebSocket-Accept: ‘+key+’\r\n’);
// 输出空行,使HTTP头结束
o.write(‘\r\n’);
}
});
}).listen(8888);

这么握手部分就早就成功了,前面就是数据帧深入深入分析与转换的活了

先看下官方提供的帧结构暗意图

永利皇宫402 4

归纳介绍下

FIN为是或不是得了的标示

MuranoSV为留下空间,0

opcode标识数据类型,是还是不是分片,是还是不是二进制解析,心跳包等等

提交一张opcode对应图

永利皇宫402 5

MASK是不是选用掩码

Payload len和前边extend payload length表示数据长度,那个是最麻烦的

PayloadLen独有7位,换到无符号整型的话唯有0到127的取值,这么小的数值当然不可能描述不小的多寡,因而显明当数码长度小于或等于125时候它才作为数据长度的陈述,借使那几个值为126,则时候背后的三个字节来囤积数据长度,要是为127则用前面几个字节来累积数据长度

Masking-key掩码

上边贴出深入解析数据帧的代码

JavaScript

function decodeDataFrame(e) { var i = 0, j,s, frame = { FIN: e[i]
>> 7, Opcode: e[i++] & 15, Mask: e[i] >> 7,
PayloadLength: e[i++] & 0x7F }; if(frame.PayloadLength === 126) {
frame.PayloadLength = (e[i++] << 8) + e[i++]; }
if(frame.PayloadLength === 127) { i += 4; frame.PayloadLength =
(e[i++] << 24) + (e[i++] << 16) + (e[i++] << 8)

  • e[i++]; } if(frame.Mask) { frame.MaskingKey = [e[i++], e[i++],
    e[i++], e[i++]]; for(j = 0, s = []; j < frame.PayloadLength;
    j++) { s.push(e[i+j] ^ frame.MaskingKey[j%4]); } } else { s =
    e.slice(i, i+frame.PayloadLength); } s = new Buffer(s); if(frame.Opcode
    === 1) { s = s.toString(); } frame.PayloadData = s; return frame; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function decodeDataFrame(e) {
var i = 0,
j,s,
frame = {
FIN: e[i] >> 7,
Opcode: e[i++] & 15,
Mask: e[i] >> 7,
PayloadLength: e[i++] & 0x7F
};
 
if(frame.PayloadLength === 126) {
frame.PayloadLength = (e[i++] << 8) + e[i++];
}
 
if(frame.PayloadLength === 127) {
i += 4;
frame.PayloadLength = (e[i++] << 24) + (e[i++] << 16) + (e[i++] << 8) + e[i++];
}
 
if(frame.Mask) {
frame.MaskingKey = [e[i++], e[i++], e[i++], e[i++]];
 
for(j = 0, s = []; j < frame.PayloadLength; j++) {
s.push(e[i+j] ^ frame.MaskingKey[j%4]);
}
} else {
s = e.slice(i, i+frame.PayloadLength);
}
 
s = new Buffer(s);
 
if(frame.Opcode === 1) {
s = s.toString();
}
 
frame.PayloadData = s;
return frame;
}

下一场是生成数据帧的

JavaScript

function encodeDataFrame(e) { var s = [], o = new
Buffer(e.PayloadData), l = o.length; s.push((e.FIN << 7) +
e.Opcode); if(l < 126) { s.push(l); } else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF); } else { s.push(127, 0, 0,
0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00)
>> 8, l&0xFF); } return Buffer.concat([new Buffer(s), o]); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function encodeDataFrame(e) {
var s = [],
o = new Buffer(e.PayloadData),
l = o.length;
 
s.push((e.FIN << 7) + e.Opcode);
 
if(l < 126) {
s.push(l);
} else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF);
} else {
s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00) >> 8, l&0xFF);
}
 
return Buffer.concat([new Buffer(s), o]);
}

都以根据帧结构暗示图上的去管理,在此不细讲,小说首要在下有些,即使对那块感兴趣的话能够活动web工夫研商所~

 

三、websocket传输图片和websocket语音聊天室

正片环节到了,那篇随笔最关键的或然显得一下websocket的部分应用景况

1、传输图片

作者们先探讨传输图片的手续是何许,首先服务器收到到顾客端乞请,然后读取图片文件,将二进制数据转载给客户端,客商端如何处理?当然是行使FileReader对象了

先给客商端代码

JavaScript

var ws = new WebSocket(“ws://xxx.xxx.xxx.xxx:8888”); ws.onopen =
function(){ console.log(“握手成功”); }; ws.onmessage = function(e) { var
reader = new File里德r(); reader.onload = function(event) { var
contents = event.target.result; var a = new Image(); a.src = contents;
document.body.appendChild(a); } reader.readAsDataUENVISIONL(e.data); };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var ws = new WebSocket("ws://xxx.xxx.xxx.xxx:8888");
 
ws.onopen = function(){
    console.log("握手成功");
};
 
ws.onmessage = function(e) {
    var reader = new FileReader();
    reader.onload = function(event) {
        var contents = event.target.result;
        var a = new Image();
        a.src = contents;
        document.body.appendChild(a);
    }
    reader.readAsDataURL(e.data);
};

吸收接纳到新闻,然后readAsDataUPAJEROL,直接将图片base64加多到页面中

转到服务器端代码

JavaScript

fs.readdir(“skyland”, function(err, files) { if(err) { throw err; }
for(var i = 0; i < files.length; i++) { fs.readFile(‘skyland/’ +
files[i], function(err, data) { if(err) { throw err; }
o.write(encodeImgFrame(data)); }); } }); function encodeImgFrame(buf) {
var s = [], l = buf.length, ret = []; s.push((1 << 7) + 2);
if(l < 126) { s.push(l); } else if(l < 0x10000) { s.push(126,
(l&0xFF00) >> 8, l&0xFF); } else { s.push(127, 0, 0, 0, 0,
(l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00)
>> 8, l&0xFF); } return Buffer.concat([new Buffer(s), buf]); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
fs.readdir("skyland", function(err, files) {
if(err) {
throw err;
}
for(var i = 0; i < files.length; i++) {
fs.readFile(‘skyland/’ + files[i], function(err, data) {
if(err) {
throw err;
}
 
o.write(encodeImgFrame(data));
});
}
});
 
function encodeImgFrame(buf) {
var s = [],
l = buf.length,
ret = [];
 
s.push((1 << 7) + 2);
 
if(l < 126) {
s.push(l);
} else if(l < 0x10000) {
s.push(126, (l&0xFF00) >> 8, l&0xFF);
} else {
s.push(127, 0, 0, 0, 0, (l&0xFF000000) >> 24, (l&0xFF0000) >> 16, (l&0xFF00) >> 8, l&0xFF);
}
 
return Buffer.concat([new Buffer(s), buf]);
}

注意s.push((1 << 7) +
2)
这一句,这里非常直接把opcode写死了为2,对于Binary
Frame,那样客户端接受到数码是不会尝试实行toString的,不然会报错~

代码相当的粗略,在那向我们享用一下websocket传输图片的进度怎么着

测验很多张图纸,总共8.24M

经常来说静态财富服务器必要20s左右(服务器较远)

cdn需要2.8s左右

那我们的websocket情势啊??!

答案是同生龙活虎需求20s左右,是还是不是很失望……速度正是慢在传输上,并不是服务器读取图片,本机上生龙活虎致的图纸资源,1s左右能够成功……那样看来数据流也回天无力冲破间隔的约束进步传输速度

上边大家来拜候websocket的另贰个用法~

 

用websocket搭建语音聊天室

先来收拾一下口音聊天室的效应

客户进入频道随后从迈克风输入音频,然后发送给后台转载给频道里面包车型大巴别的人,别的人选择到音信进行播报

看起来困难在七个地方,第贰个是节奏的输入,第二是收到到数量流进行播放

先说音频的输入,这里运用了HTML5的getUserMedia方法,可是注意了,这一个主意上线是有北角的,最终说,先贴代码

JavaScript

if (navigator.getUserMedia) { navigator.getUserMedia( { audio: true },
function (stream) { var rec = new SRecorder(stream); recorder = rec; })
}

1
2
3
4
5
6
7
8
if (navigator.getUserMedia) {
    navigator.getUserMedia(
        { audio: true },
        function (stream) {
            var rec = new SRecorder(stream);
            recorder = rec;
        })
}

首先个参数是{audio:
true},只启用音频,然后制造了贰个SRecorder对象,后续的操作基本上都在这里个目的上举行。那时候大器晚成经代码运转在地头的话浏览器应该提拔您是或不是启用Mike风输入,鲜明未来就开动了

接下去大家看下SRecorder构造函数是甚,给出主要的部分

JavaScript

var SRecorder = function(stream) { …… var context = new AudioContext();
var audioInput = context.createMediaStreamSource(stream); var recorder =
context.createScriptProcessor(4096, 1, 1); …… }

1
2
3
4
5
6
7
var SRecorder = function(stream) {
    ……
   var context = new AudioContext();
    var audioInput = context.createMediaStreamSource(stream);
    var recorder = context.createScriptProcessor(4096, 1, 1);
    ……
}

奥迪oContext是二个节奏上下文对象,有做过声音过滤管理的校友应该精通“风度翩翩段音频达到扬声器举行广播以前,半路对其打开拦截,于是我们就获得了点子数据了,那些拦截职业是由window.奥迪(Audi)oContext来做的,大家具备对旋律的操作都基于那些指标”,大家能够因而奥迪oContext成立不一致的奥迪(Audi)oNode节点,然后加多滤镜播放极度的声响

录音原理同样,大家也急需走奥迪(Audi)oContext,但是多了一步对Mike风音频输入的吸取上,而不是像过去管理音频一下用ajax必要音频的ArrayBuffer对象再decode,Mike风的负责须求用到createMediaStreamSource方法,注意这一个参数正是getUserMedia方法第贰个参数的参数

加以createScriptProcessor方法,它官方的表达是:

Creates a ScriptProcessorNode, which can be used for direct audio
processing via JavaScript.

——————

回顾下就是那些点子是利用JavaScript去管理音频采撷操作

终于到点子搜罗了!胜利就在前头!

接下去让大家把迈克风的输入和旋律收集相连起来

JavaScript

audioInput.connect(recorder); recorder.connect(context.destination);

1
2
audioInput.connect(recorder);
recorder.connect(context.destination);

context.destination官方表达如下

The destination property of
the AudioContext interface
returns
an AudioDestinationNoderepresenting
the final destination of all audio in the context.

——————

context.destination重回代表在碰到中的音频的尾声目标地。

好,到了那个时候,我们还须要五个监听音频收集的事件

JavaScript

recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0)); }

1
2
3
recorder.onaudioprocess = function (e) {
    audioData.input(e.inputBuffer.getChannelData(0));
}

audioData是二个目的,那么些是在网络找的,作者就加了一个clear方法因为背后会用到,首要有不行encodeWAV方法相当赞,别人实行了数11次的音频压缩和优化,那个最后会陪伴完整的代码一同贴出来

那时候一切客户步入频道随后从迈克风输入音频环节就早已成功啦,上面就该是向服务器端发送音频流,稍稍有一点点蛋疼的来了,刚才我们说了,websocket通过opcode差别能够代表回去的数量是文件照旧二进制数据,而作者辈onaudioprocess中input进去的是数组,最后播放声音供给的是Blob,{type:
‘audio/wav’}的指标,那样我们就不得不要在发送在此之前将数组转变来WAV的Blob,当时就用到了上面说的encodeWAV方法

服务器就如十分轻巧,只要转载就行了

本地质度量试确实能够,然则天坑来了!将次第跑在服务器上时候调用getUserMedia方法提醒作者必得在三个金昌的意况,也正是内需https,那表示ws也非得换来wss……故而服务器代码就一直不采取大家团结包装的抓手、深入分析和编码了,代码如下

JavaScript

var https = require(‘https’); var fs = require(‘fs’); var ws =
require(‘ws’); var userMap = Object.create(null); var options = { key:
fs.readFileSync(‘./privatekey.pem’), cert:
fs.readFileSync(‘./certificate.pem’) }; var server =
https.createServer(options, function(req, res) { res.writeHead({
‘Content-Type’ : ‘text/html’ }); fs.readFile(‘./testaudio.html’,
function(err, data) { if(err) { return ; } res.end(data); }); }); var
wss = new ws.Server({server: server}); wss.on(‘connection’, function(o)
{ o.on(‘message’, function(message) { if(message.indexOf(‘user’) === 0)
{ var user = message.split(‘:’)[1]; userMap[user] = o; } else {
for(var u in userMap) { userMap[u].send(message); } } }); });
server.listen(8888);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var https = require(‘https’);
var fs = require(‘fs’);
var ws = require(‘ws’);
var userMap = Object.create(null);
var options = {
    key: fs.readFileSync(‘./privatekey.pem’),
    cert: fs.readFileSync(‘./certificate.pem’)
};
var server = https.createServer(options, function(req, res) {
    res.writeHead({
        ‘Content-Type’ : ‘text/html’
    });
 
    fs.readFile(‘./testaudio.html’, function(err, data) {
        if(err) {
            return ;
        }
 
        res.end(data);
    });
});
 
var wss = new ws.Server({server: server});
 
wss.on(‘connection’, function(o) {
    o.on(‘message’, function(message) {
if(message.indexOf(‘user’) === 0) {
    var user = message.split(‘:’)[1];
    userMap[user] = o;
} else {
    for(var u in userMap) {
userMap[u].send(message);
    }
}
    });
});
 
server.listen(8888);

代码照旧很简短的,使用https模块,然后用了始于说的ws模块,userMap是效仿的频段,只兑现转载的宗旨作用

采纳ws模块是因为它极其https实现wss实在是太方便了,和逻辑代码0冲突

https的搭建在这里地就不提了,首假如急需私钥、CSHighlander证书签字和证书文件,感兴趣的同室能够了然下(不过不打听的话在现网情况也用持续getUserMedia……)

上边是共同体的前端代码

JavaScript

var a = document.getElementById(‘a’); var b =
document.getElementById(‘b’); var c = document.getElementById(‘c’);
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia; var gRecorder = null; var audio =
document.querySelector(‘audio’); var door = false; var ws = null;
b.onclick = function() { if(a.value === ”) { alert(‘请输入客商名’);
return false; } if(!navigator.getUserMedia) {
alert(‘抱歉您的装置无英文音聊天’); return false; }
SRecorder.get(function (rec) { gRecorder = rec; }); ws = new
WebSocket(“wss://x.x.x.x:8888”); ws.onopen = function() {
console.log(‘握手成功’); ws.send(‘user:’ + a.value); }; ws.onmessage =
function(e) { receive(e.data); }; document.onkeydown = function(e) {
if(e.keyCode === 65) { if(!door) { gRecorder.start(); door = true; } }
}; document.onkeyup = function(e) { if(e.keyCode === 65) { if(door) {
ws.send(gRecorder.getBlob()); gRecorder.clear(); gRecorder.stop(); door
= false; } } } } c.onclick = function() { if(ws) { ws.close(); } } var
SRecorder = function(stream) { config = {}; config.sampleBits =
config.smapleBits || 8; config.sampleRate = config.sampleRate || (44100
/ 6); var context = new 奥迪(Audi)oContext(); var audioInput =
context.createMediaStreamSource(stream); var recorder =
context.createScriptProcessor(4096, 1, 1); var audioData = { size: 0
//录音文件长度 , buffer: [] //录音缓存 , inputSampleRate:
context.sampleRate //输入采集样本率 , inputSampleBits: 16 //输入采集样本数位 8,
16 , output萨姆pleRate: config.sampleRate //输出采集样板率 , outut萨姆pleBits:
config.sampleBits //输出采集样板数位 8, 16 , 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; } , encodeWAV: 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(44 + dataLength); var data = new
DataView(buffer); var channelCount = 1;//单声道 var offset = 0; var
writeString = function (str) { for (var i = 0; i < str.length; i++) {
data.setUint8(offset + i, str.charCodeAt(i)); } }; // 能源交换文件标志符
writeString(‘福特ExplorerIFF’); offset += 4; //
下个地点开头到文件尾总字节数,即文件大小-8 data.setUint32(offset, 36 +
dataLength, true); offset += 4; // WAV文件注明 writeString(‘WAVE’);
offset += 4; // 波形格式标记 writeString(‘fmt ‘); offset += 4; //
过滤字节,平日为 0x10 = 16 data.setUint32(offset, 16, true); offset += 4;
// 格式系列 (PCM格局采集样本数据) data.setUint16(offset, 1, true); offset +=
2; // 通道数 data.setUint16(offset, channelCount, true); offset += 2; //
采集样板率,每秒样品数,表示每一个通道的播音速度 data.setUint32(offset,
sampleRate, true); offset += 4; // 波形数据传输率 (每秒平均字节数)
单声道×每秒数据位数×每样板数据位/8 data.setUint32(offset, channelCount
* sampleRate * (sampleBits / 8), true); offset += 4; // 快数据调治数
采集样板三遍占用字节数 单声道×每样板的多寡位数/8 data.setUint16(offset,
channelCount * (sampleBits / 8), true); offset += 2; // 每样品数量位数
data.setUint16(offset, sampleBits, true); offset += 2; // 数据标记符
writeString(‘data’); offset += 4; // 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength, true); offset += 4; // 写入采集样板数据
if (sampleBits === 8) { for (var i = 0; i < bytes.length; i++,
offset++) { var s = Math.max(-1, Math.min(1, bytes[i])); var val = s
< 0 ? s * 0x8000 : s * 0x7FFF; val = parseInt(255 / (65535 / (val +
32768))); data.setInt8(offset, val, true); } } else { 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], { type: ‘audio/wav’ }); }
}; this.start = function () { audioInput.connect(recorder);
recorder.connect(context.destination); } this.stop = function () {
recorder.disconnect(); } this.getBlob = function () { return
audioData.encodeWAV(); } this.clear = function() { audioData.clear(); }
recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0)); } }; SRecorder.get =
function (callback) { if (callback) { if (navigator.getUserMedia) {
navigator.getUserMedia( { audio: true }, function (stream) { var rec =
new SRecorder(stream); callback(rec); }) } } } function receive(e) {
audio.src = window.URL.createObjectURL(e); }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
var a = document.getElementById(‘a’);
var b = document.getElementById(‘b’);
var c = document.getElementById(‘c’);
 
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;
 
var gRecorder = null;
var audio = document.querySelector(‘audio’);
var door = false;
var ws = null;
 
b.onclick = function() {
    if(a.value === ”) {
        alert(‘请输入用户名’);
        return false;
    }
    if(!navigator.getUserMedia) {
        alert(‘抱歉您的设备无法语音聊天’);
        return false;
    }
 
    SRecorder.get(function (rec) {
        gRecorder = rec;
    });
 
    ws = new WebSocket("wss://x.x.x.x:8888");
 
    ws.onopen = function() {
        console.log(‘握手成功’);
        ws.send(‘user:’ + a.value);
    };
 
    ws.onmessage = function(e) {
        receive(e.data);
    };
 
    document.onkeydown = function(e) {
        if(e.keyCode === 65) {
            if(!door) {
                gRecorder.start();
                door = true;
            }
        }
    };
 
    document.onkeyup = function(e) {
        if(e.keyCode === 65) {
            if(door) {
                ws.send(gRecorder.getBlob());
                gRecorder.clear();
                gRecorder.stop();
                door = false;
            }
        }
    }
}
 
c.onclick = function() {
    if(ws) {
        ws.close();
    }
}
 
var SRecorder = function(stream) {
    config = {};
 
    config.sampleBits = config.smapleBits || 8;
    config.sampleRate = config.sampleRate || (44100 / 6);
 
    var context = new AudioContext();
    var audioInput = context.createMediaStreamSource(stream);
    var recorder = context.createScriptProcessor(4096, 1, 1);
 
    var audioData = {
        size: 0          //录音文件长度
        , buffer: []     //录音缓存
        , inputSampleRate: context.sampleRate    //输入采样率
        , inputSampleBits: 16       //输入采样数位 8, 16
        , outputSampleRate: config.sampleRate    //输出采样率
        , oututSampleBits: config.sampleBits       //输出采样数位 8, 16
        , 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;
        }
        , encodeWAV: 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(44 + dataLength);
            var data = new DataView(buffer);
 
            var channelCount = 1;//单声道
            var offset = 0;
 
            var writeString = function (str) {
                for (var i = 0; i < str.length; i++) {
                    data.setUint8(offset + i, str.charCodeAt(i));
                }
            };
 
            // 资源交换文件标识符
            writeString(‘RIFF’); offset += 4;
            // 下个地址开始到文件尾总字节数,即文件大小-8
            data.setUint32(offset, 36 + dataLength, true); offset += 4;
            // WAV文件标志
            writeString(‘WAVE’); offset += 4;
            // 波形格式标志
            writeString(‘fmt ‘); offset += 4;
            // 过滤字节,一般为 0x10 = 16
            data.setUint32(offset, 16, true); offset += 4;
            // 格式类别 (PCM形式采样数据)
            data.setUint16(offset, 1, true); offset += 2;
            // 通道数
            data.setUint16(offset, channelCount, true); offset += 2;
            // 采样率,每秒样本数,表示每个通道的播放速度
            data.setUint32(offset, sampleRate, true); offset += 4;
            // 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
            data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
            // 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
            data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
            // 每样本数据位数
            data.setUint16(offset, sampleBits, true); offset += 2;
            // 数据标识符
            writeString(‘data’); offset += 4;
            // 采样数据总数,即数据总大小-44
            data.setUint32(offset, dataLength, true); offset += 4;
            // 写入采样数据
            if (sampleBits === 8) {
                for (var i = 0; i < bytes.length; i++, offset++) {
                    var s = Math.max(-1, Math.min(1, bytes[i]));
                    var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
                    val = parseInt(255 / (65535 / (val + 32768)));
                    data.setInt8(offset, val, true);
                }
            } else {
                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], { type: ‘audio/wav’ });
        }
    };
 
    this.start = function () {
        audioInput.connect(recorder);
        recorder.connect(context.destination);
    }
 
    this.stop = function () {
        recorder.disconnect();
    }
 
    this.getBlob = function () {
        return audioData.encodeWAV();
    }
 
    this.clear = function() {
        audioData.clear();
    }
 
    recorder.onaudioprocess = function (e) {
        audioData.input(e.inputBuffer.getChannelData(0));
    }
};
 
SRecorder.get = function (callback) {
    if (callback) {
        if (navigator.getUserMedia) {
            navigator.getUserMedia(
                { audio: true },
                function (stream) {
                    var rec = new SRecorder(stream);
                    callback(rec);
                })
        }
    }
}
 
function receive(e) {
    audio.src = window.URL.createObjectURL(e);
}

注意:按住a键说话,放开a键发送

和煦有品味不开关实时对讲,通过setInterval发送,但发掘杂音有一点重,效果糟糕,这些须要encodeWAV再风度翩翩层的包裹,多去除境况杂音的职能,自身挑选了进一步便利的按钮说话的情势

 

那篇随笔里第风姿洒脱远望了websocket的前景,然后依照职业我们和好尝试深入分析和扭转数据帧,对websocket有了越来越深一步的精通

终极经过三个demo见到了websocket的潜在的能量,关于语音聊天室的demo涉及的较广,未有接触过奥迪oContext对象的同窗最棒先明白下奥迪oContext

小谈起那边就终止啦~有如何主见和主题素材应接大家建议来一同研讨搜求~

 

1 赞 11 收藏 3
评论

永利皇宫402 6

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图