引言
工作需要将一份 双声道的PCM数据转换成单声道数据,我采用的是将左右声道样点值对应相加求平均样点值的办法。
计算式如下:
typedef int s32;
typedef unsigned int u32;
typedef short s16;
typedef unsigned short u16;
/*
* 双声道转换成单声道:48khz-16bit-stereo ---> 8khz-16bit-mono
* 源数据缓冲:pSrcBuf
* 源数据长度:dwSrcLen
* 目标数据缓冲:pDstBuf
* 下面的样点必须使用 s16 表示,禁止使用 u16表示,因为u16会导致计算出错,从而产生大噪声问题!
*/
u32 dwSampleNum = dwSrcLen >> 2; //计算单声道样点数
u16* pwSrc = (u16*)pSrcBuf; //双声道数据缓冲
u16* pwDst = (u16*)pDstBuf; //单声道数据缓冲
for (u32 dwIdx = 0 ; dwIdx < dwSampleNum; dwIdx++)
{
pwDst[dwIdx] = (pwSrc[2*dwIdx] + pwSrc[2*dwIdx + 1]) / 2; //左右声道求均值
}
问题
实际测试发现,上述代码处理后的PCM数据含有大量的大噪声!
用 Adobe Audition 打开处理后的单声道数据:
由图可知,必是大噪声。
将上图局部放大
继续放大
由图分析:正常的声音波形是在样点值接近0的时候发生突变的,变成极大值或极小值。
查看这些突变样点值大小
综上分析,正常的声音波形是在样点值接近0的时候发生突变的,变成极大值或极小值(即:绝对值接近 32768 = 2^15)
分析
我们在上面计算左右声道样点值的均值时,是用 u16 来表示样点值的,这种表示方法在计算均值时出问题了!
一般左右声道的对应样点值是比较接近的,可能出现下面的两种情况:
例如:左声道样点值 = -1 ,右声道样点值 = 1 时
(-1 + 1)/2
= (0000 0000 0000 0000 1111 1111 1111 1111 + 0000 0000 0000 0000 0000 0000 0000 0001) /2 //u16 整型提升为 s32
= (0000 0000 0000 0001 0000 0000 0000 0000) /2 //加法进位
= (0000 0000 0000 0000 1000 0000 0000 0000) //除2相当于右移1位
= (1000 0000 0000 0000) //截断操作:将计算结果赋值给 目标样点值s16
= -32768 //(1000 0000 0000 0000)就是-32768的补码
例如:左声道样点值 = -2 ,右声道样点值 = 1 时
(-2 + 1)/2
= (0000 0000 0000 0000 1111 1111 1111 1110 + 0000 0000 0000 0000 0000 0000 0000 0001) /2 //u16 整型提升为 s32
= (0000 0000 0000 0000 1111 1111 1111 1111) /2 //加法进位
= (0000 0000 0000 0000 0111 1111 1111 1111) //除2相当于右移1位
= (0111 1111 1111 1111) //截断操作:将计算结果赋值给 目标样点值s16
= 32767 //(0111 1111 1111 1111)就是32767的补码
其实,类似的左右样点组合还有很多,都是在0值附近,表现在波形中就是:
正常的声音波形在样点值接近0的时候发生突变的,变成极大值或极小值。
上述分析与问题现象一致,所以可以确定问题原因就是:
因为用 u16 来表示样点值的,导致在计算均值时出错!
解决方案
在计算左右声道的对应样点均值时,样点必须使用 s16 表示,禁止使用 u16表示,因为u16会导致计算出错,从而产生大噪声问题!
/*
* 双声道转换成单声道:48khz-16bit-stereo ---> 8khz-16bit-mono
* 源数据缓冲:pSrcBuf
* 源数据长度:dwSrcLen
* 目标数据缓冲:pDstBuf
* 下面的样点必须使用 s16 表示,禁止使用 u16表示,因为u16会导致计算出错,从而产生大噪声问题!
*/
u32 dwSampleNum = dwSrcLen >> 2; //计算单声道样点数
s16* pwSrc = (s16*)pSrcBuf; //双声道数据缓冲
s16* pwDst = (s16*)pDstBuf; //单声道数据缓冲
for (u32 dwIdx = 0 ; dwIdx < dwSampleNum; dwIdx++)
{
pwDst[dwIdx] = (pwSrc[2*dwIdx] + pwSrc[2*dwIdx + 1]) / 2; //左右声道求均值
}