简介
前面两章我们介绍了PCM音频格式的录制和播放,分别是使用AudioRecord
录制,使用AudioTrack
播放,其实得到了PCM格式的音频,我们并不能随意在播放器中播放,因为PCM格式的音频,播放器还不能识别,需要编码封装成mp3或者wav等格式才能播放,但是今天我们暂时不讨论如何进行PCM编码,先来对PCM进行一些处理,比如变声,添加BGM等,今天我们的主题是变声
变声原理
对于变声的处理一般有以下三种
- 变速又变调
即改变音频的速度(语速),又改变音频的频率(音调)
我们可以对原始音频进行重采样,重采样有上采样和下采样,分别是对原始音频进行插值和抽取,比如P/Q重采样,一般我们的处理是,先对原始音频进行插值,在相邻两点间插入P个采样点,全部插入结束后再每隔Q个采样点进行采样,这样得到的音频语速和音调都是原来的Q/P倍
- 变速不变调
只改变语速,不改变音调
只是改变语速的话,那么就要稍微复杂点,和重采样方法差不多,区别在于我们需要先规定一帧音频的长度,一般我们的采样率设为44.1KHz,也就是一秒钟采样44.1K次,我们可以规定一帧为1024,所有我们可以简单的根据丢帧和重复帧来实现变速不变调,比如对于P/Q变速,我们先对原始音频的每一帧重复P次,最后的结果再进行每隔Q帧取一帧,这样就得到音频音调不变,语速变为Q/P的音频
- 变调不变速
只改变音调,不改变语速
如果只是变调的话,就要结合重采样和变速不变调来做,我们先对音频信号进行变速不变调处理,再对其进行重采样,比如,我想要让音调变为原来的P/Q倍,那么我们需要先对其进行P/Q变速不变调,语速变为原来的Q/P,接着,在对其进行Q/P重采样,这样,最后就得到了语速不变,而音调变为原来的P/Q倍
当然,还有很多对声音的处理,比如一些K歌软件,可以实现KTV、空灵、磁性等效果,那些效果就比较复杂,不在今天的讨论范围内,我们暂时只讨论简单的变声
代码实现(Java)
我们这样是使用纯Java代码实现,其实这样是存在效率问题的,如果音频较大,还是得用C语言(使用JNI和NDK去实现),对于一些特殊情况,算法可能存在问题,仅供参考
- 帧长
private static final int FRAME_LENGTH = 1024;
- 变调又变速(提高)
//变调又变速(提高)
public static byte[] up(byte[] data, int up) {
if (up == 1) {
return data;
}
int length = data.length;
int upLength = length / up;
byte[] upData = new byte[upLength];
for (int i = 0, j = 0; i < length; ) {
if (j >= upLength) {
break;
}
upData[j] = data[i];
i += up;
j++;
}
return upData;
}
- 变调又变速(降低)
public static byte[] down(byte[] data, int down) {
if (down == 1) {
return data;
}
int length = data.length;
int downLength = length * down;
byte[] downData = new byte[downLength];
for (int i = 0, j = 0; i < length - 1; ) {
for (int k = 0; k < down; k++) {
downData[j] = data[i];
j++;
}
i++;
}
return downData;
}
- 变速不变调(提高)
public static byte[] speedUp(byte[] data, int up) {
if (up == 1) {
return data;
}
int length = data.length;
int frameShift = FRAME_LENGTH * up;
int upLength = length / up;
byte[] upData = new byte[upLength];
for (int i = 0, j = 0; i < length; ) {
if (i + FRAME_LENGTH >= length) {
System.arraycopy(data, i, upData, j, length - i);
break;
}
System.arraycopy(data, i, upData, j, FRAME_LENGTH);
i += (FRAME_LENGTH + frameShift);
j += FRAME_LENGTH;
}
return upData;
}
- 变速不变调(降低)
public static byte[] speedDown(byte[] data, int down) {
if (down == 1) {
return data;
}
int length = data.length;
int downLength = length * down;
byte[] downData = new byte[downLength];
for (int i = 0, j = 0; i < length; ) {
if (i + FRAME_LENGTH >= length) {
int lastlength = length - i;
for (int k = 0; k < down; k++) {
System.arraycopy(data, lastlength, downData, j, lastlength);
j += lastlength;
}
break;
}
for (int k = 0; k < down; k++) {
System.arraycopy(data, i, downData, j, FRAME_LENGTH);
j += FRAME_LENGTH;
}
i += FRAME_LENGTH;
}
return downData;
}
- 设置语速
public static byte[] setSpeed(byte[] data, int up, int down) {
byte[] downData = speedDown(data, down);
byte[] upData = speedUp(downData, up);
return upData;
}
- 设置音调
public static byte[] setTone(byte[] data, int up, int down) {
byte[] speedData = setSpeed(data, down, up);
byte[] downData = down(speedData, down);
byte[] upData = up(downData, up);
return upData;
}
总结
我们在使用AudioRecord录音结束后,可以调用以上函数进行处理,然后再使用AudioTrack进行播放,对于up和down参数的配置,可以自己调,我将up设为4,down设为5,我的声音就变得低沉大叔的声音,更多的参数你可以自己去测试
注:如果设置的参数过大可能会出现异常,比如50,可能是算法存在问题,还需改进。
变声的简单介绍,希望大家喜欢。
网友评论