Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第八 音频

声音是人类最古老的信息载体之一。从口头语言到音乐艺术,从电话通信到语音识别,音频技术贯穿了人类文明的发展历程。在数字时代,音频被编码为二进制数据,在计算机中存储、传输和处理。本章将探讨声音的本质、音频数字化原理、常见编码格式,以及如何使用 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 bit48 dB早期游戏音效、电话语音
16 bit96 dBCD 音质标准
24 bit144 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
rodioRust 音频播放库,简单易用
symphoniaRust 多媒体格式解析库,功能强大
houndRust WAV 文件读写库,轻量无依赖

练习建议

  1. 使用 hound 库生成不同频率的正弦波,体验频率与音调的关系。
  2. 使用 rodio 播放音频文件,并实现一个简单的播放器(支持暂停、继续、音量调节)。
  3. 编写程序读取 WAV 文件,计算其 RMS(均方根)响度值。
  4. 尝试使用 symphonia 解析 MP3 或 FLAC 文件,提取其元数据(标题、艺术家等)。
  5. 对比同一首音乐的不同格式(WAV、FLAC、MP3)的文件大小,理解压缩率的差异。