視頻播放器-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接口播放聲音。