Skip to content

Commit

Permalink
wav相关demo代码升级,提供超过44字节的wav头支持
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangyuecn committed Oct 15, 2022
1 parent 131c1eb commit 73fcf4b
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 38 deletions.
65 changes: 47 additions & 18 deletions assets/runtime-codes/lib.merge.wav_merge.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,43 @@
文档:
Recorder.WavMerge(fileBytesList,True,False)
fileBytesList:[Uint8Array,...] 所有wav文件列表,每项为一个文件Uint8Array二进制数组;仅支持raw pcm、单声道、8|16位格式wav,并且列表内的所有wav的位数和采样率必须一致
fileBytesList:[Uint8Array,...] 所有wav文件列表,每项为一个文件Uint8Array二进制数组;仅支持raw pcm、单|双声道、8|16位格式wav,并且列表内的所有wav的位数和采样率和声道数必须一致
True: fn(fileBytes,duration,info) 合并成功回调
fileBytes:Uint8Array 为wav二进制文件
duration:合并后的时长
info:{
sampleRate:123 //采样率
,bitRate:8 16 //位数
,numChannels:1 2 //声道数
}
False: fn(errMsg) 出错回调
此函数可移植到后端使用
测试Tips:可先运行实时转码的demo代码,然后再运行本合并代码,免得人工不好控制片段大小
wav片段文件合并原理:本库wav格式音频是用44字节wav头+PCM数据来构成的,因此只需要将所有片段去掉44字节后,通过简单的二进制拼接就能得到完整的长pcm数据,最后在加上44字节wav头就能得到完整的wav音频文件
wav片段文件合并原理:raw编码的wav格式是直接在pcm数据前面加了一个wav头,本库编码的wav头是固定的44字节,其他编码器生成的wav头可能存在其他的数据块超过44字节。因此只需要将所有片段去掉wav头后,通过简单的二进制拼接就能得到完整的长pcm数据,最后在加上一个简单的44字节wav头就能得到完整的wav音频文件
******************/

