likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 4

背景

爬坑

  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、获取强度算法


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


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. 播放示例:

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. 获取音频强度示例:

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


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,但是也没办法试一试吧。结果完了,在微信开发者工具上直接运行都不运行了。本以为要绝望了,忽然抖了个机灵,在真机上试了一下,卧槽,成了。

代码


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

})

总结

代码片段

微信代码片段