SauronKing

写随笔只是为了记录自己的曾经,如果能给您带来些许方便,那是我莫大的荣幸!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

视频播放器-使用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接口播放声音。

posted on 2020-07-20 11:13  SauronKing  阅读(1520)  评论(0编辑  收藏  举报