基于Android P版本分析
PCM音频编码
PCM即脉冲编码调制。在PCM过程中,将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度;接收端再将这些编码还原为原来的模拟信号。
记数字音频的A/D转换(模拟信号转换为数字信号的过程叫做模数转换)过程包含三个过程:采样、量化和编码;
模数转换
PCM编码原理
PCM 采样位深
采样位深就是采样值的二进制编码的位数;
采样位深反应了采样系统对声音的辨析度,针对相同的信号,位深越大,对声音的记录就越精细,所以一般也称之为采样精度;
采样位深的含义是用多少个点来描述声音信号的强度,在上述的PCM编码图中,对应的PCM位深为3bit,使用了3个0或1的二进制编码进行的表示,2^3 = 8个点,而我们一般常见的8bit、16bit就是2^8 = 256、2^16 = 65536个点;
位深 | 采样振幅值(点) | 动态范围 |
---|---|---|
8 bit | 256 | 48.16dB |
16 bit | 65536 | 96.33dB |
24 bit | 16777216 | 144.49dB |
32 bit | 4294967296 | 192.66dB |
dB:分贝的含义;
采样率针对的是相同时间内的波频率,采样位深针对的是相同信号的振幅强度;
PCM 声道
当人听到声音时,能对声源进行定位,那么通过在不同的位置设置声源,就可以造就出更好的听觉感受,如果配合影像进行音频位置的调整,则会得到更好的视听效果。常见的声道有:
- 单声道,mono;
- 双声道,stereo,最常见的类型,包含左声道以及右声道;
- 2.1声道,在双声道基础上加入一个低音声道;
- 5.1声道,包含一个正面声道、左前方声道、右前方声道、左环绕声道、右环绕声道、一个低音声道,最早应用于早期的电影院;
- 7.1声道,在5.1声道的基础上,把左右的环绕声道拆分为左右环绕声道以及左右后置声道,主要应用于BD以及现代的电影院;
我们基于单声道和双声道进行分析;
声道结构
- 针对双声道PCM,采样数据按照时间先后顺序交叉存入,即一左声道数据,一右声道数据;
- 针对16 bit 声道数据,采样数据高八位代表左声道,低八位代表右声道,这里需要区分高/低八位和高/低字节的概念;
在Android源码中,在AudioFormat中定义了采样位深:
/** Audio data format: PCM 16 bit per sample. Guaranteed to be supported by devices. */
public static final int ENCODING_PCM_16BIT = 2;
/** Audio data format: PCM 8 bit per sample. Not guaranteed to be supported by devices. */
public static final int ENCODING_PCM_8BIT = 3;
单声道和双声道标识:
public static final int CHANNEL_IN_LEFT = 0x4;
public static final int CHANNEL_IN_RIGHT = 0x8;
public static final int CHANNEL_IN_FRONT = 0x10;
public static final int CHANNEL_IN_MONO = CHANNEL_IN_FRONT;
public static final int CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT);
声道解析
上述测试mp3文件为32 bit位深,24KHz,双声道;
声道转换
使用webrtc模块的实例说明(android/external/webrtc/webrtc/modules/utility/source/audio_frame_operations.cc);
单声道 to 双声道
单声道转换为双声道基本原理:将单声道的数据拷贝一份复制到新创建的声道中;
int AudioFrameOperations::MonoToStereo(AudioFrame* frame) {
// 首先先判断AudioFrame帧是否为单channel,是则可以继续执行,不是的话,则代表不是单声道的数据,则不知道转换操作
if (frame->num_channels_ != 1) {
return -1;
}
// 判断单个声道的采样数 * 2是否大于AudioFrame支持的最大采样数范围
if ((frame->samples_per_channel_ * 2) >= AudioFrame::kMaxDataSizeSamples) {
// Not enough memory to expand from mono to stereo.
return -1;
}
// 根据AudioFrame::kMaxDataSizeSamples创建新的承载临时数据的数组
int16_t data_copy[AudioFrame::kMaxDataSizeSamples];
// 复制内存块
memcpy(data_copy, frame->data_,
sizeof(int16_t) * frame->samples_per_channel_);
// 单声道变双声道
MonoToStereo(data_copy, frame->samples_per_channel_, frame->data_);
frame->num_channels_ = 2;
return 0;
}
/**
* src_audio:临时承载单声道源数据的数组
* samples_per_channel:源数据单声道采样数
* dst_audio:源数据承载数据的数组
*/
void AudioFrameOperations::MonoToStereo(const int16_t* src_audio,
size_t samples_per_channel,
int16_t* dst_audio) {
for (size_t i = 0; i < samples_per_channel; i++) {
// 交叉赋值,因为双声道是左声道、右声道为一组样本交叉顺序保存
dst_audio[2 * i] = src_audio[i];
dst_audio[2 * i + 1] = src_audio[i];
}
}
双声道 to 单声道
双声道转换为单声道的原理,存在两个思路:
- 直接丢弃一路数据,将剩余的一路数据专为单声道数据;
- 两路数据相加的平均值;
int AudioFrameOperations::StereoToMono(AudioFrame* frame) {
// 同样的,先判断frame数据的声道数
if (frame->num_channels_ != 2) {
return -1;
}
StereoToMono(frame->data_, frame->samples_per_channel_, frame->data_);
frame->num_channels_ = 1;
return 0;
}
丢弃一路
void AudioFrameOperations::StereoToMono(const int16_t* src_audio,
size_t samples_per_channel,
int16_t* dst_audio) {
for (size_t i = 0; i < samples_per_channel; i++) {
// 将左声道作为源数据
dst_audio[i] = src_audio[2 * i];
// 将右声道作为源数据
// dst_audio[i] = src_audio[2 * i + 1];
}
}
双声道合并
void AudioFrameOperations::StereoToMono(const int16_t* src_audio,
size_t samples_per_channel,
int16_t* dst_audio) {
for (size_t i = 0; i < samples_per_channel; i++) {
// 将左声道和右声道的数值相加,然后除以2,求得平均值;
dst_audio[i] = (src_audio[2 * i] + src_audio[2 * i + 1]) >> 1;
}
}