![e949d7fb724285f5b75530f38e0803f7.png](https://i-blog.csdnimg.cn/blog_migrate/ddde29d60f9c27f4bfba35c7ea177f54.jpeg)
web采集音频以及音频播放在前端直播有重要地位,通常可以做播放声音,连麦,甚至可以定制一个自己的播放器,包括任意倍速,数据自定义等操作。本文介绍通过AudioContext这个Api实现前端采集,发送给socket然后socket发送给另外一个界面,实现播放。
pcm-recorder
本文采集音频都是通过webrtc的getUserMedia实现的,获得一个MediaStream对象,将该对象关联到AudioContext上即可实现采集音频,代码如下:
首先我们建立音频音频分析节点
PcmRecorder.prototype.init = function() {
this.context = new (window.AudioContext || window.webkitAudioContext)();
<!-- 可以用来获取音频时间和频率数据,以及实现数据可视化 -->
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = this.config.fftSize;
<!-- 创建一个ScriptProcessorNode 用于通过JavaScript直接处理音频 -->
const createScript = this.context.createScriptProcessor || this.context.createJavaScriptNode;
this.recorder = createScript.apply(this.context,
[this.config.fftSize, this.config.numberChannels, this.config.numberChannels]);
// 音频采集
this.recorder.onaudioprocess = this.onaudioprocess.bind(this)
}
此时onaudioprocess回调会在采集够this.config.fftSize后回调一次。
将分析节点和MediaStream关联,代码如下:
var _this = this;
navigator.mediaDevices.getUserMedia({
audio: true,
}).then((stream) => {
_this.audioInput = _this.context.createMediaStreamSource(stream);
}, (error) => {
console.error(error);
}).then(() => {
_this.audioInput.connect(_this.analyser);
_this.analyser.connect(_this.recorder);
_this.recorder.connect(_this.context.destination);
});
onaudioprocess实现如下:
PcmRecorder.prototype.onaudioprocess = function(e) {
if (this.config.numberChannels === 1) {
const data = e.inputBuffer.getChannelData(0);
// 单通道
this.handlePcm(data);
}
}
此时我们在this.onaudioprocess方法中即可收到pcm音频数据,采样率根据不同浏览器不同,一般为44100,如果业务代码里播放采样率不是44100还需要resample(接下来文章会讲解),这里采用单声道采集,采集出来的数据是float32类型,如果业务里面需要转换还需要转换成Uint16Array或者Uint8Array,我们这里不转换直接发出去,在下一个界面就可以直接播放了。
采集模块通过http://socket.io发送数据代码如下:
<script src="../socket.io.js/socket.io.js"></script>
<script type="text/javascript">
var socket = io('http://127.0.0.1:8085', {transports: ['websocket']});
socket.binaryType = 'arraybuffer';
var pe = null;
function receivePcmFloat32(data) {
socket.emit('pcm-data', {
pcm: data,
});
}
</script>
socekt发送数据
server收发数据我们采用socket.io,关于http://socket.io使用推荐去 官网学习一下,这里会给出详细注释:
我们通过http模块和http://socekt.io模块构建http服务和socekt服务,当是静态资源时候直接返回该路径对应的资源 fs.readFile(recorderPlayerPath + reqPath,function(){....}),当是socekt服务时候,http://socket.io会触发connection事件,该函数有一个socket回调,io.on('connection', function (socket) { socket.on('pcm-data', function (data) {socket.broadcast.emit('to-player', data.pcm); });});,这里broadcast代表全体广播,也就是只要连接上的socekt都会收到该广播,如果想定向给某个人发,可以通过socket.id来区分每个人,从而实现定制化发送。我们在socket回调即监听前端发送的数据,以及把音频pcm数据转发给另外一个界面。
完整代码如下:
var app = require('http').createServer(handler)
var io = require('socket.io')(app);
var fs = require('fs');
var path = require('path');
app.listen(8085, function() {
console.log('server start success, port:8085');
});
function handler (req, res) {
const reqPath = req.url;
const recorderPlayerPath = path.resolve(__dirname, '../');
fs.readFile(recorderPlayerPath + reqPath,
function (err, data) {
console.log('path:', recorderPlayerPath + reqPath);
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
var socketId = socket.id;
console.log(socketId);
socket.on('pcm-data', function (data) {
// console.log('server receive data:', data);
// io.on('connection',function(socket){});//建立连接
// io.sockets.emit(约定参数,data);//向全体人员广播
// io.emit(约定参数, data);//向全体人员广播
// socket.emit(约定参数,data)//发送信息
// socket.on(约定参数,callback);//接收信息
// socket.on('disconnect',callback);//用户断开连接触发事件
socket.broadcast.emit('to-player', data.pcm);
});
});
pcm-player
http://socket.io收server下发的数据
<script src="../socket.io.js/socket.io.js"></script>
<script>
var socket = io('http://127.0.0.1:8085', {transports: ['websocket']});
socket.binaryType = 'arraybuffer';
socket.on('to-player', function (data) {
const buffer = new Float32Array(data);
console.log('player receive pcm: ', buffer.length);
player.write(buffer);
});
</script
播放模块,我们仍然通过AudioContext,采集时候通过e.inputBuffer.getChannelData(0)采集,播放时候我们通过e.outputBuffer.getChannelData(0).set(buffer)播放。
PcmPlayer.prototype.play = function() {
const _this = this;
this.scriptNode = this.audioContext.createScriptProcessor(this.config.bufferSize, 1, 1);
this.gainNode = this.audioContext.createGain();
this.scriptNode.connect(this.gainNode);
this.gainNode.connect(this.audioContext.destination);
this.scriptNode.onaudioprocess = function(e) {
if (_this.bufferLength()) {
e.outputBuffer.getChannelData(0).set(_this.read(_this.config.bufferSize));
} else {
console.log('silence');
// e.outputBuffer.getChannelData(0).set(_this.silence);
}
}
}
我们维护了一个bufferQue队列,将收到的buffer缓存在其中,然后等待AudioContext的onaudioprocess回调取出播放。
后续
后面我会陆续写出通过Webassmebly来实现倍速,重采样等功能,甚至基于electron高度自定义一个音频播放器。
github
github有完整代码以及demo跑起来的例子
github地址