PCM音频编码 & 声道

350 阅读5分钟

基于Android P版本分析

PCM音频编码

PCM即脉冲编码调制。在PCM过程中,将输入的模拟信号进行采样、量化和编码,用二进制进行编码的数来代表模拟信号的幅度;接收端再将这些编码还原为原来的模拟信号。

记数字音频的A/D转换(模拟信号转换为数字信号的过程叫做模数转换)过程包含三个过程:采样、量化和编码;

模数转换

模数转换.png

PCM编码原理

PCM编码原理.jpg

PCM 采样位深

采样位深就是采样值的二进制编码的位数;

采样位深反应了采样系统对声音的辨析度,针对相同的信号,位深越大,对声音的记录就越精细,所以一般也称之为采样精度;

采样位深的含义是用多少个点来描述声音信号的强度,在上述的PCM编码图中,对应的PCM位深为3bit,使用了3个0或1的二进制编码进行的表示,2^3 = 8个点,而我们一般常见的8bit、16bit就是2^8 = 256、2^16 = 65536个点;

位深采样振幅值(点)动态范围
8 bit25648.16dB
16 bit6553696.33dB
24 bit16777216144.49dB
32 bit4294967296192.66dB

dB:分贝的含义;

采样率针对的是相同时间内的波频率,采样位深针对的是相同信号的振幅强度

PCM 声道

当人听到声音时,能对声源进行定位,那么通过在不同的位置设置声源,就可以造就出更好的听觉感受,如果配合影像进行音频位置的调整,则会得到更好的视听效果。常见的声道有:

  • 单声道,mono;
  • 双声道,stereo,最常见的类型,包含左声道以及右声道;
  • 2.1声道,在双声道基础上加入一个低音声道;
  • 5.1声道,包含一个正面声道、左前方声道、右前方声道、左环绕声道、右环绕声道、一个低音声道,最早应用于早期的电影院;
  • 7.1声道,在5.1声道的基础上,把左右的环绕声道拆分为左右环绕声道以及左右后置声道,主要应用于BD以及现代的电影院;

我们基于单声道和双声道进行分析;

声道结构

声道结构.png

  • 针对双声道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);
声道解析

声道解析.png

上述测试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;
  }
}