1.html中引用该js文件
<script language=javascript src='/js/recorder.js'></script>
该文件下载地址:https://download.csdn.net/download/linyibin_123/86052295
2.js核心代码
var TalkRec = null;
var TalkChunck = null;
var TalkChuncks = null;
var TalkWS = null;
//开始对讲
function StartTalk (nSamplerate, url) { //nSamplerate:采样率 url:websocket要推送的url
var bsuc = true;
SpeekStop ();
TalkRec = Recorder({
type:"pcm",
bitRate:16,
onProcess:function(buffers, powerLevel, bufferDuration, bufferSampleRate){
TalkSend(buffers, bufferSampleRate, false);
}
});
TalkRec.open(function(){
TalkChuncks = null; // 重置缓冲区
// init webscoket
TalkWS = new WebSocket(url);
TalkWS.onopen = function () {
console.log("ws handshake success.");
if (TalkWS.readyState == 1) {
TalkRec.start(); // 开始录制数据
}
}
TalkWS.onerror = function (err) {
console.log(err);
}
TalkWS.onclose=function(e){
console.log("ws close success");
};
}, function(e, isUserNotAllow){
bsuc = false;
});
return bsuc;
}
//停止对讲
function StopTalk () {
// close speek record
if(TalkRec) {
TalkRec.close();
TalkRec = null;
}
// close websocket after send.
TalkSend([], 0, true);
}
//数据发送
function TalkSend(buffers, bufferSampleRate, isClose) {
if(TalkChuncks == null){
TalkChunck = null;
console.logTalkChuncks = [];
};
var pcm = [];
var pcmSampleRate = 0;
if(buffers.length > 0) {
//借用SampleData函数进行数据的连续处理,采样率转换是顺带的,得到新的pcm数据
var chunk = Recorder.SampleData(buffers, bufferSampleRate, 8000, TalkChunck);
//清理已处理完的缓冲数据,释放内存以支持长时间录音,最后完成录音时不能调用stop,因为数据已经被清掉了
for(var i = TalkChunck?TalkChunck.index:0; i<chunk.index; i++){
buffers[i] = null;
};
//此时的chunk.data就是原始的音频16位pcm数据(小端LE),直接保存即为16位pcm文件
TalkChunck = chunk;
pcm = chunk.data;
pcmSampleRate = chunk.sampleRate;
if(pcmSampleRate != 8000) {
throw new Error("error pcmSampleRate:"+pcmSampleRate+"!=8000");
}
console.log("pcm.length="+pcm.length);
}
if(pcm && pcm.length > 0){
//将pcm数据丢进缓冲,凑够一帧发送,缓冲内的数据可能有多帧,循环切分发送
TalkChuncks.push({pcm:pcm,pcmSampleRate:pcmSampleRate});
}
//从缓冲中切出一帧数据
var chunkSize = speek_fsize/2;//8位时需要的采样数和帧大小一致,16位时采样数为帧大小的一半
var pcm = new Int16Array(chunkSize), pcmSampleRate=0;
var pcmOK = false, pcmLen=0;
for1:for(var i1=0; i1<TalkChuncks.length; i1++){
var chunk = TalkChuncks[i1];
pcmSampleRate = chunk.pcmSampleRate;
for(var i2=chunk.offset||0;i2<chunk.pcm.length;i2++){
pcm[pcmLen] = chunk.pcm[i2];
pcmLen++;
//满一帧了,清除已消费掉的缓冲
if(pcmLen==chunkSize){
pcmOK = true;
chunk.offset = i2+1;
for(var i3=0; i3<i1; i3++){
TalkChuncks.splice(0, 1);
};
break for1;
}
}
};
//缓冲的数据不够一帧时,不发送 或者 是结束了
if(!pcmOK) {
//结束发送,销毁websocket.
if(isClose && TalkWS) {
// close websocket
TalkWS.close();
TalkWS = null;
}
return;
}
// 发送一帧数据
var blob = new Blob([pcm], {type:"audio/pcm"});
if(TalkWS) { // send to server
TalkWS.send(blob);
}
//循环调用,继续切分缓冲中的数据帧,直到不够一帧
TalkSend([], 0, isClose);
}
3.websocket服务器端部分代码参考
//websocket接收
static int on_ws_talk(void* param, int opcode, const void* data, size_t bytes, int flags)
{
http_websocket_t *ws = (http_websocket_t*)param;
http_session_t *ss = (http_session_t *)ws->session;
// keepalive for websocket. must keepalive by client.
ss->activity = ffss_time_get_sec_bootup() + (CONN_TIMEOUT); // to second.
switch (opcode)
{
case WEBSOCKET_OPCODE_BINARY:
talk_play_audio(data, bytes);
break;
default:
websocket_send(ws, opcode, data, bytes);
break;
}
return 0;
}
//音频数据拷贝和发送到播放端
static void talk_play_audio(const void* data, size_t bytes)
{
//data copy
audio_msg_t msgbuf = {0};
msgbuf.lenth = bytes/2;
memcpy(msgbuf.buf, (short*)data, msgbuf.lenth);
msgbuf.mtype = CGI_AUDIO_MSGTYPE;
//send message to ffavs for play
int msgid = ffstream_audio_get_msgid();
ffss_msg_snd_nowait(msgid, &msgbuf, sizeof(audio_msg_t));
if(ffss_msg_rcv_wait_timeout(msgid, &msgbuf, sizeof(audio_msg_t),
CGI_AUDIO_MSGACK, 500) == -1)
{
printf("Websocket Session Push audio timeout");
}
else
{
printf("Websocket Session Push audio success3 bytes:%d", bytes);
}
}
本文参考
https://xiangyuecn.gitee.io/recorder/assets/%E5%B7%A5%E5%85%B7-%E4%BB%A3%E7%A0%81%E8%BF%90%E8%A1%8C%E5%92%8C%E9%9D%99%E6%80%81%E5%88%86%E5%8F%91Runtime.html?jsname=teach.realtime.encode_transfer_frame_pcm