884 lines
28 KiB
Markdown
884 lines
28 KiB
Markdown
# 音频引擎API参考
|
||
|
||
## 目录
|
||
|
||
- [概述](#概述)
|
||
- [核心类型](#核心类型)
|
||
- [AudioFormat](#audioformat)
|
||
- [AudioConfig](#audioconfig)
|
||
- [音频缓冲区](#音频缓冲区)
|
||
- [AudioBuffer类](#audiobuffer类)
|
||
- [内存布局](#内存布局)
|
||
- [格式转换](#格式转换)
|
||
- [混音和增益](#混音和增益)
|
||
- [环形缓冲区](#环形缓冲区)
|
||
- [RingBuffer类](#ringbuffer类)
|
||
- [线程安全性](#线程安全性)
|
||
- [实现示例](#实现示例)
|
||
- [基本音频处理](#基本音频处理)
|
||
- [实时音频流处理](#实时音频流处理)
|
||
- [多声道混音示例](#多声道混音示例)
|
||
- [性能优化策略](#性能优化策略)
|
||
- [SIMD优化](#simd优化)
|
||
- [内存对齐](#内存对齐)
|
||
- [零拷贝技术](#零拷贝技术)
|
||
- [跨平台考虑](#跨平台考虑)
|
||
|
||
## 概述
|
||
|
||
音频引擎模块是音频后端系统的核心组件,负责高效处理和管理音频数据。该模块采用SIMD优化设计,支持多种音频格式和声道配置,并提供线程安全的音频流处理功能。
|
||
|
||
核心特性:
|
||
|
||
- 支持多种音频格式(16/24/32位整数,32/64位浮点数)
|
||
- 支持交错和非交错(平面)音频数据布局
|
||
- SIMD指令集优化(SSE/AVX/AVX-512/NEON)
|
||
- 零拷贝数据传输
|
||
- 线程安全的环形缓冲区实现
|
||
- 高性能混音和格式转换
|
||
|
||
## 核心类型
|
||
|
||
### AudioFormat
|
||
|
||
`AudioFormat`枚举定义了系统支持的音频采样格式。
|
||
|
||
```cpp
|
||
enum class AudioFormat {
|
||
UNKNOWN = 0,
|
||
INT16, // 16位有符号整数 [-32768, 32767]
|
||
INT24, // 24位有符号整数(包在int32中)[-8388608, 8388607]
|
||
INT32, // 32位有符号整数
|
||
FLOAT32, // 32位浮点数 [-1.0, 1.0]
|
||
FLOAT64 // 64位浮点数 [-1.0, 1.0]
|
||
};
|
||
```
|
||
|
||
**格式说明:**
|
||
|
||
| 格式 | 描述 | 位深度 | 数值范围 | 典型用途 |
|
||
|------|------|--------|----------|----------|
|
||
| `INT16` | 16位有符号整数 | 16位 | [-32768, 32767] | 兼容性最广,适用于存储和传输 |
|
||
| `INT24` | 24位有符号整数 | 24位 | [-8388608, 8388607] | 专业音频录制,高动态范围需求 |
|
||
| `INT32` | 32位有符号整数 | 32位 | [-2^31, 2^31-1] | 高精度处理,中间计算格式 |
|
||
| `FLOAT32` | 32位IEEE浮点数 | 32位 | [-1.0, 1.0] | 标准处理格式,良好的精度和性能平衡 |
|
||
| `FLOAT64` | 64位IEEE浮点数 | 64位 | [-1.0, 1.0] | 高精度计算,避免累积误差 |
|
||
|
||
**辅助函数:**
|
||
|
||
```cpp
|
||
// 获取音频格式的字节大小
|
||
size_t get_format_byte_size(AudioFormat format);
|
||
|
||
// 获取音频格式名称
|
||
const char* get_format_name(AudioFormat format);
|
||
```
|
||
|
||
### AudioConfig
|
||
|
||
`AudioConfig`结构定义了音频处理的配置参数,包括采样率、声道数、格式和缓冲区大小。
|
||
|
||
```cpp
|
||
struct AudioConfig {
|
||
uint32_t sample_rate = 48000; // 采样率(Hz)
|
||
uint16_t channels = 2; // 声道数
|
||
AudioFormat format = AudioFormat::FLOAT32; // 音频格式
|
||
uint32_t frames_per_buffer = 512; // 每个缓冲区的帧数
|
||
|
||
// 验证配置有效性
|
||
bool is_valid() const;
|
||
|
||
// 计算缓冲区大小(字节)
|
||
size_t get_buffer_size_bytes() const;
|
||
|
||
// 计算缓冲区大小(样本数)
|
||
size_t get_buffer_size_samples() const;
|
||
|
||
// 计算延迟(毫秒)
|
||
double get_latency_ms() const;
|
||
|
||
// 比较操作符
|
||
bool operator==(const AudioConfig& other) const;
|
||
bool operator!=(const AudioConfig& other) const;
|
||
};
|
||
```
|
||
|
||
**成员说明:**
|
||
|
||
- **sample_rate**: 音频采样率,单位Hz。常见值: 44100 (CD质量), 48000 (专业音频), 96000/192000 (高分辨率)
|
||
- **channels**: 音频声道数。1=单声道, 2=立体声, >2=多声道
|
||
- **format**: 音频数据格式,参见 `AudioFormat` 枚举
|
||
- **frames_per_buffer**: 每个处理缓冲区包含的帧数,影响延迟和CPU负载
|
||
|
||
**辅助方法:**
|
||
|
||
- `is_valid()`: 检查配置参数是否在有效范围内
|
||
- `get_buffer_size_bytes()`: 计算缓冲区的总字节数 (= frames_per_buffer × channels × 每样本字节数)
|
||
- `get_buffer_size_samples()`: 计算缓冲区的总样本数 (= frames_per_buffer × channels)
|
||
- `get_latency_ms()`: 计算基于当前缓冲区大小和采样率的理论处理延迟,单位毫秒
|
||
|
||
**常见配置示例:**
|
||
|
||
```cpp
|
||
// CD质量立体声配置
|
||
AudioConfig cd_quality;
|
||
cd_quality.sample_rate = 44100;
|
||
cd_quality.channels = 2;
|
||
cd_quality.format = AudioFormat::INT16;
|
||
cd_quality.frames_per_buffer = 1024;
|
||
|
||
// 专业音频配置(低延迟)
|
||
AudioConfig pro_audio_low_latency;
|
||
pro_audio_low_latency.sample_rate = 96000;
|
||
pro_audio_low_latency.channels = 2;
|
||
pro_audio_low_latency.format = AudioFormat::FLOAT32;
|
||
pro_audio_low_latency.frames_per_buffer = 128; // 低延迟
|
||
|
||
// 环绕声配置
|
||
AudioConfig surround;
|
||
surround.sample_rate = 48000;
|
||
surround.channels = 6; // 5.1声道
|
||
surround.format = AudioFormat::FLOAT32;
|
||
surround.frames_per_buffer = 512;
|
||
```
|
||
|
||
## 音频缓冲区
|
||
|
||
### AudioBuffer类
|
||
|
||
`AudioBuffer`是音频引擎的核心类,用于存储和处理音频数据。它支持交错和非交错(平面)格式,并提供各种音频处理功能。
|
||
|
||
```cpp
|
||
class AudioBuffer {
|
||
public:
|
||
// 构造函数
|
||
AudioBuffer();
|
||
explicit AudioBuffer(const AudioConfig& config, bool interleaved = false);
|
||
AudioBuffer(uint32_t frames, uint16_t channels, AudioFormat format, bool interleaved = false);
|
||
|
||
// 移动构造和赋值
|
||
AudioBuffer(AudioBuffer&& other) noexcept;
|
||
AudioBuffer& operator=(AudioBuffer&& other) noexcept;
|
||
|
||
// 禁止拷贝(使用clone方法显式拷贝)
|
||
AudioBuffer(const AudioBuffer&) = delete;
|
||
AudioBuffer& operator=(const AudioBuffer&) = delete;
|
||
|
||
// 显式拷贝
|
||
AudioBuffer clone() const;
|
||
|
||
// 重新分配缓冲区
|
||
void allocate(const AudioConfig& config, bool interleaved = false);
|
||
void allocate(uint32_t frames, uint16_t channels, AudioFormat format, bool interleaved = false);
|
||
|
||
// 释放缓冲区
|
||
void release();
|
||
|
||
// 清空缓冲区(填充零)
|
||
void clear();
|
||
|
||
// 获取配置
|
||
const AudioConfig& config() const;
|
||
|
||
// 访问器
|
||
uint32_t frames() const;
|
||
uint16_t channels() const;
|
||
AudioFormat format() const;
|
||
uint32_t sample_rate() const;
|
||
bool is_interleaved() const;
|
||
|
||
// 数据访问(非交错格式)
|
||
template<typename T>
|
||
T* channel_data(uint16_t channel);
|
||
|
||
template<typename T>
|
||
const T* channel_data(uint16_t channel) const;
|
||
|
||
// 数据访问(交错格式)
|
||
template<typename T>
|
||
T* interleaved_data();
|
||
|
||
template<typename T>
|
||
const T* interleaved_data() const;
|
||
|
||
// 原始数据访问
|
||
uint8_t* data();
|
||
const uint8_t* data() const;
|
||
|
||
size_t size_bytes() const;
|
||
bool empty() const;
|
||
|
||
// 转换为交错/非交错格式
|
||
AudioBuffer to_interleaved() const;
|
||
AudioBuffer to_non_interleaved() const;
|
||
|
||
// 格式转换
|
||
AudioBuffer convert_format(AudioFormat new_format) const;
|
||
|
||
// 重采样(简单的线性插值,用于格式转换)
|
||
AudioBuffer resample(uint32_t new_sample_rate) const;
|
||
|
||
// 复制数据到另一个缓冲区
|
||
void copy_to(AudioBuffer& dest) const;
|
||
|
||
// 从另一个缓冲区复制数据
|
||
void copy_from(const AudioBuffer& src);
|
||
|
||
// 混音(加法混合)
|
||
void mix_from(const AudioBuffer& src, float gain = 1.0f);
|
||
|
||
// 应用增益
|
||
void apply_gain(float gain);
|
||
|
||
// 检查缓冲区是否正确对齐
|
||
bool is_aligned() const;
|
||
};
|
||
```
|
||
|
||
### 内存布局
|
||
|
||
AudioBuffer支持两种内存布局:
|
||
|
||
1. **交错格式 (Interleaved)**:
|
||
样本按帧组织,每个帧包含所有声道的样本。布局为:`LRLRLRLR...`(立体声)
|
||
```
|
||
内存布局:[L0 R0 L1 R1 L2 R2 ... Ln Rn]
|
||
```
|
||
|
||
2. **非交错格式 (Non-interleaved/Planar)**:
|
||
样本按声道组织,每个声道的样本连续存储。布局为:`LLLL...RRRR...`(立体声)
|
||
```
|
||
内存布局:[L0 L1 L2 ... Ln R0 R1 R2 ... Rn]
|
||
```
|
||
|
||
**选择合适的布局:**
|
||
|
||
- **交错格式**适合:
|
||
- 与传统音频API集成(大多数API使用交错格式)
|
||
- 顺序访问所有声道(如文件I/O或网络传输)
|
||
|
||
- **非交错格式**适合:
|
||
- SIMD向量化处理(可以处理单个声道的连续样本)
|
||
- 需要单独处理声道的场景(如中置声道独立处理)
|
||
- 复杂的DSP算法实现
|
||
|
||
### 格式转换
|
||
|
||
AudioBuffer提供了多种格式转换功能:
|
||
|
||
```cpp
|
||
// 转换内存布局
|
||
AudioBuffer interleaved_buffer = non_interleaved_buffer.to_interleaved();
|
||
AudioBuffer planar_buffer = interleaved_buffer.to_non_interleaved();
|
||
|
||
// 转换采样格式
|
||
AudioBuffer float_buffer = int_buffer.convert_format(AudioFormat::FLOAT32);
|
||
AudioBuffer int_buffer = float_buffer.convert_format(AudioFormat::INT16);
|
||
|
||
// 重采样
|
||
AudioBuffer resampled = original.resample(96000); // 从原始采样率转到96kHz
|
||
```
|
||
|
||
**格式转换的注意事项:**
|
||
|
||
- 整数到浮点转换:整数范围将映射到[-1.0, 1.0]浮点范围
|
||
- 浮点到整数转换:[-1.0, 1.0]范围将映射到相应整数格式的最大范围
|
||
- 较低位深度到较高位深度转换:保留原有精度,向上扩展
|
||
- 较高位深度到较低位深度转换:会损失精度,可能引入截断和量化错误
|
||
- 重采样操作会改变缓冲区大小,但声道数和格式保持不变
|
||
|
||
### 混音和增益
|
||
|
||
```cpp
|
||
// 应用增益(音量调整)
|
||
buffer.apply_gain(0.5f); // 将音量降低到50%
|
||
|
||
// 混合两个缓冲区
|
||
dest_buffer.mix_from(src_buffer, 0.7f); // 以70%的音量混合src_buffer到dest_buffer
|
||
```
|
||
|
||
**混音算法:**
|
||
|
||
混音操作对每个样本执行以下计算:
|
||
```
|
||
dest[i] = dest[i] + (src[i] * gain)
|
||
```
|
||
|
||
对于FLOAT32格式,直接相加可能导致值超出[-1.0, 1.0]范围。AudioBuffer实现了两种处理策略:
|
||
|
||
1. **限幅 (Clipping)**:将超出范围的值限制在有效范围内
|
||
2. **软限幅 (Soft Clipping)**:应用非线性曲线,在接近极限值时逐渐压缩动态范围
|
||
|
||
## 环形缓冲区
|
||
|
||
### RingBuffer类
|
||
|
||
`RingBuffer`是一个模板类,用于实时音频流处理,提供线程安全的生产者-消费者模型。
|
||
|
||
```cpp
|
||
template<typename T>
|
||
class RingBuffer {
|
||
public:
|
||
// 构造函数
|
||
explicit RingBuffer(size_t capacity = 0);
|
||
|
||
// 重新分配容量
|
||
void resize(size_t new_capacity);
|
||
|
||
// 写入数据
|
||
size_t write(const T* data, size_t count);
|
||
|
||
// 读取数据
|
||
size_t read(T* data, size_t count);
|
||
|
||
// 清空缓冲区
|
||
void clear();
|
||
|
||
// 获取可用数据量
|
||
size_t available() const;
|
||
|
||
// 获取可写空间
|
||
size_t space() const;
|
||
|
||
// 获取容量
|
||
size_t capacity() const;
|
||
|
||
// 检查是否为空
|
||
bool empty() const;
|
||
|
||
// 检查是否已满
|
||
bool full() const;
|
||
};
|
||
```
|
||
|
||
RingBuffer适用于:
|
||
- 音频捕获和播放之间的缓冲
|
||
- 网络音频流的抖动缓冲
|
||
- 音频处理线程和UI线程之间的数据传输
|
||
- 多速率系统中的采样率转换缓冲
|
||
|
||
### 线程安全性
|
||
|
||
RingBuffer实现了完整的线程安全保障:
|
||
|
||
- 使用`std::mutex`保护关键部分
|
||
- 使用`std::atomic`变量跟踪读写位置和可用数据量
|
||
- 内存顺序(memory ordering)确保多线程正确性
|
||
|
||
**并发模型:**
|
||
|
||
- 一个线程作为生产者(写入数据)
|
||
- 一个线程作为消费者(读取数据)
|
||
- 生产者和消费者可以独立运行,不需要额外同步
|
||
|
||
```cpp
|
||
// 生产者线程代码
|
||
void producer_thread(RingBuffer<float>& buffer, const AudioSource& source) {
|
||
float temp_buffer[256];
|
||
while (running) {
|
||
size_t samples = source.read(temp_buffer, 256);
|
||
size_t written = buffer.write(temp_buffer, samples);
|
||
if (written < samples) {
|
||
// 缓冲区已满,处理溢出情况
|
||
log_overflow(samples - written);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 消费者线程代码
|
||
void consumer_thread(RingBuffer<float>& buffer, AudioOutput& output) {
|
||
float temp_buffer[256];
|
||
while (running) {
|
||
size_t available = buffer.available();
|
||
if (available >= 256) {
|
||
buffer.read(temp_buffer, 256);
|
||
output.play(temp_buffer, 256);
|
||
} else {
|
||
// 缓冲区数据不足,处理饥饿情况
|
||
log_underflow(256 - available);
|
||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 实现示例
|
||
|
||
### 基本音频处理
|
||
|
||
以下示例展示了基本的音频处理流程:
|
||
|
||
```cpp
|
||
#include "audio_backend/engine.h"
|
||
|
||
using namespace audio_backend::engine;
|
||
|
||
// 基本音频处理流程
|
||
void process_audio_file(const std::string& input_file, const std::string& output_file) {
|
||
// 创建音频配置
|
||
AudioConfig config;
|
||
config.sample_rate = 48000;
|
||
config.channels = 2;
|
||
config.format = AudioFormat::FLOAT32;
|
||
config.frames_per_buffer = 1024;
|
||
|
||
// 加载音频文件到缓冲区(仅用于示例,实际实现需要文件I/O)
|
||
AudioBuffer input_buffer(config);
|
||
load_audio_file(input_file, input_buffer);
|
||
|
||
// 创建处理缓冲区
|
||
AudioBuffer output_buffer(config);
|
||
|
||
// 应用音频处理
|
||
input_buffer.copy_to(output_buffer); // 首先复制原始数据
|
||
output_buffer.apply_gain(0.8f); // 将音量降低到80%
|
||
|
||
// 添加简单的回声效果
|
||
AudioBuffer delay_buffer = input_buffer.clone();
|
||
delay_buffer.apply_gain(0.4f); // 回声音量为40%
|
||
output_buffer.mix_from(delay_buffer); // 混合回声
|
||
|
||
// 保存处理后的音频(仅用于示例,实际实现需要文件I/O)
|
||
save_audio_file(output_file, output_buffer);
|
||
}
|
||
|
||
// 示例辅助函数(实际实现需要替换为真实的文件I/O代码)
|
||
void load_audio_file(const std::string& filename, AudioBuffer& buffer) {
|
||
// 此处应为实际的文件加载代码
|
||
buffer.clear(); // 先清空缓冲区
|
||
|
||
// 填充测试数据(正弦波)
|
||
float* data = buffer.interleaved_data<float>();
|
||
const float frequency = 440.0f; // A4音符频率
|
||
const float sample_rate = buffer.sample_rate();
|
||
|
||
for (uint32_t i = 0; i < buffer.frames(); ++i) {
|
||
float sample = std::sin(2.0f * M_PI * frequency * i / sample_rate);
|
||
|
||
// 对于立体声,填充两个声道
|
||
for (uint16_t ch = 0; ch < buffer.channels(); ++ch) {
|
||
data[i * buffer.channels() + ch] = sample;
|
||
}
|
||
}
|
||
}
|
||
|
||
void save_audio_file(const std::string& filename, const AudioBuffer& buffer) {
|
||
// 此处应为实际的文件保存代码
|
||
std::cout << "Saving audio to: " << filename << std::endl;
|
||
std::cout << " Sample rate: " << buffer.sample_rate() << " Hz" << std::endl;
|
||
std::cout << " Channels: " << buffer.channels() << std::endl;
|
||
std::cout << " Format: " << get_format_name(buffer.format()) << std::endl;
|
||
std::cout << " Frames: " << buffer.frames() << std::endl;
|
||
std::cout << " Duration: " << (buffer.frames() / (float)buffer.sample_rate()) << " seconds" << std::endl;
|
||
}
|
||
```
|
||
|
||
### 实时音频流处理
|
||
|
||
以下示例展示如何使用RingBuffer实现实时音频流处理:
|
||
|
||
```cpp
|
||
#include "audio_backend/engine.h"
|
||
#include <thread>
|
||
#include <atomic>
|
||
#include <chrono>
|
||
|
||
using namespace audio_backend::engine;
|
||
using namespace std::chrono_literals;
|
||
|
||
class AudioStreamer {
|
||
public:
|
||
AudioStreamer(uint32_t sample_rate, uint16_t channels, uint32_t buffer_ms = 100)
|
||
: config_(), ring_buffer_(0), running_(false) {
|
||
|
||
config_.sample_rate = sample_rate;
|
||
config_.channels = channels;
|
||
config_.format = AudioFormat::FLOAT32;
|
||
config_.frames_per_buffer = 512;
|
||
|
||
// 计算环形缓冲区大小(基于毫秒)
|
||
size_t buffer_samples = (sample_rate * channels * buffer_ms) / 1000;
|
||
ring_buffer_.resize(buffer_samples);
|
||
}
|
||
|
||
~AudioStreamer() {
|
||
stop();
|
||
}
|
||
|
||
void start() {
|
||
if (running_) return;
|
||
|
||
running_ = true;
|
||
producer_thread_ = std::thread(&AudioStreamer::producer_loop, this);
|
||
consumer_thread_ = std::thread(&AudioStreamer::consumer_loop, this);
|
||
}
|
||
|
||
void stop() {
|
||
if (!running_) return;
|
||
|
||
running_ = false;
|
||
if (producer_thread_.joinable()) producer_thread_.join();
|
||
if (consumer_thread_.joinable()) consumer_thread_.join();
|
||
}
|
||
|
||
private:
|
||
AudioConfig config_;
|
||
RingBuffer<float> ring_buffer_;
|
||
std::atomic<bool> running_;
|
||
std::thread producer_thread_;
|
||
std::thread consumer_thread_;
|
||
|
||
void producer_loop() {
|
||
// 创建用于生成音频的缓冲区
|
||
AudioBuffer source_buffer(config_);
|
||
float* source_data = source_buffer.interleaved_data<float>();
|
||
|
||
// 采样计数器(用于生成连续正弦波)
|
||
uint64_t sample_count = 0;
|
||
const float frequency = 440.0f; // A4音符频率
|
||
const float pi2 = 2.0f * 3.14159265358979323846f;
|
||
|
||
while (running_) {
|
||
// 生成音频数据(正弦波)
|
||
for (uint32_t i = 0; i < source_buffer.frames(); ++i) {
|
||
float t = static_cast<float>(sample_count++) / config_.sample_rate;
|
||
float sample = std::sin(pi2 * frequency * t);
|
||
|
||
// 对于立体声,填充两个声道
|
||
for (uint16_t ch = 0; ch < config_.channels; ++ch) {
|
||
source_data[i * config_.channels + ch] = sample;
|
||
}
|
||
}
|
||
|
||
// 写入环形缓冲区
|
||
size_t samples_to_write = source_buffer.frames() * config_.channels;
|
||
size_t written = ring_buffer_.write(source_data, samples_to_write);
|
||
|
||
if (written < samples_to_write) {
|
||
// 缓冲区溢出
|
||
std::cout << "Buffer overflow: dropped " << (samples_to_write - written) << " samples" << std::endl;
|
||
}
|
||
|
||
// 模拟生成音频的处理时间
|
||
std::this_thread::sleep_for(10ms);
|
||
}
|
||
}
|
||
|
||
void consumer_loop() {
|
||
// 创建用于播放音频的缓冲区
|
||
AudioBuffer output_buffer(config_);
|
||
float* output_data = output_buffer.interleaved_data<float>();
|
||
|
||
// 计算每个缓冲区的持续时间(毫秒)
|
||
float buffer_duration_ms = (config_.frames_per_buffer * 1000.0f) / config_.sample_rate;
|
||
|
||
while (running_) {
|
||
// 从环形缓冲区读取数据
|
||
size_t samples_to_read = output_buffer.frames() * config_.channels;
|
||
size_t read = ring_buffer_.read(output_data, samples_to_read);
|
||
|
||
if (read < samples_to_read) {
|
||
// 缓冲区下溢
|
||
std::cout << "Buffer underflow: missing " << (samples_to_read - read) << " samples" << std::endl;
|
||
|
||
// 用零填充剩余部分
|
||
memset(output_data + read, 0, (samples_to_read - read) * sizeof(float));
|
||
}
|
||
|
||
// 这里应该将数据发送到音频输出设备
|
||
// audio_device.play(output_data, samples_to_read);
|
||
|
||
// 模拟实际播放的时间
|
||
std::this_thread::sleep_for(std::chrono::duration<float, std::milli>(buffer_duration_ms));
|
||
}
|
||
}
|
||
};
|
||
|
||
// 使用示例
|
||
void run_audio_streaming() {
|
||
// 创建音频流处理器(48kHz立体声,100ms缓冲)
|
||
AudioStreamer streamer(48000, 2, 100);
|
||
|
||
// 启动处理
|
||
streamer.start();
|
||
|
||
// 运行5秒
|
||
std::this_thread::sleep_for(5s);
|
||
|
||
// 停止处理
|
||
streamer.stop();
|
||
}
|
||
```
|
||
|
||
### 多声道混音示例
|
||
|
||
以下示例演示如何使用AudioBuffer进行多声道混音处理:
|
||
|
||
```cpp
|
||
#include "audio_backend/engine.h"
|
||
#include <vector>
|
||
|
||
using namespace audio_backend::engine;
|
||
|
||
// 多声道混音处理器
|
||
class MultiChannelMixer {
|
||
public:
|
||
MultiChannelMixer(uint32_t sample_rate, uint32_t frames_per_buffer)
|
||
: sample_rate_(sample_rate),
|
||
frames_per_buffer_(frames_per_buffer),
|
||
output_config_(),
|
||
output_buffer_() {
|
||
|
||
// 配置输出缓冲区(立体声,32位浮点)
|
||
output_config_.sample_rate = sample_rate;
|
||
output_config_.channels = 2;
|
||
output_config_.format = AudioFormat::FLOAT32;
|
||
output_config_.frames_per_buffer = frames_per_buffer;
|
||
|
||
// 分配输出缓冲区
|
||
output_buffer_.allocate(output_config_, true); // 交错格式
|
||
}
|
||
|
||
// 添加输入源(返回源ID)
|
||
int add_source() {
|
||
AudioConfig source_config;
|
||
source_config.sample_rate = sample_rate_;
|
||
source_config.channels = 2; // 假设所有源都是立体声
|
||
source_config.format = AudioFormat::FLOAT32;
|
||
source_config.frames_per_buffer = frames_per_buffer_;
|
||
|
||
input_buffers_.emplace_back(source_config, true); // 交错格式
|
||
volumes_.push_back(1.0f); // 默认音量为100%
|
||
|
||
return static_cast<int>(input_buffers_.size() - 1);
|
||
}
|
||
|
||
// 设置源的音量
|
||
void set_source_volume(int source_id, float volume) {
|
||
if (source_id >= 0 && source_id < static_cast<int>(volumes_.size())) {
|
||
volumes_[source_id] = volume;
|
||
}
|
||
}
|
||
|
||
// 提供源数据(例如从文件或网络读取)
|
||
void provide_source_data(int source_id, const float* data, size_t samples) {
|
||
if (source_id >= 0 && source_id < static_cast<int>(input_buffers_.size())) {
|
||
// 确保没有超出缓冲区大小
|
||
size_t max_samples = input_buffers_[source_id].frames() * input_buffers_[source_id].channels();
|
||
size_t copy_samples = std::min(samples, max_samples);
|
||
|
||
// 复制数据到源缓冲区
|
||
float* dest = input_buffers_[source_id].interleaved_data<float>();
|
||
std::memcpy(dest, data, copy_samples * sizeof(float));
|
||
}
|
||
}
|
||
|
||
// 执行混音处理
|
||
const AudioBuffer& process() {
|
||
// 清空输出缓冲区
|
||
output_buffer_.clear();
|
||
|
||
// 混合所有输入源
|
||
for (size_t i = 0; i < input_buffers_.size(); ++i) {
|
||
// 使用各自的音量设置混合
|
||
output_buffer_.mix_from(input_buffers_[i], volumes_[i]);
|
||
}
|
||
|
||
return output_buffer_;
|
||
}
|
||
|
||
private:
|
||
uint32_t sample_rate_;
|
||
uint32_t frames_per_buffer_;
|
||
std::vector<AudioBuffer> input_buffers_; // 输入源缓冲区
|
||
std::vector<float> volumes_; // 每个源的音量
|
||
AudioConfig output_config_; // 输出配置
|
||
AudioBuffer output_buffer_; // 输出缓冲区
|
||
};
|
||
|
||
// 使用示例
|
||
void mix_audio_channels() {
|
||
// 创建混音器(48kHz,512帧缓冲)
|
||
MultiChannelMixer mixer(48000, 512);
|
||
|
||
// 添加两个音频源
|
||
int source1 = mixer.add_source();
|
||
int source2 = mixer.add_source();
|
||
|
||
// 设置音量
|
||
mixer.set_source_volume(source1, 0.7f); // 70%音量
|
||
mixer.set_source_volume(source2, 0.5f); // 50%音量
|
||
|
||
// 生成测试数据(这里只是示例)
|
||
std::vector<float> test_data1(1024, 0.0f); // 1024个样本
|
||
std::vector<float> test_data2(1024, 0.0f); // 1024个样本
|
||
|
||
// 源1:440Hz正弦波
|
||
float freq1 = 440.0f;
|
||
for (int i = 0; i < 512; ++i) { // 512帧
|
||
float sample = std::sin(2.0f * M_PI * freq1 * i / 48000.0f);
|
||
test_data1[i*2] = test_data1[i*2+1] = sample; // 立体声相同
|
||
}
|
||
|
||
// 源2:880Hz正弦波
|
||
float freq2 = 880.0f;
|
||
for (int i = 0; i < 512; ++i) { // 512帧
|
||
float sample = std::sin(2.0f * M_PI * freq2 * i / 48000.0f);
|
||
test_data2[i*2] = test_data2[i*2+1] = sample; // 立体声相同
|
||
}
|
||
|
||
// 提供数据
|
||
mixer.provide_source_data(source1, test_data1.data(), test_data1.size());
|
||
mixer.provide_source_data(source2, test_data2.data(), test_data2.size());
|
||
|
||
// 执行混音
|
||
const AudioBuffer& mixed = mixer.process();
|
||
|
||
// 在实际应用中,现在可以播放或处理mixed缓冲区
|
||
std::cout << "混音完成: " << mixed.frames() << " 帧,"
|
||
<< mixed.channels() << " 声道,"
|
||
<< "格式: " << get_format_name(mixed.format()) << std::endl;
|
||
}
|
||
```
|
||
|
||
## 性能优化策略
|
||
|
||
### SIMD优化
|
||
|
||
音频引擎对大多数处理操作都实现了SIMD优化版本,支持以下指令集:
|
||
|
||
- x86/x64: SSE2, SSE3, SSE4, AVX, AVX2, AVX-512
|
||
- ARM: NEON (32位和64位)
|
||
|
||
SIMD优化适用的操作:
|
||
- 格式转换(整数到浮点,浮点到整数)
|
||
- 混合和增益应用
|
||
- 声道转换(单声道到立体声,立体声到单声道)
|
||
- 各种数学运算(加、减、乘、平方根等)
|
||
|
||
关键SIMD函数示例:
|
||
```cpp
|
||
// 不同指令集的实现示例(仅示意)
|
||
namespace audio_backend::simd {
|
||
|
||
// SIMD版本选择标记
|
||
struct SSE2Tag {};
|
||
struct AVX2Tag {};
|
||
struct AVX512Tag {};
|
||
struct NEONTag {};
|
||
|
||
// 混音函数的SSE2实现
|
||
void mix_audio_f32_impl(const float* input1, const float* input2, float* output,
|
||
size_t samples, SSE2Tag) {
|
||
// 使用SSE2指令集实现
|
||
}
|
||
|
||
// 混音函数的AVX2实现
|
||
void mix_audio_f32_impl(const float* input1, const float* input2, float* output,
|
||
size_t samples, AVX2Tag) {
|
||
// 使用AVX2指令集实现
|
||
}
|
||
|
||
// 调度函数
|
||
void mix_audio_f32(const float* input1, const float* input2, float* output, size_t samples) {
|
||
// 根据当前支持的最高指令集动态选择实现
|
||
if (cpu_features.has_avx512) {
|
||
mix_audio_f32_impl(input1, input2, output, samples, AVX512Tag{});
|
||
} else if (cpu_features.has_avx2) {
|
||
mix_audio_f32_impl(input1, input2, output, samples, AVX2Tag{});
|
||
} else if (cpu_features.has_sse2) {
|
||
mix_audio_f32_impl(input1, input2, output, samples, SSE2Tag{});
|
||
} else {
|
||
mix_audio_f32_scalar(input1, input2, output, samples); // 回退到标量实现
|
||
}
|
||
}
|
||
|
||
} // namespace audio_backend::simd
|
||
```
|
||
|
||
### 内存对齐
|
||
|
||
AudioBuffer使用内存对齐技术以优化SIMD指令的性能:
|
||
|
||
```cpp
|
||
// 对齐的内存分配
|
||
template<typename T, size_t Alignment>
|
||
class AlignedBuffer {
|
||
public:
|
||
AlignedBuffer() = default;
|
||
|
||
explicit AlignedBuffer(size_t size) {
|
||
allocate(size);
|
||
}
|
||
|
||
void allocate(size_t size) {
|
||
// 分配对齐的内存
|
||
}
|
||
|
||
// ... 其他方法
|
||
};
|
||
|
||
// 在AudioBuffer中的使用
|
||
simd::AlignedBuffer<uint8_t, simd::ALIGNMENT_AVX> data_;
|
||
```
|
||
|
||
内存对齐的重要性:
|
||
- SIMD指令通常要求数据对齐到16字节(SSE)或32字节(AVX)边界
|
||
- 未对齐的内存访问可能导致性能下降或特定平台上的异常
|
||
- 更高级的指令集(如AVX-512)对对齐的要求更严格
|
||
|
||
### 零拷贝技术
|
||
|
||
系统使用多种技术最小化不必要的内存复制:
|
||
|
||
1. **移动语义**:使用C++移动构造函数和移动赋值运算符避免不必要的复制
|
||
|
||
```cpp
|
||
// 移动构造和赋值
|
||
AudioBuffer(AudioBuffer&& other) noexcept;
|
||
AudioBuffer& operator=(AudioBuffer&& other) noexcept;
|
||
```
|
||
|
||
2. **引用传递**:通过引用传递大型缓冲区,而不是通过值传递
|
||
|
||
```cpp
|
||
// 通过引用传递大型缓冲区
|
||
void process_audio(const AudioBuffer& input, AudioBuffer& output);
|
||
```
|
||
|
||
3. **原位处理**:尽可能在原地修改数据,避免创建临时缓冲区
|
||
|
||
```cpp
|
||
// 原位增益应用(不创建新缓冲区)
|
||
buffer.apply_gain(0.8f);
|
||
```
|
||
|
||
## 跨平台考虑
|
||
|
||
音频引擎设计为在所有主要平台(Windows、Linux、macOS)上以相同方式工作,但需注意以下平台特定差异:
|
||
|
||
### 内存对齐
|
||
|
||
不同平台的对齐要求可能不同:
|
||
- Windows: 通常使用`_aligned_malloc`和`_aligned_free`
|
||
- Linux/macOS: 通常使用`posix_memalign`或`aligned_alloc`
|
||
|
||
### SIMD指令集
|
||
|
||
平台支持的SIMD指令集不同:
|
||
- Intel/AMD处理器:支持SSE/AVX指令集
|
||
- ARM处理器:支持NEON指令集
|
||
- 特定指令可能在不同CPU代之间有可用性差异
|
||
|
||
### 线程模型
|
||
|
||
线程优先级和实时音频策略在不同平台实现方式不同:
|
||
- Windows:使用`SetThreadPriority`
|
||
- Linux:使用SCHED_FIFO或SCHED_RR策略
|
||
- macOS:使用`thread_policy_set`
|
||
|
||
### 字节序
|
||
|
||
在处理原始音频数据时需考虑字节序(尤其是整数格式):
|
||
- 大多数音频格式要求特定字节序(通常为小端序)
|
||
- 跨平台时需确保一致的字节序处理 |