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

背景

老板要做录音体验优化,有一条实现和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

})

总结

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

相关推荐
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606112 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了12 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅12 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅13 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment13 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端
爱敲代码的小鱼13 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax