美文网首页
第十三节 利用SoundTouch实现变速、变调

第十三节 利用SoundTouch实现变速、变调

作者: 最美下雨天 | 来源:发表于2018-12-06 14:51 被阅读55次
    image.png image.png
    image.png
    image.png
    image.png

    我们将include和SoundTouch目录下的文件考入到工程


    image.png

    修改CMakeList.txt

    
    cmake_minimum_required(VERSION 3.4.1)
    
    #导入include路径
    include_directories(src/main/cpp/include)
    
    include_directories(src/main/cpp/soundtouch/include)
    include_directories(src/main/cpp/soundtouch/source)
    
    
    add_library( # Sets the name of the library.
                 native-lib
    
                 # Sets the library as a shared library.
                 SHARED
    
                 # Provides a relative path to your source file(s).
                 src/main/cpp/native-lib.cpp
                 src/main/cpp/CallBackJava.cpp
                 src/main/cpp/HFFmpeg.cpp
                 src/main/cpp/HAudio.cpp
                 src/main/cpp/HQueue.cpp
                 src/main/cpp/HPlayStatus.cpp
    
    
                 src/main/cpp/soundtouch/source/AAFilter.cpp
                 src/main/cpp/soundtouch/source/FIFOSampleBuffer.cpp
                 src/main/cpp/soundtouch/source/FIRFilter.cpp
                 src/main/cpp/soundtouch/source/cpu_detect_x86.cpp
                 src/main/cpp/soundtouch/source/sse_optimized.cpp
                 src/main/cpp/soundtouch/source/RateTransposer.cpp
                 src/main/cpp/soundtouch/source/SoundTouch.cpp
                 src/main/cpp/soundtouch/source/InterpolateCubic.cpp
                 src/main/cpp/soundtouch/source/InterpolateLinear.cpp
                 src/main/cpp/soundtouch/source/InterpolateShannon.cpp
                 src/main/cpp/soundtouch/source/TDStretch.cpp
                 src/main/cpp/soundtouch/source/PeakFinder.cpp
                 )
    
    
    find_library( # Sets the name of the path variable.
                  log-lib
    
                  # Specifies the name of the NDK library that
                  # you want CMake to locate.
                  log )
    
    
    #添加动态库
    add_library( avcodec-57 SHARED IMPORTED)
    #设置动态库路径
    set_target_properties( avcodec-57
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavcodec-57.so)
    
    
    add_library( avdevice-57 SHARED IMPORTED)
    set_target_properties( avdevice-57
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavdevice-57.so)
    
    
    add_library( avfilter-6 SHARED IMPORTED)
    set_target_properties( avfilter-6
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavfilter-6.so)
    
    
    add_library( avformat-57 SHARED IMPORTED)
    set_target_properties( avformat-57
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavformat-57.so)
    
    
    add_library( avutil-55 SHARED IMPORTED)
    set_target_properties( avutil-55
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libavutil-55.so)
    
    add_library( postproc-54 SHARED IMPORTED)
    set_target_properties( postproc-54
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libpostproc-54.so)
    
    add_library( swresample-2 SHARED IMPORTED)
    set_target_properties( swresample-2
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libswresample-2.so)
    
    
    add_library( swscale-4 SHARED IMPORTED)
    set_target_properties( swscale-4
                           PROPERTIES IMPORTED_LOCATION
                           ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libswscale-4.so)
    
    
    target_link_libraries( # Specifies the target library.
                           native-lib
    
                           avcodec-57
                           avdevice-57
                           avfilter-6
                           avformat-57
                           avutil-55
                           postproc-54
                           swresample-2
                           swscale-4
                           OpenSLES
    
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )
    
    /**
     * 设置音速
     * @param speed
     */
    void HAudio::setSpeed(jfloat speed) {
        this->speed=speed;
        if(soundTouch!=NULL)
        {
            soundTouch->setTempo(speed);
        }
    
    }
    
    /**
     * 设置音调
     * @param tonal
     */
    void HAudio::setTonal(jfloat tonal) {
        this->pitch=tonal;
        if(soundTouch!=NULL)
        {
            soundTouch->setPitch(tonal);
        }
    
    }
    
    int HAudio::getSoundTouchData() {
        while(hPlayStatus != NULL && !hPlayStatus->exit)
        {
            out_buffer = NULL;
            if(finished)
            {
                finished = false;
                data_size = resampleAudio((void **) &out_buffer);
                if(data_size > 0)
                {
                    for(int i = 0; i < data_size / 2 + 1; i++)
                    {
                        sampleBuffer[i] = (out_buffer[i * 2] | ((out_buffer[i * 2 + 1]) << 8));
                    }
                    soundTouch->putSamples(sampleBuffer, nb);
                    num = soundTouch->receiveSamples(sampleBuffer, nb);
                } else{
                    soundTouch->flush();
                }
            }
            if(num == 0)
            {
                finished = true;
                continue;
            } else{
                if(out_buffer == NULL)
                {
                    num = soundTouch->receiveSamples(sampleBuffer, nb);
                    if(num == 0)
                    {
                        finished = true;
                        continue;
                    }
                }
                return num;
            }
        }
        return 0;
    }
    

    OpenSL ES的播放回调方法中

    void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void * context)
    {
        HAudio *wlAudio = (HAudio *) context;
        if(wlAudio != NULL)
        {
            if(LOG_DEBUG)
            {
                LOGI("循环调用重采样");
            }
            int buffersize = wlAudio->getSoundTouchData();
    
            //int buffersize = wlAudio->resampleAudio((void **) &wlAudio->out_buffer);;
            if(buffersize > 0)
            {
                int out_channels=av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
                int totalBytes=buffersize*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
                wlAudio->clock+=totalBytes/(double)(wlAudio->sample_rate*2*2);
                //我们0.1s回调一次
                if(wlAudio->clock-wlAudio->last_time>0.1)
                {
                    wlAudio->last_time=wlAudio->clock;
                    //回调Java层显示时间
                    wlAudio->callBackJava->onShowTime(CHILD_THREAD,wlAudio->duration,wlAudio->clock);
    
                }
                //(* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> buffer, buffersize);
                //注意这儿的代码变化
                //(* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> sampleBuffer, buffersize*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
                (* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> sampleBuffer, totalBytes);
            }
        }
    }
    

    粘贴一下HAudio.cpp的完整代码

    //
    // Created by 霍振鹏 on 2018/10/19.
    //
    #include "HAudio.h"
    
    
    HAudio::HAudio(HPlayStatus *hPlayStatus,int sample_rate,CallBackJava *callBackJava) {
    
        this->hPlayStatus=hPlayStatus;
        this->hQueue=new HQueue(hPlayStatus);
        this->sample_rate=sample_rate;
        this->callBackJava=callBackJava;
        buffer = (uint8_t *) av_malloc(sample_rate * 2 * 2);
    
        //SoundTouch
        sampleBuffer= (SAMPLETYPE *) av_malloc(sample_rate * 2 * 2);
        soundTouch=new SoundTouch();
        soundTouch->setSampleRate(sample_rate);
        soundTouch->setChannels(2);
        soundTouch->setPitch(pitch);
        soundTouch->setTempo(speed);
    
    
    }
    
    HAudio::~HAudio() {
    
    }
    
    void *writeData(void *data)
    {
        HAudio *hAudio= (HAudio *) data;
        hAudio->initOpenSLES();
        pthread_exit(&hAudio->pthread);
    }
    void HAudio::play() {
    
        //创建一个线程,准备写入pcm数据
        pthread_create(&pthread,NULL,writeData,this);
    
    }
    //FILE *outFile=fopen("/storage/emulated/0/voiceplayer.pcm","w");
    int HAudio::resampleAudio(void **pcmbuf) {
        if(LOG_DEBUG)
        {
            LOGI("开始写入pcm数据");
        }
        while(hPlayStatus!=NULL&&!hPlayStatus->exit)
        {
            if(LOG_DEBUG)
            {
                LOGI("进入循环");
            }
    
            if(hPlayStatus->seek)
            {
                continue;
            }
            AVPacket *avPacket=av_packet_alloc();
            if(hQueue->getAvPacket(avPacket)!=0)
            {
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket=NULL;
                continue;
            } else
            {
    
            }
            //return 0 on success
            int ret=0;
            ret=avcodec_send_packet(avCodecContext,avPacket);
            if(ret!=0)
            {
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket=NULL;
                continue;
            }
            AVFrame *avFrame=av_frame_alloc();
            ret =avcodec_receive_frame(avCodecContext,avFrame);
            if(ret==0)
            {
                if(avFrame->channels>0&&avFrame->channel_layout==0)
                {
                    avFrame->channel_layout=av_get_default_channel_layout(avFrame->channels);
    
                }
                else if(avFrame->channels==0&&avFrame->channel_layout>0)
                {
                    avFrame->channel_layout=av_get_channel_layout_nb_channels(avFrame->channel_layout);
                }
    
                SwrContext *swrContext;
    
                swrContext=swr_alloc_set_opts(
                        NULL,
                        AV_CH_LAYOUT_STEREO,
                        AV_SAMPLE_FMT_S16,
                        avFrame->sample_rate,
                        avFrame->channel_layout,
                        (AVSampleFormat) avFrame->format,
                        avFrame->sample_rate,
                        NULL,NULL
                );
                if(!swrContext||swr_init(swrContext)<0)
                {
                    av_packet_free(&avPacket);
                    av_free(avPacket);
                    avPacket = NULL;
                    av_frame_free(&avFrame);
                    av_free(avFrame);
                    avFrame = NULL;
                    swr_free(&swrContext);
                    continue;
                }
    
                //return number of samples output per channel
                //返回每个通道输出的样本数,立体声就是2个通道
                nb=swr_convert(
                        swrContext,
                        &buffer,
                        avFrame->nb_samples,
                        (const uint8_t **) avFrame->data,
                        avFrame->nb_samples
                );
                int out_channels=av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
    
                //每个通道输出的样本数*通道数*每个样本占用的字节,感觉加上了SoundTouch后不好理解,换种写法,跟不加之前统一,主要是Enqueue这儿的代码统一
                data_size=nb*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
    
    //            fwrite(buffer,1,data_size,outFile);
    
                //当前AVFrame中存放的时间(比如说该Frame出现在2分钟的时候,那么它的值就是2分钟)
                now_time=avFrame->pts*av_q2d(time_base);
                //clock表示的是从开始播放到现在已经播放的时长
                if(now_time<clock)
                {
                    now_time=clock;
                }
                clock=now_time;
                *pcmbuf=buffer;
                if(LOG_DEBUG)
                {
                    LOGI("总时长duration:%d     当前时长:%d ",duration,now_time);
                }
    
    
                //LOGI("data_size is %d", data_size);
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket = NULL;
                av_frame_free(&avFrame);
                av_free(avFrame);
                avFrame = NULL;
                swr_free(&swrContext);
                break;
    
            } else{
                av_packet_free(&avPacket);
                av_free(avPacket);
                avPacket=NULL;
                av_frame_free(&avFrame);
                av_free(avFrame);
                avFrame=NULL;
                continue;
            }
        }
    //    fclose(outFile);
        return data_size;
    }
    
    void pcmBufferCallBack(SLAndroidSimpleBufferQueueItf bf, void * context)
    {
        HAudio *wlAudio = (HAudio *) context;
        if(wlAudio != NULL)
        {
            if(LOG_DEBUG)
            {
                LOGI("循环调用重采样");
            }
            int buffersize = wlAudio->getSoundTouchData();
    
            //int buffersize = wlAudio->resampleAudio((void **) &wlAudio->out_buffer);;
            if(buffersize > 0)
            {
                int out_channels=av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
                int totalBytes=buffersize*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
                wlAudio->clock+=totalBytes/(double)(wlAudio->sample_rate*2*2);
                //我们0.1s回调一次
                if(wlAudio->clock-wlAudio->last_time>0.1)
                {
                    wlAudio->last_time=wlAudio->clock;
                    //回调Java层显示时间
                    wlAudio->callBackJava->onShowTime(CHILD_THREAD,wlAudio->duration,wlAudio->clock);
    
                }
                //(* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> buffer, buffersize);
                //注意这儿的代码变化
                //(* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> sampleBuffer, buffersize*out_channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
                (* wlAudio-> pcmBufferQueue)->Enqueue( wlAudio->pcmBufferQueue, (char *) wlAudio-> sampleBuffer, totalBytes);
            }
        }
    }
    
    
    void HAudio::initOpenSLES() {
    
        SLresult result;
        result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
        result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
        result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    
        //第二步,创建混音器
        const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};
        const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};
        result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, mids, mreq);
        (void)result;
        result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
        (void)result;
        result = (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB, &outputMixEnvironmentalReverb);
        if (SL_RESULT_SUCCESS == result) {
            result = (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
                    outputMixEnvironmentalReverb, &reverbSettings);
            (void)result;
        }
        SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
        SLDataSink audioSnk = {&outputMix, 0};
    
    
        // 第三步,配置PCM格式信息
        SLDataLocator_AndroidSimpleBufferQueue android_queue={SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,2};
    
        SLDataFormat_PCM pcm={
                SL_DATAFORMAT_PCM,//播放pcm格式的数据
                2,//2个声道(立体声)
                getCurrentSampleRateForOpensles(sample_rate),//44100hz的频率
                SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16位
                SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行
                SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)
                SL_BYTEORDER_LITTLEENDIAN//结束标志
        };
        SLDataSource slDataSource = {&android_queue, &pcm};
    
    
        const SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE,SL_IID_VOLUME,SL_IID_MUTESOLO};
        const SLboolean req[3] = {SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE};
    
        (*engineEngine)->CreateAudioPlayer(engineEngine, &pcmPlayerObject, &slDataSource, &audioSnk, 3, ids, req);
        //初始化播放器
        (*pcmPlayerObject)->Realize(pcmPlayerObject, SL_BOOLEAN_FALSE);
    
    //    得到接口后调用  获取Player接口
        (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_PLAY, &pcmPlayerPlay);
    
        (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_VOLUME, &pcmVolumePlay);
    
        (*pcmPlayerObject)->GetInterface(
                pcmPlayerObject,
                SL_IID_MUTESOLO,
                &pcmPlayPlayerMuteSolo);
    
    //    注册回调缓冲区 获取缓冲队列接口
        (*pcmPlayerObject)->GetInterface(pcmPlayerObject, SL_IID_BUFFERQUEUE, &pcmBufferQueue);
        //缓冲接口回调
        (*pcmBufferQueue)->RegisterCallback(pcmBufferQueue, pcmBufferCallBack, this);
    //    获取播放状态接口
        (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay, SL_PLAYSTATE_PLAYING);
        pcmBufferCallBack(pcmBufferQueue, this);
    
    
    }
    
    int HAudio::getCurrentSampleRateForOpensles(int sample_rate) {
        int rate = 0;
        switch (sample_rate)
        {
            case 8000:
                rate = SL_SAMPLINGRATE_8;
                break;
            case 11025:
                rate = SL_SAMPLINGRATE_11_025;
                break;
            case 12000:
                rate = SL_SAMPLINGRATE_12;
                break;
            case 16000:
                rate = SL_SAMPLINGRATE_16;
                break;
            case 22050:
                rate = SL_SAMPLINGRATE_22_05;
                break;
            case 24000:
                rate = SL_SAMPLINGRATE_24;
                break;
            case 32000:
                rate = SL_SAMPLINGRATE_32;
                break;
            case 44100:
                rate = SL_SAMPLINGRATE_44_1;
                break;
            case 48000:
                rate = SL_SAMPLINGRATE_48;
                break;
            case 64000:
                rate = SL_SAMPLINGRATE_64;
                break;
            case 88200:
                rate = SL_SAMPLINGRATE_88_2;
                break;
            case 96000:
                rate = SL_SAMPLINGRATE_96;
                break;
            case 192000:
                rate = SL_SAMPLINGRATE_192;
                break;
            default:
                rate =  SL_SAMPLINGRATE_44_1;
        }
        return rate;
    }
    
    void HAudio::pause() {
    
        if(pcmPlayerPlay!=NULL)
        {
            (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay,SL_PLAYSTATE_PAUSED);
        }
    
    
    }
    
    void HAudio::resume() {
    
        if(pcmPlayerPlay!=NULL)
        {
            (*pcmPlayerPlay)->SetPlayState(pcmPlayerPlay,SL_PLAYSTATE_PLAYING);
        }
    
    }
    
    
    void HAudio::setVolume(int percent) {
        volumePercent = percent;
        if(pcmVolumePlay != NULL)
        {
            if(percent > 30)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -20);
            }
            else if(percent > 25)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -22);
            }
            else if(percent > 20)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -25);
            }
            else if(percent > 15)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -28);
            }
            else if(percent > 10)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -30);
            }
            else if(percent > 5)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -34);
            }
            else if(percent > 3)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -37);
            }
            else if(percent > 0)
            {
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -40);
            }
            else{
                (*pcmVolumePlay)->SetVolumeLevel(pcmVolumePlay, (100 - percent) * -100);
            }
        }
    }
    
    void HAudio::setStereoVolume() {
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                1,  //0右声道1左声道
                false //声道是否开启
        );
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                0,  //0右声道1左声道
                false //声道是否开启
        );
    }
    
    void HAudio::setRightVolume() {
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                1,  //0右声道1左声道
                false //声道是否开启
        );
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                0,  //0右声道1左声道
                true //声道是否开启
        );
    }
    
    void HAudio::setLeftVolume() {
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                0,  //0右声道1左声道
                false //声道是否开启
        );
        (*pcmPlayPlayerMuteSolo)->SetChannelMute(
                pcmPlayPlayerMuteSolo,
                1,  //0右声道1左声道
                true //声道是否开启
        );
    }
    
    
    /**
     * 设置音速
     * @param speed
     */
    void HAudio::setSpeed(jfloat speed) {
        this->speed=speed;
        if(soundTouch!=NULL)
        {
            soundTouch->setTempo(speed);
        }
    
    }
    
    /**
     * 设置音调
     * @param tonal
     */
    void HAudio::setTonal(jfloat tonal) {
        this->pitch=tonal;
        if(soundTouch!=NULL)
        {
            soundTouch->setPitch(tonal);
        }
    
    }
    
    int HAudio::getSoundTouchData() {
        while(hPlayStatus != NULL && !hPlayStatus->exit)
        {
            out_buffer = NULL;
            if(finished)
            {
                finished = false;
                data_size = resampleAudio((void **) &out_buffer);
                if(data_size > 0)
                {
                    for(int i = 0; i < data_size / 2 + 1; i++)
                    {
                        sampleBuffer[i] = (out_buffer[i * 2] | ((out_buffer[i * 2 + 1]) << 8));
                    }
                    soundTouch->putSamples(sampleBuffer, nb);
                    num = soundTouch->receiveSamples(sampleBuffer, nb);
                } else{
                    soundTouch->flush();
                }
            }
            if(num == 0)
            {
                finished = true;
                continue;
            } else{
                if(out_buffer == NULL)
                {
                    num = soundTouch->receiveSamples(sampleBuffer, nb);
                    if(num == 0)
                    {
                        finished = true;
                        continue;
                    }
                }
                return num;
            }
        }
        return 0;
    }
    
    
    
    
    
    
    

    相关文章

      网友评论

          本文标题:第十三节 利用SoundTouch实现变速、变调

          本文链接:https://www.haomeiwen.com/subject/qlfxtqtx.html