WebCodecs 音视频四音频处理
为什么单独介绍音频处理?
- 因为网络上缺乏音频处理的资料,绝大多数示例都是针对视频而略过音频,很多人在网上寻找音频处理的示例
- 对前端开发者来说,音频处理相对视频略微复杂一些
所以,本文专门针对音频数据,汇总讲解采集-处理-编码-封装全过程,帮助初学者入门。
上图是在 Web 中从采集音频到封装文件的大致流程。
你可以跳过原理介绍,直接查看 WebAV 编码封装音频示例
采集
# 数字化声波
声音的本质是波,对一段连续声波进行采样,每一个点用一个浮点数来表示,声音就被数字化成了一个浮点数组;js 中通常使用 Float32Array
来记录数字化后的浮点数组。
数字化(Pulse Code Modulation, PCM)一段声音后(Float32Array
),还需要几个必要属性来描述这段数据
- SampleRate(采样率):采样声波的频率,48KHz 就是每秒采样 48000 个数字
- ChannelCount(声道数):声音来源数量,比如两个声波(双声道)采样后会得到两个
Float32Array
,通常会将它们前后拼接成一个Float32Array
,前一半为左声道声波采样数据,后一半为右声道数据
为什么没有声音时长?
时长 duration = Float32Array.length / ChannelCount / SampleRate
假设一段单声道音频数据(Float32Array)长度为 96000,SampleRate 为 48KHz
那么这段音频的持续时长为: 96000 / 1 / 48000 = 2
秒
# 声音数据(Float32Array)来源
列举三个主要音频来源,转换到 Float32Array
的过程
- 本地或网络音频文件 ->
ArrayBuffer
->AudioContext.decodeAudioData
->Float32Array
Video or Audio Element
->MediaElement.captureStream()
->MediaStream
->MediaStreamTrack
->MediaStreamTrackProcessor
->AudioData
->Float32Array
- 麦克风、屏幕分享 ->
MediaStream
-> ...(如上)
转换过程不算复杂,只是需要阅读的 API 比较多;获得 Float32Array
后就可以进行下一步处理。
处理
图像处理的计算复杂度高很多,依赖硬件加速;对前端开发者来说是绘制到 canvas,使用对应的 API 去操作,反而对音频处理更陌生。
这里举几个常见例子,帮助大家熟悉音频处理逻辑;可以选择写代码编辑音频数据,也可以借助已有的 Web Audio API (如 重采样)。
# Web Audio API
Web Audio API 提供了在 Web 上控制音频的一个非常有效通用的系统,允许开发者来自选音频源,对音频添加特效,使音频可视化,添加空间效果(如平移)等等。
先提一下 Web Audio API ,它包含非常多的 API 用于在 Web 中创建、处理音频;
本文会依赖其中少量 API,但不会过多介绍,有兴趣的同学可以阅读张鑫旭的文章 JS交互增加声音
# 音量调节
中学物理学过,声波的振幅表示音量大小,乘以一个数可以改变振幅(音量);
所以 Float32Array
乘以 0~1 之间小数相当于降低音量,大于 1 相当于增大音量
for (let i = 0; i < float32Arr.length; i ) float32Arr[i] *= 0.5
以上是原理,因为人耳对音量大小的感知是对数关系而不是线性的,实际音量调节要复杂很多,可阅读 PCM 音量控制
# 混流
因为声音的本质是波,所以多个声音混合即波的叠加,使用加法即可
float32Arr1 float32Arr2 => outFloat32Arr
const len = Math.max(float32Arr1.length, float32Arr2.length)
const outFloat32Arr = new Float32Array(len)
for (let i = 0; i < len; i )
outFloat32Arr[i] = (float32Arr1[i] ?? 0) (float32Arr2[i] ?? 0)
# 声音淡入/淡出
最常见的场景,点击按钮暂停音乐时,声音大小是快速降低为 0,而不是瞬间消失。
多年前很多音乐播放器是没有做声音淡出的,现在已经体验不到那种声音瞬间消失的难受感觉了。
假设需要截断一个音频,为了前半段的音频结尾听起不那么难受,需要将结尾的 0.5s 音量降低至 0;音频采样率为 48KHz。
const pcmF32Arr = new Float32Array(Array(48000).fill(0).map(() => Math.random() * 2 - 1))
const start = pcmF32Arr.length - 1 - 48000 / 2
for (let i = 0; i < 48000 / 2; i = 1)
pcmF32Arr[start i] *= (1 - i / 48000 / 2)
# 重采样
当输入的音频采样率跟输出不一致,或需要混流两个采样率不同的音频时,就需要对音频进行重采样,改变音频的采样率。
重采样的原理是对 Float32Array
进行抽取或插值;
比如,48KHz 采样率的音频 1s 有 48000 个点(数字),采样率降低至 44.1KHz 则 1s 的音频需要丢掉其中 48000 - 44100
个点。
在 Web 中 AudioContext、OfflineAudioContext API 已经提供重采样能力,我们可以利用 API 来对音频进行重采样,无需自己实现相关算法。
使用 OfflineAudioContext 重采样音频数据
编码音频
因为 AudioEncoder 只能编码 AudioData 对象,所以需要先将 Float32Array 转换成 AudioData 对象。
new AudioData({
timestamp: 0,
numberOfChannels: 2,
numberOfFrames: pcmF32Arr.length / 2,
sampleRate: 48000,
format: 'f32-planar',
data: pcmF32Arr
})
创建并初始化音频编码器
const encoder = new AudioEncoder({
output: (chunk) => {
},
error: console.error
})
encoder.configure({
codec: 'mp4a.40.2',
sampleRate: 48000,
numberOfChannels: 2,
})
encoder.encode(audioData)
TIP
创建编码器之前,记得先使用 AudioEncoder.isConfigSupported
检测兼容性
封装
继续使用 mp4box.js 来演示封装 EncodedAudioChunk
主要代码
限于篇幅,上述代码以来的 createESDSBox 的源码 在这里
如果你需要自己写代码实现音频封装逻辑,需要注意:
- mp4box.js 创建 ESDS Box 还有 Bug 未修复,查看详情
- 音频轨道与视频轨道创建完成(addTrack)之后才能添加数据(addSample),而创建轨道都是需要等待编码器(VideoEncoder、AudioEncoder)的 meta 数据,请查看同步创建音视频轨道 代码
- 封装的音频数据时间(
Sample.cts
)似乎不能用来控制音频偏移,如果某一段时间没有声音,你仍然需要填充数据,比如 10s 没有声音的 PCM 数据new Float32Array(Array(10 * 48000).fill(0))
WebAV 编码封装音频示例
如果从零开始实现编码封装音频,除了参考以上的原理介绍,还需要阅读大量 API 和细节处理。
你可以略过细节,使用 @webav/av-cliper
提供的工具函数 recodemux 、 file2stream
来快速编码封装音频,创建视频文件。
以下是从麦克风获取音频数据并编码封装的核心代码。
import { recodemux, file2stream } from '@webav/av-cliper'
const muxer = recodemux({
video: {
width: 1280,
height: 720,
expectFPS: 30
},
audio: {
codec: 'aac',
sampleRate: 48000,
channelCount: 2
}
})
const { stream, stop: stopOutput } = file2stream(muxer.mp4file, 500)
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
const audioTrack = mediaStream.getAudioTracks()[0]
const reader = new MediaStreamTrackProcessor({ track: audioTrack }).readable.getReader()
async function readAudioData() {
while (true) {
const { value, done } = await reader.read()
if (done) {
stopOutput()
}
await muxer.encodeAudio(value)
}
}
readAudioData.catch(console.error)
这里是从摄像头、麦克风录制数据导出 MP4 文件的完整示例 代码,点击立即体验 DEMO
附录
- WebAV 基于 WebCodecs 构建的音视频处理 SDK
- 录制摄像头、麦克风 DEMO
- Web Audio API
- JS 交互增加声音
- PCM 音量控制
- AudioData 、 AudioEncoder
- WebAV audioResample 源码
- WebAV createESDSBox 源码
本文使用 文章同步助手 同步
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgajeaf
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
怎样阻止微信小程序自动打开
PHP中文网 06-13