一、前端实现
H5数据采集
web audio的概念和使用详见;接口文档
采集音频数据
var context = new AudioContext();
var audioInput = context.createMediaStreamSource(stream);
var recorder = context.createScriptProcessor(4096, 1, 1);
var context = new AudioContext();
var audioInput = context.createMediaStreamSource(stream);
var recorder = context.createScriptProcessor(4096, 1, 1);
recorder.onaudioprocess = function (e) {
var channelData = e.inputBuffer.getChannelData(0); // PCM数据
audioData.input(channelData);
ws.send(gRecorder.getBlob()); //发送pcm转wav音频数据
audioData.clear();
}]
音频PCM转WAV
代码参考 https://www.bbsmax.com/A/rV57M9WzPD/ HZRecorder.js 实现
gRecorder.getBlob(); // 返回blod wav文件
WEBSOCKET推流
实例化websocket
ws = new WebSocket("ws://localhost:8084/myHandler");
ws.onopen = function () {
console.log('握手成功');
};
ws.send(gRecorder.getBlob()); // websocket发送音频流
WEBSOCKET接收流
ws.onmessage = function (e) {
// 收到的音频二进制wav数据
var reader = new FileReader();
reader.readAsArrayBuffer(e.data); //转为ArrayBuffer
reader.onload = function () {
// WAV转PCM
context.decodeAudioData(reader.result, function (buffer) {
var float32Array = buffer.getChannelData(0); // PCM数据
audioChuncks.push(float32Array);//保存到播放的音频数组
},
function (e) {
"Error with decoding audio data" + e.err
}
);
}
};
音频WAV转PCM
代码同上
context.decodeAudioData
H5播放音频
this.play = function () {
//创建播放音频对象
let myBuffer = context.createBuffer(1, 4096, context.sampleRate);
let source = context.createBufferSource();
let playRecorder = context.createScriptProcessor(4096, 1, 1);
source.connect(playRecorder);
playRecorder.connect(context.destination);
playRecorder.onaudioprocess = function (ev) {
//播放audioChuncks里面真正的二进制数据;来源websocket.onmessage
if (audioChuncks.length > 0) {
ev.outputBuffer.copyToChannel(audioChuncks.shift() || new Float32Array(4096), 0, 0);
}
};
}
也可以直接播放PCM,使用开源PCM-PLAYER
二、后端实现
springboot集成websocket;继承BinaryWebSocketHandler
接收音频
消息接收保存到文件
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
super.handleBinaryMessage(session, message);
final ByteBuffer byteBuffer = message.getPayload();
byte[] array = byteBuffer.array();
final String fielName = getFileNameBySession(session);
final File file = new File(fielName);
try (FileOutputStream outputStream = new FileOutputStream(file, true)) {
outputStream.write(array);
outputStream.flush();
outputStream.close();
}
}
发送音频
建立连接就实时发送音频数据
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//新建线程实时输出音频
Thread thread = new Thread(new SendFileTask(session));
thread.start();
}
static class SendFileTask implements Runnable {
WebSocketSession session = null;
public SendFileTask(WebSocketSession session) {
this.session = session;
}
@Override
public void run() {
final String name = getFileNameBySession(session);
final File file = new File(name);
int oldFileSize = 0;
while (session.isOpen()) {
if (file.exists()) {
try (FileInputStream fileInputStream = new FileInputStream(file)) {
final int length = 726;
byte[] readByte = new byte[length];
int read = 0;
final long fileSize = file.length();
if (fileSize > oldFileSize) {
fileInputStream.skip(oldFileSize);
int offset = oldFileSize;
while ((read = fileInputStream.read(readByte, 0, length)) > 0) {
if (session.isOpen()) {
offset = offset + read;
oldFileSize = offset;
session.sendMessage(new BinaryMessage(readByte, readByte.length != length));
} else {
break;
}
}
}
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(150L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
参考文档:
- web音频流转发之音视频直播
- web音频流转发之音频源
- web音频流转发之AudioNode
- wav文件格式分析与详解