不使用任何插件,前端原生js从音频数据源上改动尝试音频倒放(未完成)

1,680 阅读6分钟

不使用任何插件,前端原生js从音频数据源上改动尝试音频倒放

为啥是尝试——因为现在我也没搞明白前端使用blob二进制储存音频到底是个什么解析规则,网上实在找不到参考资料,完全是自己一点一点试出来的,下面说我是怎么试的以及试出来的结果。

界面

三个<audio>标签,分别用来录音,正放和倒放,两个<button>分别用来开始录音和结束录音。

lsf的小玩意儿

尝试

首先我将二进制的blob转为了无符号Uint8Array数组,然后:

第一次尝试

我天真的以为把这个数组倒过来,然后再转回Blob,然后就可以播放了!

结果当然是——门都没有!!!

第二次尝试

我发现我把他截取了数组前面的一部分,依然可以播放,但是单独放后面的,不可。

得到结论,这个数组前面有一段固定得长度用来表示这个是个音频文件(还有单声道呀,或者什么什么得,我也不太懂),然后我就对比了两个录音片段,大家公共的长度是多少呢——91(90之前两个音频数组都是是一样的),但是公共长度只是音频头(不循环的部分)的一部分,音频头是截止到160的(长度为161)

问题一:至于91到97这一段为啥不一样?

答:我也不知道

问题二:那164之后也相同,为什么你认定这个就不是音频头了呢?

答:从161位开始的【163,65,X,129】,我发现这个东西在后面重复出现了,而且出现下一个这个的间隔等于X+259,那么我就大胆推测——这个就是代表声音的一个最小单位的开始。

lsf的小玩应
lsf的小玩应

注:

我们倒置的最小单位是以【163,65,X,129.......】开头的数组段(其中X是这段数组的长度),10个数组段播放时间是0.48s,但是16个数组段播放时间是从0.78s到0.9s不等,所以说并不是一个代码块记录了固定多少毫秒的时间(这不是我定义的,用官方方法把blob音频转为数组然后他就这样,我也没什么办法)

第三次尝试

然后我就开始,在保留代码头的情况下,把从一个【163,65,X,129】到另一个【163,65,X,129】之间的代码作为最小单位S,然后在保持最小单位S内的代码顺序不变,将所有最小单位的排序顺序倒置了(从S_1,S_2,S_3,.....,S_n变成S_n,S_n-1,.....,S_1)

然后结果当然是——还是不行。

然后我将每个都倒放变成了把5个S当作一个整体,再倒放,成功了!我自己读的123456变成了654321!但是那个音频条不能拖动,一直是在最后一秒。如下图这样播完4s。

第四次尝试

这我能忍么!肯定不能,音频条不能拖动是个什么玩应儿!然后我把数组的前一半倒置了,后一半不倒置,结果点击播放那一刻,6秒的音频是从3s开始的,持续了3s(一直显示0:03),音频正常播放到了6s(因为后三秒没倒置),那么我觉得,应该是有个东西记录了时间。那么我们往下看,发现了一些规律(输出的分别是,X,【163,65,X,129】中65的位置,后面接着的四位),直觉告诉我,这个递增的两位,就是记录了时间!

lsf的小玩意

lsf的小玩应

然后我就循环将每一段的这两位更改为递增,播放时便是从零秒开始的,逐渐增加直到最后一秒,现在的代码,有的可以倒放,有的不行(8s的原音频倒放后变成了4s或者5s反正就是变短了),原因还未知,所以写着未完成。。。

代码

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Document</title>
 <script>
   let recorder
   function start() {
     let videoTarget = document.getElementById('audio')
     navigator.mediaDevices.getUserMedia({audio: true, video: false})
       .then((stream) => {
         recorder = new MediaRecorder(stream)
         videoTarget.srcObject = stream
         //s用来标记这是否找到了代码头
         let s = 0
         recorder.ondataavailable = (event) => {
           let blob = event.data
           let videoTarget2 = document.getElementById('audio2')
           let videoTarget3 = document.getElementById('audio3')
           let a = blob.stream().getReader()
           a.read().then(({ done, value }) => {
             let newValue = [] //用来存放倒置后的新数组
             let r = 0 //r代表指针记录新数组插入元素的位置
             let b = 0 //b用来记录这是第几个S(S是我们上面提到的最小音频单位)
             for(i = 0; i < value.length; i++) {
               if(value[i] === 163){
               //s=1表示进入戒备状态
                 s = 1
               } else if(s === 1 && value[i] === 65) {
               //在戒备状态的条件下value[i] === 65说明的确是开始了新的一段最小单位的音频
                 b === 5 ? (r = 162,b = 0) : b++
                 //这里b===5是可以改的,我定的是5个S作为一个整体倒置,因为每个S持续时间太短了,一个S一倒置放出来的音频效果很差
                 //r=162是将指针移到162也就是出去音频头最早开始循环的位置,这样我们就完成了倒置,打完5个S就把指针移回最开始继续打
               } else {
               //value[i] !== 65说明只是凑巧,并不是找到了代码头,我们再将s标示置为0
                 s = 0
               }
               //在指针位置插入元素
               newValue.splice(r, 0, value[i])
               //指针后移
               r++
             }
             b = 0
             //我们现在得到的数组就是第三次尝试时的到的数组,播放时一直显示最后一秒
             newValue.splice(r, 0, 163)
             //删掉最后一个元素是因为上面指针后移操作会使数组最后多一个163(戒备状态那个判断用到的163)
             newValue.pop()
             //然后我们就要将音频最小单位S的代码头后面两位变成0 0, 0 60,0 120....
             for(i = 0; i < newValue.length; i++) {
               if(newValue[i] == 163){
                 s = 1
               } else if(s == 1 && newValue[i] == 65) {
               //得到的规律是每次后面加60,但是如果超过了256就要进位,前面加1
                 newValue[i+3] = parseInt(b * 60 / 256)
                 newValue[i+4] = b * 60 % 256
                 b++
                 console.log(i, newValue[i+3], newValue[i+4])
               } else {
                 s = 0
               }
             }
             console.log(newValue)
             console.log(value)
             //然后再将新数组转成blob赋给audio就可播放了
             newValue = new Uint8Array(newValue)
             let blob2 = new Blob([newValue], {type: 'audio/webm;codecs=opus'})
             videoTarget2.src = window.URL.createObjectURL(blob2)
             let blob3 = new Blob([value], {type: 'audio/webm;codecs=opus'})
             videoTarget3.src = window.URL.createObjectURL(blob3)
           })
         }
         recorder.start()
       });
   }

   function stop() {
     document.getElementById('audio').pause()
     recorder.stop()
   }
 </script>
</head>

<body>
 <audio id="audio" controls autoplay></audio>录音(不用点它去点开始和结束)
 <audio id="audio2" controls autoplay></audio>倒放
 <audio id="audio3" controls></audio>正放
 <input onclick="start()" type="button" value="开始" />
 <input onclick="stop()" type="button" value="结束" />
</body>

</html>