微信小程序实现实时录音音频强度输出

背景

老板要做录音体验优化,有一条实现和app中一样的音频强度波纹曲线输出。因为之前在web端实现过,所以我想应该不难吧,无非就是把曲线代码用微信小程序重写一遍?于是赶紧搞参考之前的曲线生成代码搞了一份,上体验,完美。我以为就这么简单就去搞别的优化了,结果临近deadline再回来搞他,好家伙,噩梦来了------这个强度值搞不到。

爬坑

  1. 微信小程序提供了录音 Api ------ wx.getRecorderManager()。正常是没有实时信息输出的,前几年微信提供了onFrameRecorded,如果设置了format为mp3或pcm,就可以设置frameSize,然后回调就会输出相应frameSize大小的帧文件ArrayBuffer。这也使得很多功能得以实现,比如驰声先声的实时打分、科大讯飞的实时语音转文字等。

  2. 正常思路是我们拿到了这个ArrayBuffer,然后处理这个文件就行,可以这个ArrayBuffer就是字节流,根本不能直接看出什么门道,于是开始百度,也知道了如果我们录的是mp3,就需要将mp3先转换为pcm,然后在通过一定的解析算法对pcm进行解析,然后在通过一定算法计算出音频强度,至于为啥一定要mp3,是因为之前的业务都必须要mp3格式的音频才能走下去。

  3. 那如何解析mp3呢,网上说要用到一个库js-mp3,于是搞下来,一调用发现没任何反应,网上说要使用指定的采样率,指定的编码码率,指定的frameSize,但是一一尝试并没有任何反应。也许之前可以,但是可能微信做了调整,现在不可以。

  4. 然后有同事建议说使用ffmpeg,这更离谱了,了解ffmpeg的都知道,这个要求的成本更高。

  5. 最后,很多测试分贝的微信小程序都使用的是服务端计算的方案,基于websocket,但是这个成本也比较高,而且性能也折扣很大,小程序那边 10 个测试分贝的,也就 1-2 个能够真实测试,大部分都是假的效果,或者没反应。本来就是做用户体验优化,所以这个也pass了。

念念不忘,又有一村

由于时间的原因,就先做了一版假的动效上线,但是老板说这效果必须搞出来。于是又开始研究之前web端那边的库源码,web端那边的音频强度是基于AudioContext进行读取的,这边放一下两段代码:

1、获取强度算法

javascript 复制代码
r.prototype.getFrequencyData = function a(e) {

    var t = this.__analyser

    var n = null

    var r = 0

    var s = 0

    var i = 0

    var o = function o() {

        try {

            n = new Uint8Array(t.frequencyBinCount)

            t.getByteFrequencyData(n)

        } catch (a) {

            console.error(a)

        }

        r = Math.max.apply(null, n)

        s = Math.min.apply(null, n)

        i = (r - s) / 128

        i = Math.round(i * 100 / 2)

        i = i > 100 ? 100 : i

        if (typeof e === 'function') {

            e({

                soundIntensity: i

            })

        }

        setTimeout(o, 167)

    }

    o()

}

// 创建 __analyser

javascript 复制代码
var e = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.msAudioContext

this.context = new e()

var n = 0

var t = D.buff_size()

var s = this.context.createMediaStreamSource(a)

this.inputPoint = this.context.createGain()

this.audioInput = s

this.audioInput.connect(this.inputPoint)

this.analyserNode = this.context.createAnalyser()

this.analyserNode.fftSize = 2048

this.inputPoint.connect(this.analyserNode)

于是就搜索了createAnalyser相关的内容,忽然发现微信小程序文档也有这个方法,原来微信小程序提供了一个兼容web端AudioContext的音频播放器WebAudioContext,也给出了两段代码:

  1. 播放示例:
javascript 复制代码
const audioCtx = wx.createWebAudioContext()

const loadAudio = (url) => {

    return new Promise((resolve) => {

        wx.request({

            url,

            responseType: 'arraybuffer',

            success: res => {

                console.log('res.data', res.data)

                audioCtx.decodeAudioData(res.data, buffer => {

                    resolve(buffer)

                },
                err => {

                    console.error('decodeAudioData fail', err)

                    reject()

                })

            },

            fail: res => {

                console.error('request fail', res)

                reject()

            }

        })

    })

}

const play = () => {

    loadAudio('xxx-test.mp3').then(buffer => {

        let source = audioCtx.createBufferSource()

        source.buffer = buffer

        source.connect(audioCtx.destination)

        source.start()

    }).catch(() => {

        console.log('fail')

    })

}

play()
  1. 获取音频强度示例:
javascript 复制代码
const audioCtx = wx.createWebAudioContext();

const analyser = audioCtx.createAnalyser();

analyser.fftSize = 2048;

const bufferLength = analyser.fftSize;

const dataArray = new Uint8Array(bufferLength);

analyser.getByteTimeDomainData(dataArray);

