首页vns威尼斯城官网登入 › 它实现了浏览器与服务器全双工通信,发送的数据长度分别是 8bit和

它实现了浏览器与服务器全双工通信,发送的数据长度分别是 8bit和

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中的源码图

图片 1

图片 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");

图片 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(!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);

与上述同类握手部分就早就做到了,前边便是多少帧解析与变化的活了

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

图片 4

简短介绍下

FIN为是或不是终止的标识

QX56SV为留住空间,0

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

交给一张opcode对应图

图片 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 FileReader(); reader.onload = function(event) { var
contents = event.target.result; var a = new Image(); a.src = contents;
document.body.appendChild(a); } reader.readAsDataU福特ExplorerL(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);
};

接受到新闻,然后readAsDataU中华VL,直接将图纸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对象,后续的操作基本上都在此个指标上实行。那时候风姿洒脱旦代码运营在本土的话浏览器应该提示您是否启用迈克风输入,鲜明现在就运行了

接下去大家看下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.奥迪oContext来做的,大家具有对旋律的操作都基于那一个指标”,大家得以由此奥迪oContext创建差异的奥迪oNode节点,然后加多滤镜播放极其的声响

录音原理同样,大家也供给走奥迪(Audi卡塔 尔(英语:State of Qatar)oContext,可是多了一步对迈克风音频输入的收到上,实际不是像往常管理音频一下用ajax乞求音频的ArrayBuffer对象再decode,迈克风的承担必要用到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方法异常的赞,别人举行了累累的韵律压缩和优化,这一个最后会陪伴完整的代码一同贴出来

那时整个客商步向频道随后从Mike风输入音频环节就早就成功啦,下边就该是向服务器端发送音频流,微微有一些蛋疼的来了,刚才大家说了,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的搭建在这里地就不提了,首固然急需私钥、CS奥德赛证书签字和证件文件,感兴趣的同室能够领悟下(不过不打听的话在现网境况也用持续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('抱歉您的设备无土耳其共和国(Türkiye Cumhuriyeti卡塔 尔(英语:State of Qatar)语音聊天'); 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 奥迪oContext(); var audioInput =
context.createMediaStreamSource(stream); var recorder =
context.createScriptProcessor(4096, 1, 1); var audioData = { size: 0
//录音文件长度 , buffer: [] //录音缓存 , input萨姆pleRate:
context.sampleRate //输入采集样本率 , input萨姆pleBits: 16 //输入采集样板数位 8,
16 , outputSampleRate: 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('中华VIFF'); 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涉及的较广,未有接触过AudioContext对象的同室最佳先领会下奥迪oContext

文章到此地就终止啦~有哪些主张和难题应接大家建议来一同评论探寻~

 

1 赞 11 收藏 3
评论

图片 6

服务器

ubuntu下python2.76

 

windows python 2.79, chrome37 firefox35通过

 

  1. 数码长度在128-65525以内时, Payload Length位设为126,
    前面额外使用16bit意味长度(前面包车型客车126不再是长度的豆蔻年华有个别)
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>WebSocket</title>  
  5.   
  6.     <style>  
  7.         html, body {  
  8.             font: normal 0.9em arial, helvetica;  
  9.         }  
  10.   
  11.         #log {  
  12.             width: 440px;  
  13.             height: 200px;  
  14.             border: 1px solid #7F9DB9;  
  15.             overflow: auto;  
  16.         }  
  17.   
  18.         #msg {  
  19.             width: 330px;  
  20.         }  
  21.     </style>  
  22.   
  23.     <script>  
  24.         var socket;  
  25.   
  26.         function init() {  
  27.             var host = "ws://127.0.0.1:12345/";  
  28.             try {  
  29.                 socket = new WebSocket(host);  
  30.                 socket.onopen = function (msg) {  
  31.                     log('Connected');  
  32.                 };  
  33.                 socket.onmessage = function (msg) {  
  34.                     log(msg.data);  
  35.                 };  
  36.                 socket.onclose = function (msg) {  
  37.                     log("Lose Connection!");  
  38.                 };  
  39.             }  
  40.             catch (ex) {  
  41.                 log(ex);  
  42.             }  
  43.             $("msg").focus();  
  44.         }  
  45. 它实现了浏览器与服务器全双工通信,发送的数据长度分别是 8bit和。  
  46.         function send() {  
  47.             var txt, msg;  
  48.             txt = $("msg");  
  49.             msg = txt.value;  
  50.             if (!msg) {  
  51.                 alert("Message can not be empty");  
  52.                 return;  
  53.             }  
  54.             txt.value = "";  
  55.             txt.focus();  
  56.             try {  
  57.                 socket.send(msg);  
  58.             } catch (ex) {  
  59.                 log(ex);  
  60.             }  
  61.         }  
  62.   
  63.         window.onbeforeunload = function () {  
  64.             try {  
  65.                 socket.send('quit');  
  66.                 socket.close();  
  67.                 socket = null;  
  68.             }  
  69.             catch (ex) {  
  70.                 log(ex);  
  71.             }  
  72.         };  
  73.   
  74.   
  75.         function $(id) {  
  76.             return document.getElementById(id);  
  77.         }  
  78.         function log(msg) {  
  79.             $("log").innerHTML += "<br>" + msg;  
  80.         }  
  81.         function onkey(event) {  
  82.             if (event.keyCode == 13) {  
  83.                 send();  
  84.             }  
  85.         }  
  86.     </script>  
  87.   
  88. </head>  
  89.   
  90.   
  91. <body onload="init()">  
  92. <h3>WebSocket</h3>  
  93. <br><br>  
  94.   
  95. <div id="log"></div>  
  96. <input id="msg" type="textbox" onkeypress="onkey(event)"/>  
  97. <button onclick="send()">发送</button>  
  98. </body>  
  99.   
  100. </html>  

发送和选取是平等的, 举个例子

 

 

客户端 

1.尺寸小于125时(由于选择126, 127用作标识位.)

 

[html] view
plaincopy图片 7图片 8

  1. Fin (bit 0): determines if this is the last frame in the message.
    This would be set to 1 on the end of a series of frames, or in a
    single-frame message, it would be set to 1 as it is both the first
    and last frame.
  2. RSV1, RSV2, RSV3 (bits 1-3): these three bits are reserved for
    websocket extensions, and should be 0 unless a specific extension
    requires the use of any of these bytes.
  3. Opcode (bits 4-7): these four bits deterimine the type of the frame.
    Control frames communicate WebSocket state, while non-control frames
    communicate data. The various types of codes include:

    1. x0: continuation frame; this frame contains data that should be
      appended to the previous frame
    2. x1: text frame; this frame (and any following) contains text
    3. x2: binary frame; this frame (and any following) contains binary
      data
    4. x3 - x7: non-control reserved frames; these are reserved for
      possible websocket extensions
    5. x8: close frame; this frame should end the connection
    6. x9: ping frame
    7. xA: pong frame
    8. xB - xF: control reserved frames
  4. Mask (bit 8): this bit determines whether this specific frame uses a
    mask or not.

  5. Payload Length (bits 9-15, or 16-31, or 16-79): these seven bytes
    determine the payload length. If the length is 126, the length is
    actually determined by bits 16 through 31 (that is, the following
    two bytes). If the length is 127, the length is actually determined
    by bits 16 through 79 (that is, the following eight bytes).
  6. Masking Key (the following four bytes): this represents the mask, if
    the Mask bit is set to 1.
  7. Payload Data (the following data): finally, the data. The payload
    data may be sent over multiple frames; we know the size of the
    entire message by the payload length that was sent, and can append
    data together to form a single message until we receive the message
    with the Fin flag. Each consecutive payload, if it exists, will
    contain the 0 “continuation frame” opcode.

转载本站文章请注明出处:vns威尼斯城官网登入 http://www.tiec-ccpittj.com/?p=3618

上一篇:

下一篇:

相关文章