www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

Web前端图表的力量

2019-11-29 09:06 来源:未知

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

Web前端 1

Web前端 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");

Web前端 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卡塔尔(英语:State of Qatar).digest('base64'卡塔尔(英语:State of Qatar); // 输出重临给顾客端的多寡,那几个字段都以必需的 o.write('HTTP/1.1 101 Switching Protocolsrn'); o.write('Upgrade: websocketrn'); o.write('Connection: Upgradern'卡塔尔(قطر‎; // 那个字段带上服务器管理后的KEY o.write('Sec-WebSocket-Accept: ' key 'rn'卡塔尔国; // 输出空行,使HTTP头结束 o.write('rn'); } }); }).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 Protocolsrn');
o.write('Upgrade: websocketrn');
o.write('Connection: Upgradern');
// 这个字段带上服务器处理后的KEY
o.write('Sec-WebSocket-Accept: ' key 'rn');
// 输出空行,使HTTP头结束
o.write('rn');
}
});
}).listen(8888);

如此这般握手部分就已经到位了,前面正是多少帧深入分析与调换的活了

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

Web前端 4

大致介绍下

FIN为是或不是得了的标示

ENCORESV为留住空间,0

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

提交一张opcode对应图

Web前端 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("握手成功"卡塔尔(英语:State of Qatar); }; ws.onmessage = function(e卡塔尔(英语:State of Qatar) { var reader = new FileReader(卡塔尔(英语:State of Qatar); reader.onload = function(event卡塔尔 { var contents = event.target.result; var a = new Image(卡塔尔国; a.src = contents; document.body.appendChild(a卡塔尔国; } reader.readAsDataU普拉多L(e.data卡塔尔(英语:State of Qatar); };

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凯雷德L,直接将图片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);
    ……
}

奥迪(Audi卡塔尔(قطر‎oContext是叁个节奏上下文对象,有做过声音过滤管理的同学应该精晓“生机勃勃段音频到达扬声器进行广播从前,半路对其進展阻挠,于是大家就赢得了节奏数据了,这几个拦截职业是由window.奥迪(Audi卡塔尔oContext来做的,大家富有对旋律的操作都依照那一个指标”,大家能够经过奥迪(Audi卡塔尔国oContext成立差异的奥迪(Audi卡塔尔国oNode节点,然后增添滤镜播放极其的动静

录音原理雷同,我们也亟需走奥迪(Audi卡塔尔(قطر‎oContext,但是多了一步对迈克风音频输入的接收上,并不是像以后管理音频一下用ajax诉求音频的ArrayBuffer对象再decode,迈克风的选取必要用到createMediaStreamSource方法,注意那几个参数就是getUserMedia方法第3个参数的参数

并且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'卡塔尔(英语:State of Qatar); 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卡塔尔(英语:State of Qatar) { alert('抱歉您的设施无德文音聊天'卡塔尔; return false; } SRecorder.get(function (rec卡塔尔(英语:State of Qatar) { 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卡塔尔(英语:State of Qatar) { receive(e.data卡塔尔; }; document.onkeydown = function(e卡塔尔国 { if(e.keyCode === 65卡塔尔 { if(!door卡塔尔 { gRecorder.start(卡塔尔(英语:State of Qatar); 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卡塔尔(英语:State of Qatar); var context = new 奥迪(Audi卡塔尔国oContext(卡塔尔(英语:State of Qatar); 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 //输出采样率 , oututSampleBits: config.sampleBits //输出采集样本数位 8, 16 , clear: function(卡塔尔(قطر‎ { this.buffer = []; this.size = 0; } , input: function (data卡塔尔(英语:State of Qatar) { this.buffer.push(new Float32Array(data卡塔尔(英语:State of Qatar)卡塔尔国; this.size = data.length; } , compress: function (卡塔尔(英语:State of Qatar){ //合并压缩 //归并 var data = new Float32Array(this.size卡塔尔(英语:State of Qatar); 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 卡塔尔(英语:State of Qatar) { data.setUint8(offset i, str.charCodeAt(i卡塔尔国卡塔尔(قطر‎; } }; // 能源调换文件标志符 writeString('悍马H2IFF'卡塔尔国; offset = 4; // 下个地方开头到文件尾总字节数,即文件大小-8 data.setUint32(offset, 36 dataLength, true卡塔尔(英语:State of Qatar); 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卡塔尔(英语:State of Qatar); offset = 2; // 通道数 data.setUint16(offset, channelCount, true卡塔尔(英语:State of Qatar); offset = 2; // 采集样板率,每秒样品数,表示每一种通道的播音速度 data.setUint32(offset, sampleRate, true卡塔尔(英语:State of Qatar); 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'卡塔尔(英语:State of Qatar); offset = 4; // 采集样板数据总量,即数据总大小-44 data.setUint32(offset, dataLength, true卡塔尔国; offset = 4; // 写入采集样板数据 if (sampleBits === 8卡塔尔(英语:State of Qatar) { 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对象的同室最棒先了然下奥迪(Audi卡塔尔oContext

小说到此处就结束啦~有哪些主张和主题材料应接大家提议来一同谈谈索求~

 

1 赞 11 收藏 3 评论

Web前端 6

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于Web前端,转载请注明出处:Web前端图表的力量