第八 音频
声音是人类最古老的信息载体之一。从口头语言到音乐艺术,从电话通信到语音识别,音频技术贯穿了人类文明的发展历程。在数字时代,音频被编码为二进制数据,在计算机中存储、传输和处理。本章将探讨声音的本质、音频数字化原理、常见编码格式,以及如何使用 Rust 进行音频处理。
8.1 声音的本质
8.1.1 声波
声音是一种机械波,由物体振动产生,通过介质(通常是空气)以纵波的形式传播。当声波到达人耳时,引起鼓膜振动,最终被大脑感知为声音。
声波具有三个基本属性:
- 频率(Frequency):表示声波每秒振动的次数,单位是赫兹(Hz)。频率决定音调的高低。人耳可听范围约为 20 Hz ~ 20,000 Hz。
- 振幅(Amplitude):表示声波振动的强度,决定声音的响度。振幅越大,声音越响。
- 波形(Waveform):表示声波的形状,决定声音的音色。不同乐器演奏同一音高时,音色差异源于波形不同。
8.1.2 纯音与复音
纯音是单一频率的正弦波,如音叉发出的声音。复音则由多个不同频率的纯音叠加而成,包含基频和谐波。任何复杂的声音都可以通过傅里叶变换分解为一系列纯音的叠加,这也是音频压缩和合成的基础。
8.2 音频采样与量化
模拟声波是连续的,而计算机只能处理离散的数字信号。将模拟音频转换为数字音频的过程称为模数转换(ADC),包含两个核心步骤:采样和量化。
8.2.1 采样率
采样是在时间轴上对模拟信号进行离散化,每隔固定时间间隔记录一次声波的振幅值。每秒采样的次数称为采样率(Sample Rate)。
根据奈奎斯特-香农采样定理,为了完整还原原始信号,采样率必须至少是信号最高频率的两倍。因此:
- 44.1 kHz:CD 音质标准,可还原最高 22.05 kHz 的频率,覆盖人耳全部可听范围。
- 48 kHz:专业音频和数字视频的标准采样率。
- 96 kHz / 192 kHz:高解析度音频(Hi-Res),用于音乐制作和发烧级播放。
8.2.2 位深度
量化是在振幅轴上对采样值进行离散化,用有限位数的二进制数表示每个采样点的振幅。**位深度(Bit Depth)**决定了每个采样点使用的位数。
| 位深度 | 动态范围 | 应用场景 |
|---|---|---|
| 8 bit | 48 dB | 早期游戏音效、电话语音 |
| 16 bit | 96 dB | CD 音质标准 |
| 24 bit | 144 dB | 专业录音和制作 |
| 32 bit 浮点 | 超过 150 dB | 音频后期处理 |
位深度越大,量化误差越小,动态范围越广,信噪比越高。
8.2.3 声道与音频数据量
音频可以按声道数分为单声道(Mono)、立体声(Stereo,双声道),以及多声道环绕声(5.1、7.1 等)。
未经压缩的音频数据量计算公式:
数据量(字节/秒)= 采样率 × 位深度 × 声道数 / 8
以 CD 音质(44.1 kHz、16 bit、立体声)为例:
44100 × 16 × 2 / 8 = 176,400 字节/秒 ≈ 172 KB/s
一分钟约 10 MB,一小时约 600 MB
可见未经压缩的音频数据量相当可观,因此音频压缩编码技术至关重要。
8.3 音频编码格式
音频编码格式分为无损压缩、有损压缩和不压缩三大类。
8.3.1 WAV
WAV(Waveform Audio File Format)是微软和 IBM 联合开发的一种音频文件格式。它通常使用 PCM(Pulse Code Modulation,脉冲编码调制)编码,不压缩或仅使用无损压缩,完整保留原始音频数据。
- 优点:音质无损、解码简单、兼容性好、适合音频编辑。
- 缺点:文件体积大。
- 适用场景:专业音频制作、音效素材存储、需要反复编辑的音频文件。
8.3.2 MP3
MP3(MPEG-1 Audio Layer III)是最广为人知的有损压缩音频格式。它利用人耳的听觉掩蔽效应,去除人耳不易察觉的音频信息,实现大幅压缩。
- 压缩比:通常可达 1:10 ~ 1:12(128 kbps 时约为 CD 音质的 1/11)。
- 优点:兼容性好、文件小、普及度极高。
- 缺点:有损压缩,高频细节有所损失。
- 适用场景:音乐播放、网络流媒体、便携设备存储。
8.3.3 AAC
AAC(Advanced Audio Coding)是 MP3 的后继者,在相同码率下通常比 MP3 提供更好的音质。
- 优点:音质优于 MP3、支持多声道、被苹果生态广泛采用。
- 适用场景:Apple Music、YouTube、数字广播、移动设备。
8.3.4 FLAC
FLAC(Free Lossless Audio Codec)是一种开源的无损压缩音频格式,可将音频文件压缩到原大小的 50%~70%,同时完整保留所有音频信息,解码后可 100% 还原为原始 PCM 数据。
- 优点:无损压缩、开源免费、支持元数据标签。
- 缺点:压缩比不如有损格式,文件仍较大。
- 适用场景:音乐收藏、发烧级音频存储、音频归档。
| 格式 | 压缩类型 | 典型码率 | 特点 |
|---|---|---|---|
| WAV | 不压缩/无损 | 1411 kbps | 音质无损,文件最大 |
| FLAC | 无损压缩 | 700~1000 kbps | 体积减半,音质无损 |
| AAC | 有损压缩 | 128~256 kbps | 效率高,苹果生态主流 |
| MP3 | 有损压缩 | 128~320 kbps | 最普及,兼容性最好 |
8.4 Rust 音频处理
Rust 生态系统提供了多个优秀的音频处理库,涵盖音频播放、格式解析、WAV 读写等功能。
8.4.1 使用 rodio 播放音频
rodio 是一个纯 Rust 编写的音频播放库,支持 WAV、MP3、Vorbis、FLAC 等格式,使用简单,适合游戏和应用程序中的音频播放需求。
在 Cargo.toml 中添加依赖:
[dependencies]
rodio = "0.17"
播放本地音频文件:
use std::fs::File;
use std::io::BufReader;
fn main() {
// 1. 获取默认音频输出设备
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
// 2. 打开音频文件
let file = File::open("music.mp3").unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
// 3. 播放音频
stream_handle.play_raw(source.convert_samples()).unwrap();
// 4. 等待播放完成(示例中休眠 10 秒)
std::thread::sleep(std::time::Duration::from_secs(10));
}
rodio 还支持音量控制、播放速度调整、音频混合等高级功能:
use rodio::{Decoder, Source};
use std::fs::File;
use std::io::BufReader;
fn main() {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let file = File::open("music.wav").unwrap();
let source = Decoder::new(BufReader::new(file)).unwrap();
// 调整音量(0.5 表示一半音量)
let volume_adjusted = source.amplify(0.5);
// 调整播放速度(1.5 倍速)
let speed_adjusted = volume_adjusted.speed(1.5);
stream_handle.play_raw(speed_adjusted.convert_samples()).unwrap();
std::thread::sleep(std::time::Duration::from_secs(10));
}
8.4.2 使用 symphonia 解析音频
symphonia 是一个纯 Rust 编写的高性能多媒体格式解析库,支持 MP3、AAC、FLAC、WAV、OGG 等多种格式,设计注重正确性和安全性。
在 Cargo.toml 中添加依赖:
[dependencies]
symphonia = { version = "0.5", features = ["mp3", "flac", "wav"] }
读取音频文件元数据和采样数据:
use symphonia::core::codecs::DecoderOptions;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
use std::fs::File;
fn main() {
// 1. 打开文件
let file = File::open("music.mp3").unwrap();
let mss = MediaSourceStream::new(Box::new(file), Default::default());
// 2. 创建提示和选项
let hint = Hint::new();
let format_opts: FormatOptions = Default::default();
let metadata_opts: MetadataOptions = Default::default();
let decoder_opts: DecoderOptions = Default::default();
// 3. 探测格式
let probed = symphonia::default::get_probe()
.format(&hint, mss, &format_opts, &metadata_opts)
.unwrap();
let mut format = probed.format;
// 4. 获取音频流信息
let track = format.default_track().unwrap();
let codec_params = &track.codec_params;
println!("采样率: {:?}", codec_params.sample_rate);
println!("声道数: {:?}", codec_params.channels);
// 5. 创建解码器并读取采样数据
let mut decoder = symphonia::default::get_codecs()
.make(codec_params, &decoder_opts)
.unwrap();
while let Ok(packet) = format.next_packet() {
match decoder.decode(&packet) {
Ok(decoded) => {
// 处理解码后的音频采样数据
let samples = decoded.samples();
println!("解码得到 {} 个采样点", samples.len());
}
Err(_) => break,
}
}
}
8.4.3 使用 hound 读写 WAV 文件
hound 是一个轻量级的纯 Rust WAV 文件读写库,无外部依赖,适合需要生成或处理 WAV 音频的场景。
在 Cargo.toml 中添加依赖:
[dependencies]
hound = "3.5"
读取 WAV 文件:
fn main() {
let reader = hound::WavReader::open("music.wav").unwrap();
let spec = reader.spec();
println!("采样率: {}", spec.sample_rate);
println!("声道数: {}", spec.channels);
println!("位深度: {}", spec.bits_per_sample);
println!("样本格式: {:?}", spec.sample_format);
// 读取所有采样值(假设为 16 位有符号整数)
let samples: Vec<i16> = reader.into_samples::<i16>()
.filter_map(|s| s.ok())
.collect();
println!("总采样数: {}", samples.len());
}
生成 WAV 文件(生成一个 1 秒的正弦波):
use std::f32::consts::PI;
fn main() {
let sample_rate = 44100;
let frequency = 440.0; // A4 音符
let duration = 1; // 1 秒
let amplitude = 0.5;
let spec = hound::WavSpec {
channels: 1,
sample_rate,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = hound::WavWriter::create("sine.wav", spec).unwrap();
for t in 0..sample_rate * duration {
let sample = (amplitude * (2.0 * PI * frequency * t as f32 / sample_rate as f32).sin())
* i16::MAX as f32;
writer.write_sample(sample as i16).unwrap();
}
writer.finalize().unwrap();
println!("已生成 sine.wav");
}
8.4.4 音频可视化:提取波形数据
结合 hound 库,我们可以提取音频数据用于可视化分析:
#![allow(unused)]
fn main() {
fn analyze_audio(path: &str) {
let reader = hound::WavReader::open(path).unwrap();
let spec = reader.spec();
let samples: Vec<i16> = reader.into_samples::<i16>()
.filter_map(|s| s.ok())
.collect();
// 计算每个声道的采样数
let samples_per_channel = samples.len() / spec.channels as usize;
// 计算平均振幅(响度近似值)
let sum: i64 = samples.iter().map(|&s| s.abs() as i64).sum();
let avg_amplitude = sum as f64 / samples.len() as f64;
println!("文件: {}", path);
println!("总采样数: {}", samples.len());
println!("每声道采样数: {}", samples_per_channel);
println!("平均振幅: {:.2}", avg_amplitude);
// 找出最大振幅
let max_amplitude = samples.iter().map(|&s| s.abs()).max().unwrap();
println!("最大振幅: {}", max_amplitude);
}
}
8.5 本章小结
| 概念 | 说明 |
|---|---|
| 声波 | 机械纵波,由频率、振幅、波形三要素描述 |
| 采样率 | 每秒采样次数,CD 标准为 44.1 kHz |
| 位深度 | 每个采样点的位数,CD 标准为 16 bit |
| WAV | 不压缩/无损,音质最好,文件最大 |
| FLAC | 无损压缩,体积减半,音质无损 |
| MP3 | 有损压缩,最普及,兼容性好 |
| AAC | 有损压缩,效率优于 MP3 |
| rodio | Rust 音频播放库,简单易用 |
| symphonia | Rust 多媒体格式解析库,功能强大 |
| hound | Rust WAV 文件读写库,轻量无依赖 |
练习建议
- 使用 hound 库生成不同频率的正弦波,体验频率与音调的关系。
- 使用 rodio 播放音频文件,并实现一个简单的播放器(支持暂停、继续、音量调节)。
- 编写程序读取 WAV 文件,计算其 RMS(均方根)响度值。
- 尝试使用 symphonia 解析 MP3 或 FLAC 文件,提取其元数据(标题、艺术家等)。
- 对比同一首音乐的不同格式(WAV、FLAC、MP3)的文件大小,理解压缩率的差异。