1. 录音播放功能实现
背景:业务中需要在前端页面上实现录音的播放功能,可能选择位置、速度进行播放,以及实时当前播放位置对应的语音转写文本。
录音文件通过 http 方式获取,服务器不进行格式转换。所以会出现 wav/flac/pcm 这些前端不能直接播放的声音格式,针对这些问题进行技术调研和验证。
1.1. 目前可用的开源方案调研
1.1.1. 音乐播放器 aplayer
6.2k star
成熟度高,功能完整、维护时间长。
项目地址: github.com/DIYgod/APla…
调研结果:
这是一个比较类似音乐播放器的项目,本身提供了一些音乐播放基本的API,例如歌词显示、播放、停止、选择位置等。如果格式情况满足要求的的话经过一些修改用到项目中。
声音格式支持为:
- mp3
- acc
- wave pcm - 不直接支持 pcm
- ogg
- falc - 不支持 falc
转写文本(歌词)
- lrc
使用示例
const ap = new APlayer({
container: document.getElementById('aplayer'),
audio: [{
name: 'name',
artist: 'artist',
url: 'url.mp3',
cover: 'cover.jpg'
}]
});
1.1.2. 声音播放库 Howler
20.4k star
成熟度高,仅用于专门播放声音、维护时间长。
项目地址: github.com/goldfire/ho…
调研结果:
这个库用于专门播放声音,特别用于多声音文件合并播放、合并处理。可以自动缓存加载过的音乐。支持声音分段播放。
声音格式支持为:
- mp3
- acc
- pcm - 不支持 pcm
- ogg
- falc
- wav
转写文本(歌词)
- 不支持
使用示例 基本示例:
import {Howl, Howler} from 'howler';
// 初始化一个音频类
const sound = new Howl({
src: ['sound.webm', 'sound.mp3']
});
// 播放音频
sound.play();
// 改变全局音频声音大小
Howler.volume(0.5);
// 只想改变某个音频的大小可以在初始化的时候修改
const sound = new Howl({
src: ['sound.webm', 'sound.mp3'],
volume:0.5
});
定义和播放某一部分的音频:
var sound = new Howl({
urls: ['sounds.mp3', 'sounds.ogg'],
sprite: {
blast: [0, 1000],
laser: [2000, 3000],
winner: [4000, 7500]
}
});
// shoot the laser!
sound.play('laser');
1.2. 已知开源方案横向对比进行选择
如果是做一个类似 mp3 播放器列表的形式的话偏向于选择 aplayer, 因为它比较接近。同时还支持歌词。
但当前业务是对单个文件进行分段播放比较多,样式和交互与 aplayer 提供的有较大的差别,虽然 aplayer 提供了歌词功能,我们可以把自己的语音转写文本转换为符合规范的 lrc 文件。
但当前原型的交互也与 aplayer 提供的歌词显示情况很不一致,对于已经为播放器而生的 aplayer 封装来说,要修改这些交互和样式作出的成本很高。
howler 只提供播放声音和部分我们可以使用的声音操作 api,不提供声音与对应的文本播放功能,不提供播放界面。 但是它提供的 api 和支持的格式相比于 aplayer 来说对于我们的可用性更多一些。
综上所述:
| 要求 | aplayer | howler | 备注 |
|---|---|---|---|
| 常见格式 mp3/acc/ogg/wav | [x] | [x] | -- |
| 无损音频 flac | [ ] | [x] | -- |
| 声音源文件 pcm | [ ] | [ ] | -- |
| 分段播放 | [ ] | [x] | -- |
| 转写(歌词) | [x] | [ ] | 与原型差别较大 |
| 播放器界面 | [x] | [ ] | 与原型差别较大 |
| 播放速度控制 | [ ] | [x] |
选择: aplayer 所提供的界面难以复用,并且不支持 flac,所以我们选择 howler。
1.3. 需要额外处理的
就仅上述要求而言,我们需要做以下事情:
- 处理 pcm 的支持情况
- 验证 howler 的 api 和格式
- 根据原型和UI开发交互界面
1.3.1. 处理 pcm 的支持情况
根据研究, pcm 格式可以简单理解为是模板信息的数字形式存储,不属于完整的直接可播放的声音文件。一般可直接播放的声音文件中,包含了一部分信息,这部分信息用于描述自身的一些属性以让播放器知道如何播放自己。
有一些开源的库可以对 pcm 直接播放,但由于这里我们要兼容其他格式,要使用 howler 来进行播放,所以不能直接使用这些播放 pcm 的库。
了解到与 pcm 想近的格式是 wav,所以这里我们把它转换为 wav,一般情况下,wav数据实际上就是裸数据pcm外面包了一层文件头。
wav头如下图表示:
这里附上可用的转换代码,若需要了解具体原理请参考相关文章。
// 资源交换文件标识符
writeString(data, offset, 'RIFF'); offset += 4;
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + bytes.byteLength, true); offset += 4;
// WAV文件标志
writeString(data, offset, 'WAVE'); offset += 4;
// 波形格式标志
writeString(data, offset, 'fmt '); offset += 4;
// 过滤字节,一般为 0x10 = 16
data.setUint32(offset, 16, true); offset += 4;
// 格式类别 (PCM形式采样数据)
data.setUint16(offset, 1, true); offset += 2;
// 声道数
data.setUint16(offset, channelCount, true); offset += 2;
// 采样率,每秒样本数,表示每个通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4;
// 波形数据传输率 (每秒平均字节数) 声道数 × 采样频率 × 采样位数 / 8
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4;
// 快数据调整数 采样一次占用字节数 声道数 × 采样位数 / 8
data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
// 采样位数
data.setUint16(offset, sampleBits, true); offset += 2;
// 数据标识符
writeString(data, offset, 'data'); offset += 4;
// 采样数据总数,即数据总大小-44
data.setUint32(offset, bytes.byteLength, true); offset += 4;
// 给wav头增加pcm体
for (let i = 0; i < bytes.byteLength; ++i) {
data.setUint8(offset, bytes.getUint8(i, true), true);
offset++;
}
在本地调试的时候,可以使用 audacity 进行播放验证。 Audacity 是一个易用、多轨音频录制和编辑的自由、开源、跨平台音乐软件。
- 下载地址: github.com/audacity/au…
1.3.2. 验证 howler 的 api 和格式
对 pcm、wav、m4a、flac、aiff、m4a、mp3、ogg 这些格式的声音文件以及 播放、时间跳转、停止、下载、速度 功能进行尝试,均可正常使用。
以下为测试代码, 相关的测试文件以附件方式与本文一起提供:
<template>
<div class="test">
<div v-for="item in audioHowlList" :key="item.link">
<div>{{ item.link }}</div>
<button @click="playItem(item)">播放</button>
<button @click="item.howl.seek(5)">时间跳转</button>
<button @click="item.howl.stop()">停止</button>
<button @click="downItem(item)">下载</button>
<button @click="item.howl.rate(0.5)">速度 0.5</button>
<button @click="item.howl.rate(4.0)">速度 4.0</button>
<button @click="item.howl.rate(1.0)">速度 1.0</button>
<hr />
</div>
</div>
</template>
<script>
import { Howl, Howler } from 'howler'
export default {
name: `Home`,
data() {
return {
audioHowlList: [],
audioList: [
`/audio/2channels-16bit-8kHz.pcm`,
`/audio/pcm1608s.wav`,
`/audio/Sample_BeeMoved_48kHz16bit.m4a`,
`/audio/Sample_BeeMoved_96kHz24bit.flac`,
`/audio/AIFF-Testfile.aiff`,
`/audio/ALAC-Testfile.m4a`,
`/audio/MP3-Testfile.mp3`,
`/audio/OGG-Testfile.ogg`,
],
}
},
components: {},
async mounted() {
window.ctx = Howler.ctx
let res = []
for (let i = 0; i < this.audioList.length; i++) {
let url = this.audioList[i]
if (url.endsWith(`.pcm`)) {
url = await window.pcmUrlToWavUrl({
pcmUrl: `/audio/2channels-16bit-8kHz.pcm`,
rate: 8000,
bit: 16,
channel: 2,
})
}
console.log(`url`, url)
res.push({
link: url.slice(0, 100) + `...`,
howl: new Howl({
src: [url],
loop: true,
}),
})
}
this.audioHowlList = res
},
computed: {},
methods: {
playItem(item) {
Howler.stop()
item.howl.play()
},
playSprite(item, spriteName) {
Howler.stop()
item.howl.play(spriteName)
},
downItem(item) {
window.open(item.link)
},
},
}
</script>
<style lang="less" scoped>
.test {
}
</style>
如果需要其他格式的测试文件,可以到下面的网站上直接获取:
- wav pcm mauvecloud.net/sounds/
- mp3 flac alac www.hyperion-records.co.uk/testfiles.a…
- wav ogg www.24bit96.com/downloads/d…
1.4. 参考
- wikipedia
- Adobe Flash Video File Format Specification Version10.1
- Digitizingaudio
- ISO-14496-15 AVC file format
- ISO/IEC 14496-3
- WebAudioAPI