音频处理的常见操作

最近刚开始接触音频处理这块(之前相关经验仅限于 speex 压缩、pcm 转 wav 等),这里简单做下记录。

调整音量

我们知道,声音是波,而音量对应声波的振幅。

所以直接调整音频数据的数值(通常是乘以一个系数)就能改变音量:

for (int i = 0; i < len/2; i++) {
    SInt16 value  = (buf[2*i+1] << 8 | (buf[2*i] & 0xFF));
    buf[2*i] = value;
    buf[2*i+1] = value >> 8;
}

防止爆音

但是实际调试中发现会出现爆音,一般的解决方式是限制下值的范围:

#define LIMIT_AUDIO(a)  ((a)<(-32768.0f) ? (-32768.0f) : ((a)>(32767.0f) ? (32767.0f) : (a)))

为什么要这么做呢?

因为通常音频采样的位宽是 16 bits,对应的二进制范围就是 [-32768, 32767]。

混音

同样是类比物理中波的概念,每路声音对应一个波形,声音叠加(混音)就是波的叠加:

for (int i = 0; i < len/2; i++) {
    SInt16 input1Value  = (input1[2*i + 1] << 8 | (input1[2*i] & 0xFF));
    SInt16 input2Value  = (input2[2*i + 1] << 8 | (input2[2*i] & 0xFF));
    output[2*i] = outputValue;
    output[2*i+1] = (outputValue >> 8);
}

大小端转换

在做内录混音的时候,发现最终总是听不到内录的声音。

请教了下组里的老司机,说可能是大小端问题。

那什么是大小端呢?

  • 大端,常用于网络传输(重要的头部数据放最前面);

  • 小端,常用于 CPU 处理内存数据。

可以看到,其实就是低位高位顺序的不同,所以先读取后面的即可实现大小端转换:

Byte *byteArray = (Byte*)[data bytes];
NSMutableData *audioData = [NSMutableData new];
for (int i = 0; i < [data length] / 2; i++) {
    [audioData appendBytes: &byteArray[i*2+1] length:1];
    [audioData appendBytes: &byteArray[i*2] length:1];
}

提取单声道

由于左右声道数据是相间的,所以交叉读写即可:

FILE *fp = fopen(url, "rb+");
FILE *fp1 = fopen("output_l.pcm", "wb+");
FILE *fp2 = fopen("output_r.pcm", "wb+");
unsigned char *sample = (unsigned char *)malloc(4);
while(!feof(fp)) {
    fread(sample, 1, 4, fp); 
    fwrite(sample, 1, 2, fp1); //左声道
    fwrite(sample+2, 1, 2, fp2); //右声道
}

参考: