金沙贵宾会官网|金沙贵宾会登录-官网

【A】金沙贵宾会官网超高的返奖率为娱乐者提供资金保障,所以金沙贵宾会登录官网更加的方便了你的娱乐,申请88元彩金,因为在当中不仅仅只有游戏。

websocket查究其与话音

日期:2019-10-01编辑作者:Web网络

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 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);

那般握手部分就已经实现了,前面正是数据帧深入分析与调换的活了

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

图片 4

简短介绍下

FIN为是不是终止的标识

奥迪Q3SV为留下空间,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奥迪Q3L(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);
};

接过到新闻,然后readAsDataUOdysseyL,直接将图纸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搭建语音聊天室

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

客商步入频道随后从Mike风输入音频,然后发送给后台转载给频道里面包车型客车其旁人,其余人接收到音讯进行播放

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

先说音频的输入,这里运用了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.奥迪oContext来做的,大家全部对旋律的操作都依照这一个目的”,大家能够经过奥迪oContext成立分化的奥迪(Audi)oNode节点,然后加多滤镜播放特别的音响

录音原理同样,我们也亟需走奥迪(Audi)oContext,可是多了一步对Mike风音频输入的收到上,实际不是像未来处理音频一下用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方法绝对的赞,外人举办了累累的节奏压缩和优化,这一个最终会伴随完整的代码一齐贴出来

那时全体客商踏入频道随后从迈克风输入音频环节就早就成功啦,上边就该是向服务器端发送音频流,稍微有一点点蛋疼的来了,刚才大家说了,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的搭建在这里就不提了,首若是索要私钥、CSHaval证书签名和证书文件,感兴趣的同班能够理解下(可是不打听的话在现网景况也用持续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 奥迪oContext(); var audioInput = context.createMediaStreamSource(stream); var recorder = context.createScriptProcessor(4096, 1, 1); var audioData = { size: 0 //录音文件长度 , buffer: [] //录音缓存 , input山姆pleRate: context.sampleRate //输入采集样品率 , inputSampleBits: 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('揽胜IFF'); 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涉及的较广,未有接触过奥迪(Audi)oContext对象的校友最佳先明白下奥迪(Audi)oContext

作品到此处就得了啦~有何主见和主题材料款待我们建议来一同谈谈查究~

 

1 赞 11 收藏 3 评论

图片 6

本文由金沙贵宾会官网发布于Web网络,转载请注明出处:websocket查究其与话音

关键词:

详解移动端HTML5音频与视频问题及解决方案,移动

移动端H5音频与视频问题及解决方案 2015/09/16 · HTML5 · 1评论 ·视频,音频 原文出处:Aaron的博客    最近在研究用视频...

详细>>

纯属干货,网页无图再不是期望

金沙贵宾会官网,网页无图再不是梦想 2015/08/22 · HTML5 · 1评论 ·网页开发 原文出处: 百码山庄    一直以来,网页...

详细>>

你所不了解的javascript操作DOM的细节知识点,Ele

DOM Element节点类型详解 2015/09/21 · HTML5 ·DOM 本文笔者: 伯乐在线 -韩子迟。未经小编许可,禁绝转发! 迎接加入伯乐...

详细>>

剖判中间人攻击之SSL欺诈,有的时候候比

为什么 HTTP 有时候比 HTTPS 好? 2015/05/15 · HTML5 · 3评论 ·HTTP,HTTPS 原文出处:stormpath   译文出处:开源中国社区  ...

详细>>