zoukankan      html  css  js  c++  java
  • 视频播放器-使用SoundTouch算法库对声音进行变速

    视频播放器-视频播放前期调研

    视频播放器-使用FFMPEG技术对视频解封装和解码

    视频播放器-使用SoundTouch算法库对声音进行变速

    视频播放器-使用OpenAL技术播放声音

    视频播放器-使用封装的C++插件在Unity3d中播放视频

    视频播放器-FFMPEG官方库,包含lib,include,bin x64和x86平台的所有文件,提取码4v2c

    视频播放器-LQVideo实现视频解码C++源代码,提取码br9u

    视频播放器-SoundTouch实现声音变速的C++源代码,提取码6htk

    视频播放器-官方openal安装文件,提取码yl3j

    视频播放器-OpenAL实现音频播放功能,提取码mjp2

     

    上一篇我们使用了FFMPEG库对视频进行了解码,抛开细节不谈,通过使用接口IntPtr get_audio_frame(int key),我们可以获取到音频的数据,也就是一堆字节数组,接下来,就轮到SoundTouch上场了,我们可以把SoundTouch看做是一个工具库,通过输入音频数据,输出经过变速后的新的数据。

     

    关于SoundTouch的使用我们依然分为三部分

    • 环境配置
    • 定义接口及实现接口
    • 需要注意的问题

     

    环境配置

    1. 下载源代码 https://gitlab.com/soundtouch/soundtouch/-/archive/2.1.2/soundtouch-2.1.2.tar.bz2
      没有什么是比拿到源代码更令人放心的了
    2. 代码的soundtouch-master文件夹下的lib文件夹中有编译好的dll和链接库,include中有头文件,但是,我们依然推荐自己手动编译,根据自己的平台要求和调试要求可以生成自己需要的静态链接库(lib)和动态链接库(dll)。找到source文件夹下的SoundTouch文件夹,用VS双击打开.sln文件,我这边是可以直接编译成功的
    3. 项目右键->属性->常规,配置类型选择静态库,编译生成lib文件,选择动态库,编译生成dll文件
    4. image

    5. 用VS新建C++工程,或者直接在SoundTouch工程中新建项目也可以,按照上一篇文件的过程分别配置“附加包含目录”,“附加库目录”和“附加依赖项”。
    6. 经过第四步,我们可以在新建的工程中使用SoundTouch.h文件了。

    定义接口及实现接口

    配置好环境后,我们开始定义接口,在这之前,先说明一个SoundTouch的规则,SoundTouch的加速算法好像是异步的,也就是说把待加速的数据输入后不一定可以立即输出加速后的数据,所以我们需要定义一个方法用来获取当前可以输出的数据是多少:

    uint GetSampleNum():获取当前加速完成的数据

    void CreateInstance():创建SoundTouch实例
    void DestroyInstance():销毁SoundTouch实例

    void SetTempo(double value):设置变速系数

    void SetChannel(uint value):设置声道数

    void SetSampleRate(uint value):设置采样率

    void PutSampleShort(short *data, uint sampleLength):输入数据,这个方法说明一下,SoundTouch算法接收的数据其实是4个字节的float数据,但是我们上篇文章获取音频数据的时候输出格式是16位的也就是2个字节,所以我们需要进行一下转化,转成4字节的数据。第二个参数看表面意思表示采样数据的长度,需要注意的是这个长度要区分声道数,举个例子,我们输入的数据的总字节数为1024,一个采样点是16位2个字节,音频的声道数是2,那么这个参数应该是1024/采样点字节数2/音频声道数2=256。这个方法是最重要的也最容易出错的方法,我自己在这个方法占用的时间特别长,因为我刚开始一直把两个采样点的数据合成一个4字节的float的数据,这样其实是错误的,如果输出的数据是那种呲呲的声音,基本问题都出现在了这个方法上,希望能给使用该算法的同学节省点时间。

    uint GetSampleShort(short *data, uint sampleLength):获取输出数据,返回值是真实返回的数据长度,这个长度不一定==sampleLength,因为我们说过算法是异步的。

    好了,基本上上面这几个API就可以对声音数据进行变速了,接下来提供代码,这个C++库更简单,只有一个头文件和一个C++文件。

     

    LQAudio.h

    #pragma once
    #include "SoundTouch.h"
    using namespace soundtouch;
    
    extern "C" _declspec(dllexport) void CreateInstance();
    extern "C" _declspec(dllexport) void DestroyInstance();
    extern "C" _declspec(dllexport) void SetTempo(double value);
    extern "C" _declspec(dllexport) void SetChannel(uint value);
    extern "C" _declspec(dllexport) void SetSampleRate(uint value);
    extern "C" _declspec(dllexport) void Flush();
    extern "C" _declspec(dllexport) void PutSampleShort(short *data, uint sampleLength);
    extern "C" _declspec(dllexport) uint GetSampleShort(short *data, uint sampleLength);
    extern "C" _declspec(dllexport) uint GetSampleNum();

    LQAudio.cpp

    #include "LQAudio.h"
    
    SoundTouch *ins=NULL;
    
    /*创建变速算法的实例*/
    void CreateInstance()
    {
        ins = new SoundTouch();
    }
    
    /*销毁变速算法的实例。
    其实这里面应该进行delete,但是我这边总报异常,待处理*/
    void DestroyInstance()
    {
        if (ins==NULL)
        {
            return;
        }
        ins = NULL;
    }
    
    /*设置变速系数*/
    void SetTempo(double value)
    {
        if (ins == NULL)
        {
            return;
        }
        ins->setTempo(value);
    }
    /*设置声道*/
    void SetChannel(uint value)
    {
        if (ins == NULL)
        {
            return;
        }
        ins->setChannels(value);
    }
    
    /*设置采样率*/
    void SetSampleRate(uint value)
    {
        if (ins == NULL)
        {
            return;
        }
        ins->setSampleRate(value);
    }
    /*输入采样数据*/
    void PutSampleShort(short *data, uint sampleLength)
    {
        if (ins == NULL)
        {
            return;
        }
        uint numChannels = ins->numChannels();
    
        // iterate until all samples converted & put to SoundTouch object
        while (sampleLength > 0)
        {
            float convert[8192];    // allocate temporary conversion buffer from stack
    
            // how many multichannel samples fit into 'convert' buffer:
            uint convSamples = 8192 / numChannels;
    
            // convert max 'nround' values at a time to guarantee that these fit in the 'convert' buffer
            uint n = (sampleLength > convSamples) ? convSamples : sampleLength;
            for (uint i = 0; i < n * numChannels; i++)
            {
                convert[i] = data[i];
            }
            // put the converted samples into SoundTouch
            ins->putSamples(convert, n);
            sampleLength -= n;
            data += n * numChannels;
        }
    }
    /*输出采样数据*/
    uint GetSampleShort(short *data, uint sampleLength)
    {
        if (ins == NULL)
        {
            return 0;
        }
        uint outTotal = 0;
        if (data == NULL)
        {
            // only reduce sample count, not receive samples
            return ins->receiveSamples(sampleLength);
        }
    
        uint numChannels = ins->numChannels();
    
        // iterate until all samples converted & put to SoundTouch object
        while (sampleLength > 0)
        {
            float convert[8192];    // allocate temporary conversion buffer from stack
            // how many multichannel samples fit into 'convert' buffer:
            uint convSamples = 8192 / numChannels;
            // request max 'nround' values at a time to guarantee that these fit in the 'convert' buffer
            uint n = (sampleLength > convSamples) ? convSamples : sampleLength;
            uint out = ins->receiveSamples(convert, n);
            // convert & saturate received samples to int16
            for (uint i = 0; i < out * numChannels; i++)
            {
                // first convert value to int32, then saturate to int16 min/max limits
                int value = (int)convert[i];
                value = (value < SHRT_MIN) ? SHRT_MIN : (value > SHRT_MAX) ? SHRT_MAX : value;
                data[i] = (short)value;
            }
            outTotal += out;
            if (out < n) break;  // didn't get as many as asked => no more samples available => break here
            sampleLength -= n;
            data += out * numChannels;
        }
        // return number of processed samples
        return outTotal;
    }
    
    uint GetSampleNum()
    {
        if (ins == NULL)
        {
            return 0;
        }
        return ins->numSamples();
    }
    
    void Flush()
    {
        if (ins == NULL)
        {
            return;
        }
        ins->flush();
    }

    需要注意的问题

    • 上述代码中的PutSampleShort方法和GetSampleShort方法不是我自己写的,其实在官方给的源代码中有一个SoundTouchDLL项目,里面有这个两个方法
    • 该文章有个点没有讲到,就是声音的音频数据怎么转化为short数据,对于C#来说,有官方的方法Buffer.BlockCopy()

    好了,剩下的就是把文件编译成dll文件,和SoundTouch.dll文件一起待用,下一篇将要介绍使用OpenAL接口播放声音。

  • 相关阅读:
    控制流程
    表达式
    2020.2.7
    寒假自学进度六
    2020.2.6
    2020.2.5
    寒假自学进度五
    Scala初级实验
    寒假自学进度四
    Spark运行基本流程
  • 原文地址:https://www.cnblogs.com/sauronKing/p/13344015.html
Copyright © 2011-2022 走看看