Android音视频开发(三):变声

简介

前面两章我们介绍了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进行播放,对于updown参数的配置,可以自己调,我将up设为4,down设为5,我的声音就变得低沉大叔的声音,更多的参数你可以自己去测试

注:如果设置的参数过大可能会出现异常,比如50,可能是算法存在问题,还需改进。

变声的简单介绍,希望大家喜欢。

你可能感兴趣的:(Android音视频开发(三):变声)