记录 SoundTouch的配置与代码分析

背景

最近做一个基于Qt + FFmpeg实时音视频推流的项目,利用OpenCV实时美颜已经OK。计划用SoundTouch再做变声处理,本来希望男声变女声,大叔变萝莉、单车变摩托的效果,但是多次测试后梦醒了,基本不太可能,只能实现基本的变声,这种应该需要比较专业的算法处理在里面。
但不可否认SoundTouch是个很好的音频处理库,实现基本的变调、变速效果还是不错的,就不勉强更理想的效果了,毕竟定位就不同,常见的音频处理需求SoundTouch可以胜任。

说明

现在看SoundTouch的使用还是比较简单的,但还是摸索一两天才玩透彻。网上的资料在搜索的时候感觉并不多,而且好多是Android相关的。并且还受到了不少误导,因此记录一下,也希望需要的人少走弯路有所帮助。

库的编译

官网很好找,直接下载的库看到有动态库dll,直接使用报错,应该是有问题,最好自己编译静态库
方式:
下载最新源码,结构如下图,使用CMake进行编译,然后如VS2017 IDE,再打开SoundTouch文件夹,一般需要重定解决方案,选中项目右键点击生成,默认配置已经设置好,即可得到静态库。比较简单,不具体叙述。
注意:
直接用VS2017编译生成的静态库使用中存在链接报错,可能我的编译选项设置有问题。建议用CMake先执行下再编译生成就正常了,仍有问题可留言。

源码

库的使用

三方库的配置都是类似的,可以移步另一篇讲库的配置的文章,重头戏是代码。
网上好不容易找到SoundTouch相关的文章有的是互相照抄的,有的不知道是搬运的哪的代码,缺头少尾不知道变量从哪来的, 什么作用,就找到两三篇比较合适的文章。还是转头去找SoundTouch本身提供的例子(官方的最权威,其它库也一样),是一个把变速、变调等功能做成命令行程序的源码,此源码是对输入的Wav音频文件做变速、变调处理后再输出为一个新的Wav文件。不断地查看源码算是理清的头绪,其实就是把一个个采样点的音频数据,送到SoundTouch里进行处理,然后处理完返回一定个数的采样点数据,返回得到的数据就是变速、变调处理后的数据,就可以进行其它操作。库内部的处理算法复杂没看太懂,不过不影响使用。
下面是梳理清楚后写的一份简洁的代码:文件中读取PCM数据处理后输出一个新的PCM文件,目的是表现出最核心的逻辑,先不迷失在其它的一些细节上。理清逻辑后完全可以从内存里取数据,处理后再写到内存,或者其它复杂的处理,逻辑都是一样的。注释很详细,阅读代码即可。

   
   //源码默认6720 改动为最大4*2*1024 (32位 2通道 1024帧大小)
	#define MAX_BUFF_SIZE 8192

	//打开文件
    QString inPcmFileName = "48000_2_s16le.pcm";
    QFile inPcmFile(inPcmFileName);
    if(!inPcmFile.open(QIODevice::ReadOnly))
    {
    
    
        qDebug() << "read inPcmFile error";
        return;
    }

    QString outPcmFileName = "outPcm.pcm";
    QFile outPcmFile(outPcmFileName);
    outPcmFile.open(QIODevice::WriteOnly);

    //音频数据参数
    int sampleRate = 48000;
    int channel = 2;
    int bytePerSample = 16 / 8;

    //音频数据缓存 读取一帧原始音频数据(可修改)
    int inPcmSize = channel * bytePerSample * 1024;
    uint8_t *inPcmBuf = new uint8_t[inPcmSize];

    //soundtouch数据缓存 格式需要为SAMPLETYPE(short)
    SAMPLETYPE touchBuffer[MAX_BUFF_SIZE];

    //soundTouch设置
    soundtouch::SoundTouch soundTouch;
    soundTouch.setSampleRate(sampleRate); // 设置采样率
    soundTouch.setChannels(channel);      // 设置通道数
    soundTouch.setTempo(1.0);      //变速
    soundTouch.setPitch(0.8);      //变调

    //接收处理后sample个数的最大值
    int maxRecv = MAX_BUFF_SIZE / channel / bytePerSample;
    //sample个数
    int nSamples = 0;

    //循环读取文件信息
    while(1)
    {
    
    
        int readLen = inPcmFile.read((char *)inPcmBuf, inPcmSize);
        if(readLen <= 0)
            break;

        //char*类型读取的内容进行转换 uint8转short16
        for (int i=0; i<readLen/2; i++)
        {
    
    
            touchBuffer[i] = (inPcmBuf[i * 2] | (inPcmBuf[i * 2 + 1] << 8));
        }

        //计算此次读取数据中包含的sample个数
        nSamples = readLen / channel / bytePerSample;

        //将nSamples个数的样本写入soundTouch进行处理 注:需要数据缓冲,可能putSamples多次,才能receiveSamples收到处理后的音频数据
        soundTouch.putSamples(touchBuffer, nSamples);

        //循环接收处理后的音频数据 因为putSamples与receiveSamples的个数不一定对等,存在多次才接收完的情况
        while(1)
        {
    
    
            //返回处理好的样本个数 每次接收个数不会超过最大值maxRecv 防止一次接收的数据量过大
            nSamples = soundTouch.receiveSamples(touchBuffer, maxRecv);
            if(nSamples == 0)
                break;

            //根据样本数,计算处理后的数据长度
            int length = nSamples * channel * bytePerSample;
            outPcmFile.write((char*)touchBuffer, length);
        }
    }

    //存在剩余的处理好的数据,冲刷一下
    soundTouch.flush();
    while(1)
    {
    
    
        //接收处理后的数据,步骤与上相同
        nSamples = soundTouch.receiveSamples(touchBuffer, maxRecv);
        if(nSamples == 0)
            break;

        int length = nSamples * channel * bytePerSample;
        outPcmFile.write((char*)touchBuffer, length);
    }

    qDebug() << "Finish";

    //释放资源
    inPcmFile.close();
    outPcmFile.close();
    delete[] inPcmBuf;
    inPcmBuf = nullptr;
    

猜你喜欢

转载自blog.csdn.net/T__zxt/article/details/127109646