视频播放器-FFMPEG官方库,包含lib,include,bin x64和x86平台的所有文件,提取码4v2c
视频播放器-LQVideo实现视频解码C++源代码,提取码br9u
视频播放器-SoundTouch实现声音变速的C++源代码,提取码6htk
上一篇我们使用了FFMPEG库对视频进行了解码,抛开细节不谈,通过使用接口IntPtr get_audio_frame(int key),我们可以获取到音频的数据,也就是一堆字节数组,接下来,就轮到SoundTouch上场了,我们可以把SoundTouch看做是一个工具库,通过输入音频数据,输出经过变速后的新的数据。
关于SoundTouch的使用我们依然分为三部分
- 环境配置
- 定义接口及实现接口
- 需要注意的问题
环境配置
- 下载源代码 https://gitlab.com/soundtouch/soundtouch/-/archive/2.1.2/soundtouch-2.1.2.tar.bz2
没有什么是比拿到源代码更令人放心的了 - 代码的soundtouch-master文件夹下的lib文件夹中有编译好的dll和链接库,include中有头文件,但是,我们依然推荐自己手动编译,根据自己的平台要求和调试要求可以生成自己需要的静态链接库(lib)和动态链接库(dll)。找到source文件夹下的SoundTouch文件夹,用VS双击打开.sln文件,我这边是可以直接编译成功的
- 项目右键->属性->常规,配置类型选择静态库,编译生成lib文件,选择动态库,编译生成dll文件
- 用VS新建C++工程,或者直接在SoundTouch工程中新建项目也可以,按照上一篇文件的过程分别配置“附加包含目录”,“附加库目录”和“附加依赖项”。
- 经过第四步,我们可以在新建的工程中使用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接口播放声音。