其中第一段示例可以看出这个音频播放器是可以直接播放mp3的ArrayBuffer的。第二段可以发现就和web端的那个示例很像了。当时只是感觉这两个示例应该可以搞出音频强度,但是官方也没有直接把饭喂到嘴里,还得自己试。

实现

  1. 最开始是将两段代码整合,将音频帧的ArrayBuffer通过createWebAudioContext实例的decodeAudioData直接转换为可播放buffer试了一下播放,确实可以在录音时边录边播放,而且播放的是有损的音频,但是可以听出强度大小。顿时感觉这玩意靠谱但又不知道该怎么搞,边录边播放总是不行的。

  2. 结果又是一天快过去了,直到第二天,忽然搜到了抖音小程序那边给了一个音频强度绘制的示例,代码如下:

javascript 复制代码
const ctx = tt.getAudioContext();

const audio = ctx.createAudio();

audio.src = "xxxx.mp3";

audio.oncanplay = () => {

    audio.play();

};

const source = ctx.createMediaElementSource(audio);

const analyser = ctx.createAnalyser();

source.connect(analyser);

var bufferLength = analyser.frequencyBinCount;

var array = new Uint8Array(bufferLength);

analyser.getByteFrequencyData(array);

let values = 0;

const arrlength = array.length;

// 获取频率振幅

for (let i = 0; i < arrlength; i++) {

    values += array[i];

}

//通过平均值去到当前音量数据

const average = values / arrlength;

const data = average;
  1. 仔细看发现BufferSourceNode链接的都是AnalyserNode,和web端保持一致,会不会是微信小程序那边也可以,然后又专门看了BufferSourceNode的connect方法的参数类型:AudioNode|AudioParam destination。似乎不支持AnalyserNode,但是也没办法试一试吧。结果完了,在微信开发者工具上直接运行都不运行了。本以为要绝望了,忽然抖了个机灵,在真机上试了一下,卧槽,成了。

代码

javascript 复制代码
let recorder = wx.getRecorderManager()

const audioCtx = wx.createWebAudioContext()

const analyser = audioCtx.createAnalyser();

recorder.onFrameRecorded(listener => {

    if (listener.isLastFrame) {

        console.log('soundIntensity',0)

    } else {

        audioCtx.decodeAudioData(listener.frameBuffer, buffer => {

            let source = audioCtx.createBufferSource()

            source.buffer = buffer

            source.connect(analyser)

            source.start()

            let n = new Uint8Array(analyser.frequencyBinCount)

            analyser.getByteTimeDomainData(n)

            let i = 0, r = 0, s = 0

            r = Math.max.apply(null, n)

            s = Math.min.apply(null, n)

            i = (r - s) / 128

            i = Math.round(i * 100 / 2)

            i = i > 100 ? 100 : i

            console.log('soundIntensity', listener.isLastFrame ? 0 : i)

        }, err => {

        console.error('decodeAudioData fail', err)

        })

    }

})


recorder.start({

    duration: 1000,

    sampleRate: 16000, //采样率

    numberOfChannels: 1, //录音通道数

    encodeBitRate: 96000, //编码码率

    format: 'mp3', //音频格式,有效值 aac/mp3

    frameSize: 1

})

总结

探索的过程总是很有趣。在群里问了好久,都是以前做过,但是忘了。或者就是网上不是有很多现成的。很受打击,在微信小程序交流社区也是一堆人年年天天在催官方给出示例的,但是似乎都没有结果。好在网上是开放的,在诸多的帮助下,终于搞出来,特写这篇文章分享给大家,希望需要的同学不至于像我一般搞了这么多天。另外吐槽一下掘金真的是微信小程序的荒漠,啥有效内容都搜索不到。感谢阅读,为了方便搜索就不做标题党了。

相关推荐
多啦C梦a几秒前
🪄 用 React 玩转「图片识词 + 语音 TTS」:月影大佬的 AI 英语私教是怎么炼成的?
前端·react.js
呆呆的心几秒前
大厂面试官都在问的 WEUI Uploader,源码里藏了多少干货?🤔
前端·微信·面试
heartmoonq2 分钟前
深入理解 Vue 3 响应式系统原理:Proxy、Track 与 Trigger 的协奏曲
前端
一念杂记3 分钟前
免费开源!微信小程序商城源码,快速搭建你的线上商城系统!
微信小程序·uni-app
Web小助手4 分钟前
js高级程序设计(1/2章节)
javascript
长路 ㅤ   13 分钟前
前端技术博客汇总文档
javascript·vue.js·css3·html5·前端技术
独立开阀者_FwtCoder27 分钟前
放弃 JSON.parse(JSON.stringify()) 吧!试试现代深拷贝!
前端·javascript·github
张晓~183399481212 小时前
数字人源码部署流程分享--- PC+小程序融合方案
javascript·小程序·矩阵·aigc·文心一言·html5
爱喝水的小周2 小时前
AJAX vs axios vs fetch
前端·javascript·ajax
Jinxiansen02112 小时前
unplugin-vue-components 最佳实践手册
前端·javascript·vue.js