//=====wav文件合并核心函数==========
Recorder.WavMerge=function(fileBytesList,True,False){
var wavHead=new Uint8Array(fileBytesList[0].buffer.slice(0,44));

//计算所有文件的长度、校验wav头
var size=0,baseInfo;
var size=0,baseInfo,wavHead44,dataIdxs=[];
for(var i=0;i<fileBytesList.length;i++){
var file=fileBytesList[i];
var info=readWavInfo(file);
if(!info){
False&&False("第"+(i+1)+"个文件不是单声道wav raw pcm格式音频,无法合并");
False&&False("第"+(i+1)+"个文件不是单或双声道wav raw pcm格式音频,无法合并");
return;
};
dataIdxs.push(info.dataPos);
wavHead44||(wavHead44=info.wavHead44);
baseInfo||(baseInfo=info);
if(baseInfo.sampleRate!=info.sampleRate || baseInfo.bitRate!=info.bitRate){
False&&False("第"+(i+1)+"个文件位数或采样率不一致");
if(baseInfo.sampleRate!=info.sampleRate || baseInfo.bitRate!=info.bitRate || baseInfo.numChannels!=info.numChannels){
False&&False("第"+(i+1)+"个文件位数或采样率或声道数不一致");
return;
};

size+=file.byteLength-44;
size+=file.byteLength-info.dataPos;
};
if(size>50*1024*1024){
False&&False("文件大小超过限制");
Expand All @@ -51,15 +52,15 @@ Recorder.WavMerge=function(fileBytesList,True,False){
var fileBytes=new Uint8Array(44+size);
var pos=44;
for(var i=0;i<fileBytesList.length;i++){
var pcm=new Uint8Array(fileBytesList[i].buffer.slice(44));
var pcm=new Uint8Array(fileBytesList[i].buffer.slice(dataIdxs[i]));
fileBytes.set(pcm,pos);
pos+=pcm.byteLength;
};

//添加新的wav头,直接修改第一个的头就ok了
write32(wavHead,4,36+size);
write32(wavHead,40,size);
fileBytes.set(wavHead,0);
write32(wavHead44,4,36+size);
write32(wavHead44,40,size);
fileBytes.set(wavHead44,0);

//计算合并后的总时长
var duration=Math.round(size/info.sampleRate*1000/(info.bitRate==16?2:1));
Expand All @@ -73,7 +74,7 @@ var write32=function(bytes,pos,int32){
bytes[pos+3]=(int32>>24)&0xff;
};
var readWavInfo=function(bytes){
//检测wav文件头
//读取wav文件头,统一成44字节的头
if(bytes.byteLength<44){
return null;
};
Expand All @@ -87,12 +88,39 @@ var readWavInfo=function(bytes){
return true;
};
if(eq(0,"RIFF")&&eq(8,"WAVEfmt ")){
if(wavView[20]==1 && wavView[22]==1){//raw pcm 单声道
var numCh=wavView[22];
if(wavView[20]==1 && (numCh==1||numCh==2)){//raw pcm 单或双声道
var sampleRate=wavView[24]+(wavView[25]<<8)+(wavView[26]<<16)+(wavView[27]<<24);
var bitRate=wavView[34]+(wavView[35]<<8);
return {
sampleRate:sampleRate
,bitRate:bitRate
var heads=[wavView.subarray(0,12)],headSize=12;//head只保留必要的块
//搜索data块的位置
var dataPos=0; // 44 或有更多块
for(var i=12,iL=wavView.length-8;i<iL;){
if(wavView[i]==100&&wavView[i+1]==97&&wavView[i+2]==116&&wavView[i+3]==97){//eq(i,"data")
heads.push(wavView.subarray(i,i+8));
headSize+=8;
dataPos=i+8;break;
}
var i0=i;
i+=4;
i+=4+wavView[i]+(wavView[i+1]<<8)+(wavView[i+2]<<16)+(wavView[i+3]<<24);
if(i0==12){//fmt
heads.push(wavView.subarray(i0,i));
headSize+=i-i0;
}
}
if(dataPos){
var wavHead=new Uint8Array(headSize);
for(var i=0,n=0;i<heads.length;i++){
wavHead.set(heads[i],n);n+=heads[i].length;
}
return {
sampleRate:sampleRate
,bitRate:bitRate
,numChannels:numCh
,wavHead44:wavHead
,dataPos:dataPos
};
};
};
};
Expand All @@ -117,6 +145,7 @@ var test=function(){
};
Recorder.WavMerge(files,function(file,duration,info){
Runtime.Log("合并"+files.length+"个成功"+(exclude?",排除"+exclude+"个非wav文件":""),2);
console.log(info);
info.type="wav";
Runtime.LogAudio(new Blob([file.buffer],{type:"audio/wav"}),duration,{set:info});
},function(msg){
Expand Down
45 changes: 33 additions & 12 deletions assets/runtime-codes/lib.transform.wav2other.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
文档:
Recorder.Wav2Other(newSet,wavBlob,True,False)
newSet:Recorder的set参数,用来生成新格式,注意:要先加载好新格式的编码引擎
wavBlob:wav二进制数据
wavBlob:wav二进制数据,仅支持raw编码、支持单声道和双声道音频
True: fn(blob,duration,mockRec) 和Recorder的stop函数参数一致,mockRec为转码时用到的Recorder对象引用
False: fn(errMsg) 和Recorder的stop函数参数一致
原理:raw编码的wav格式是直接在pcm数据前面加了一个wav头,本库编码的wav头是固定的44字节,其他编码器生成的wav头可能存在其他的数据块超过44字节。因此只需要寻找到wav头的大小,去掉此wav头即可得到pcm格式数据,然后再将pcm通过Recorder的mock方法转换成其他格式。
******************/

//=====wav转其他格式核心函数==========
Expand All @@ -27,24 +29,43 @@ Recorder.Wav2Other=function(newSet,wavBlob,True,False){
};
var pcm;
if(eq(0,"RIFF")&&eq(8,"WAVEfmt ")){
if(wavView[20]==1 && wavView[22]==1){//raw pcm 单声道
var numCh=wavView[22];
if(wavView[20]==1 && (numCh==1||numCh==2)){//raw pcm 单或双声道
var sampleRate=wavView[24]+(wavView[25]<<8)+(wavView[26]<<16)+(wavView[27]<<24);
var bitRate=wavView[34]+(wavView[35]<<8);
console.log("wav info",sampleRate,bitRate);
if(bitRate==16){
pcm=new Int16Array(wavView.buffer.slice(44));
}else if(bitRate==8){
pcm=new Int16Array(wavView.length-44);
//8位转成16位
for(var j=44,d=0;j<wavView.length;j++,d++){
var b=wavView[j];
pcm[d]=(b-128)<<8;
//搜索data块的位置
var dataPos=0; // 44 或有更多块
for(var i=12,iL=wavView.length-8;i<iL;){
if(wavView[i]==100&&wavView[i+1]==97&&wavView[i+2]==116&&wavView[i+3]==97){//eq(i,"data")
dataPos=i+8;break;
}
i+=4;
i+=4+wavView[i]+(wavView[i+1]<<8)+(wavView[i+2]<<16)+(wavView[i+3]<<24);
}
console.log("wav info",sampleRate,bitRate,numCh,dataPos);
if(dataPos){
if(bitRate==16){
pcm=new Int16Array(wavView.buffer.slice(dataPos));
}else if(bitRate==8){
pcm=new Int16Array(wavView.length-dataPos);
//8位转成16位
for(var j=dataPos,d=0;j<wavView.length;j++,d++){
var b=wavView[j];
pcm[d]=(b-128)<<8;
};
};
};
if(pcm && numCh==2){//双声道简单转单声道
var pcm1=new Int16Array(pcm.length/2);
for(var i=0;i<pcm1.length;i++){
pcm1[i]=(pcm[i*2]+pcm[i*2+1])/2;
}
pcm=pcm1;
};
};
};
if(!pcm){
False&&False("非单声道wav raw pcm格式音频,无法转码");
False&&False("非单或双声道wav raw pcm格式音频,无法转码");
return;
};

Expand Down
23 changes: 17 additions & 6 deletions assets/工具-裸PCM转WAV播放测试.html
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,22 @@
if(wavView[20]==1 && (numCh==1||numCh==2)){//raw pcm
var sampleRate=wavView[24]+(wavView[25]<<8)+(wavView[26]<<16)+(wavView[27]<<24);
var bitRate=wavView[34]+(wavView[35]<<8);
console.log("wav info",sampleRate,bitRate,numCh);
//搜索data块的位置
var dataPos=0; // 44 或有更多块
for(var i=12,iL=wavView.length-8;i<iL;){
if(wavView[i]==100&&wavView[i+1]==97&&wavView[i+2]==116&&wavView[i+3]==97){//eq(i,"data")
dataPos=i+8;break;
}
i+=4;
i+=4+wavView[i]+(wavView[i+1]<<8)+(wavView[i+2]<<16)+(wavView[i+3]<<24);
}
console.log("wav info",sampleRate,bitRate,numCh,dataPos);

return {
u8arr:new Uint8Array(wavView.buffer.slice(44))
,info:{bitRate:bitRate,sampleRate:sampleRate,numChannels:numCh}
if(dataPos){
return {
u8arr:new Uint8Array(wavView.buffer.slice(dataPos))
,info:{bitRate:bitRate,sampleRate:sampleRate,numChannels:numCh}
};
};
};
};
Expand Down Expand Up @@ -445,9 +456,9 @@
/* sample rate */
write32(sampleRate);
/* byte rate (sample rate * block align) */
write32(sampleRate*(bitRateNew/8));
write32(sampleRate*(numChannelsNew*bitRateNew/8));
/* block align (channel count * bytes per sample) */
write16(bitRateNew/8);
write16(numChannelsNew*bitRateNew/8);
/* bits per sample */
write16(bitRateNew);
/* data chunk identifier */
Expand Down
4 changes: 2 additions & 2 deletions src/engine/wav.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ Recorder.prototype.wav=function(res,True,False){
/* sample rate */
write32(sampleRate);
/* byte rate (sample rate * block align) */
write32(sampleRate*(bitRate/8));
write32(sampleRate*(bitRate/8));// *1 声道
/* block align (channel count * bytes per sample) */
write16(bitRate/8);
write16(bitRate/8);// *1 声道
/* bits per sample */
write16(bitRate);
/* data chunk identifier */
Expand Down

0 comments on commit 73fcf4b

Please sign in to comment.