FFmpeg + SoundTouch實現音頻的變調變速
本文使用FFmpeg + SoundTouch實現將音頻解碼后,進行變調變速處理,并將處理后的結果保存為WAV文件。
主要有以下內容:
-
實現一個FFmpeg的工具類,保存多媒體文件所需的解碼信息
-
將解碼后的音頻保存為WAV文件
-
SoundTouch的使用指南
1.從視頻文件中提取音頻保存為WAV文件
本小節實現從視頻文件中提取音頻,解碼并保存為WAV文件。
在使用FFmpeg解碼時,一般的流程是:
- 打開一個多媒體文件流
- 得到媒體流信息
- 查找視頻、音頻流的index
- 根據流的index查找相應的的CODEC,打開 AVCodecContext
進行完以上操作后,就得到解碼所需的各種信息: AVFormateContext 、 AVCodecContext 以及對應流的index。也就說,這些數據是解碼多媒體流的必須信息,所以這里對上述操作做一個封裝,提供一個單一接口來獲取解碼所需的信息。
1.1 MediaInfo工具類
在使用FFmpeg進行解碼的時候,所需要的信息如下:
- AVFormatContext
- AVCodecContext
- 流的index
MediaInfo 的聲明如下:
class CMediaInfo
{
public:
CMediaInfo();
CMediaInfo(MEDIA_TYPE media);
~CMediaInfo();
public:
ERROR_TYPE open(const char *filename);
void close();
void error_message(ERROR_TYPE error);
public:
MEDIA_TYPE type;
AVFormatContext *pFormatContext;
AVCodecContext *pVideo_codec_context;
AVCodecContext *pAudio_codec_context;
int video_stream_index;
int audio_stream_index;
};
- 構造函數需要一個參數,指出該類中包含的信息為視頻、音頻或者音視頻都包含;
- open 方法,根據傳入的多媒體文件填充各個字段信息; close 方法,關閉打開的 AVFormatContext 和 AVCodecContext 等。
- 字段 為解碼所需的各類信息。
至于具體的實現,可參考前面的文章 ,在最后會提供本文使用的代碼,這里不再多說。
1.2 從視頻中提取音頻
1.2.1 獲取解碼所需的信息
使用上面的提供的 MediaInfo 工具類,首先根據視頻文件路徑填充 MediaInfo 的各個字段
char* filename = "E:\\Wildlife.wmv";
CMediaInfo media(MEDIA_TYPE::AUDIO);
media.open(filename);
1.2.2 設置音頻的保存格式
在真正的提取解碼之前,需要首先設置好要保存的WAV的音頻格式。FFmpeg使用 SwrContext 設置音頻的轉換格式,具體代碼如下:
AVSampleFormat dst_format = AV_SAMPLE_FMT_S16;
uint8_t dst_channels = 2;
auto dst_layout = av_get_default_channel_layout(dst_channels);
auto audio_ctx = media.pAudio_codec_context;
if (audio_ctx->channel_layout <= 0)
audio_ctx->channel_layout = av_get_default_channel_layout(audio_ctx->channels);
SwrContext *swr_ctx = swr_alloc();
swr_alloc_set_opts(swr_ctx, dst_layout, dst_format, audio_ctx->sample_rate,
audio_ctx->channel_layout, audio_ctx->sample_fmt, audio_ctx->sample_rate, 0, nullptr);
if (!swr_ctx || swr_init(swr_ctx))
return -1;
這里設置音頻的sample格式為16位的有符號整數,通道數為2通道,采樣率不變。
1.2.3 解碼,并保存為WAV文件
使用 MediaInfo 獲取到關于解碼的相關信息,并且設置好格式轉換需要的 SwrContext ,然后調用 av_read_frame 從流中讀取packet,解碼。最后將解碼后的數據進行格式轉換后,將轉換后的數據寫入WAV文件。
int pcm_data_size = 0;
while (av_read_frame(media.pFormatContext, packet) >= 0)
{
if (packet->stream_index == media.audio_stream_index)
{
auto ret = avcodec_send_packet(media.pAudio_codec_context, packet);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
return -1;
ret = avcodec_receive_frame(media.pAudio_codec_context, frame);
if (ret < 0 && ret != AVERROR_EOF)
return -1;
auto nb = swr_convert(swr_ctx, &buffer, 192000, (const uint8_t **)frame->data, frame->nb_samples);
auto length = nb * dst_channels * av_get_bytes_per_sample(dst_format);
ofs.write((char*)buffer, length);
pcm_data_size += length;
}
}
在寫入文件的時候要使用二進制的方式,并且要記錄好寫入的音頻的數據的字節數,在最后寫WAV文件頭的時候需要。
寫入WAV文件頭
// 寫Wav文件頭
Wave_header header(dst_channels, audio_ctx->sample_rate, av_get_bytes_per_sample(dst_format) * 8);
header.data->cb_size = ((pcm_data_size + 1) / 2) * 2;
header.riff->cb_size = 4 + 4 + header.fmt->cb_size + 4 + 4 + header.data->cb_size + 4;
ofs.seekp(0, ios::beg);
CWaveFile::write_header(ofs, header);
首先將音頻的PCM數據寫入文件,然后根據PCM數據的長度填充WAV文件頭的相關字段。
2.SoundTouch使用指南
SoundTouch 是一個開源的音頻庫,主要有以下功能:
- 變速不變調(TSM,Time Scale Modification),改變音頻的播放速度(快或者慢)同時不影響音頻的聲調(Pitch)。
- 變調不變速 Pitch Shifting ,改變音頻聲調的同時保持音頻的播放速度不變
- 變調變速,同時改變音頻的聲調和速度
2.1 編譯
從 SoundTouch 下載源代碼,解壓后在 README.html 中給出了具體的編譯方法,在Windows下有兩種方法來編譯源代碼:
-
執行解壓文件夾下面的 make-win.bat 腳本。試過這種方法沒有成功,看了下make-win.bat腳本的內容,應該是沒有找到相關的環境變量(VS2008)。該腳本主要是執行下面命令
devenv source\SoundStretch\SoundStretch.vcproj /upgrade devenv source\SoundStretch\SoundStretch.vcproj /build debug devenv source\SoundStretch\SoundStretch.vcproj /build release devenv source\SoundStretch\SoundStretch.vcproj /build releasex64
-
使用Visudl Studio IDE來編譯,打開source\Soundtouch下面的SoundTouch.sln,然后編譯即可。SoundTouch.sln編譯出來的是靜態鏈接庫,使用VS版本為Visual Studio 2008。
對編譯后庫的使用需要注意以下兩點:
- VS2008編譯出來的靜態鏈接庫在VS2013調用會出現問題,提示ERROR LINK2019錯誤找不到相關的符號。
- 在source目錄下有個 SoundTouchDLL 項目,一看名字就是編譯動態鏈接庫dll的。編譯,配置相應的參數(dll,lib),然后實例化 SoundTouch s_touch 。這時候又會提示ERROR LINK2019,一直以為是環境沒有配置好,找不到相應的dll文件。結果,是動態鏈接庫dll的導出的不是整個 SoundTouch 類,只是其中的一些方法。
/// Sets new rate control value. Normal rate = 1.0, smaller values
/// represent slower rate, larger faster rates.
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRate(HANDLE h, float newRate);
/// Sets new tempo control value. Normal tempo = 1.0, smaller values
/// represent slower tempo, larger faster tempo.
SOUNDTOUCHDLL_API void __cdecl soundtouch_setTempo(HANDLE h, float newTempo);
/// Sets new rate control value as a difference in percents compared
/// to the original rate (-50 .. +100 %);
SOUNDTOUCHDLL_API void __cdecl soundtouch_setRateChange(HANDLE h, float newRate);
后來,看了下Android的示例,這個動態鏈接庫導出的函數應該是提供給Android使用的API。
2.2 使用
得到編譯后的靜態鏈接庫后,SoundTouch的使用還是很簡單的,其外部API封裝在了類 SoundTouch 中。在使用的時候只需要下面三個步驟:
- 實例話 SoundTouch 類
- 設置相關的參數(速度,音調的改變)
- 調用 putSamples 方法傳入處理的Audio Sample;調用 receiveSamples 接收處理后的Sample。
- 在處理完成后,調用 soundtouch.fflush() 接收管道內余下的sample
使用實例如下:
////////////////////////////////////////////////////////////////////
// 1. 設置SoundTouch,配置變調變速參數
soundtouch::SoundTouch s_touch;
s_touch.setSampleRate(audio_ctx->sample_rate); // 設置采樣率
s_touch.setChannels(audio_ctx->channels); // 設置通道數
////////////////////////////////////////////
// 2. 設置 rate或者pitch的改變參數
//s_touch.setRate(0.5); // 設置速度為0.5,原始的為1.0
s_touch.setRateChange(-50.0);
//////////////////////////////////////////////////////////////
// 3. 傳入sample,并接收處理后的sample
// 將解碼后的buffer(uint8*)轉換為soundtouch::SAMPLETYPE,也就是singed int 16
auto len = nb * dst_channels * av_get_bytes_per_sample(dst_format);
for (auto i = 0; i < len; i++)
{
touch_buffer[i] = (buffer[i * 2] | (buffer[i * 2 + 1] << 8));
}
// 傳入Sample
s_touch.putSamples(touch_buffer, nb);
do
{
// 接收處理后的sample
nb = s_touch.receiveSamples(touch_buffer, 96000);
auto length = nb * dst_channels * av_get_bytes_per_sample(dst_format);
ofs.write((char*)touch_buffer, length);
pcm_data_size += length;
} while (nb != 0);
///////////////////////////////////////////////
// 4. 接收管道內余下的處理后數據
s_touch.flush();
int nSamples;
do
{
nSamples = s_touch.receiveSamples(touch_buffer, 96000);
auto length = nSamples * dst_channels * av_get_bytes_per_sample(dst_format);
ofs.write((char*)touch_buffer, length);
pcm_data_size += length;
} while (nSamples != 0);
SoundTouch內部使用通道的方式來管理sample數據,所以在主循環接收好,要接收管道內剩余的sample。
使用的時候需要注意以下幾點
-
sample的類型。SoundTouch支持兩種類型sample類型:16位有符號整數和32位浮點數,默認使用的是32為浮點數。其sample類型在頭文件 STTypes.h 中聲明為 SAMPLETYPE 。在該文件的開始位置,使用宏 SOUNDTOUCH_INTEGER_SAMPLES 和 SOUNDTOUCH_FLOAT_SAMPLES 來決定使用那種sample類型。
#define SOUNDTOUCH_INTEGER_SAMPLES 1 //< 16bit integer samples //#define SOUNDTOUCH_FLOAT_SAMPLES 1 //< 32bit float samples
另外,為了防止計算時有溢出,也支持32為有符號整數和64位浮點數,其類型為 LONG_SAMPLETYPE 。
- 速度和pitch參數的設置
- 變調不變速
- setPitch(double newPitch) 源pitch = 1.0,小于1音調變低;大于1音調變高
- setPitchOctaves(double newPitch) 在源pitch的基礎上,使用八度音(Octave)設置新的pitch [-1.00, 1.00]。
- setPitchSemiTones(double or int newPitch) 在源pitch的基礎上,使用半音(Semitones)設置新的pitch [-12.0,12.0]
- 變速不變調
- setRate(double newRate) 設置新的rate,源rate=1.0,小于1變慢;大于1變快
- setRateChange(double newRate) 在源rate的基礎上,以百分比設置新的rate[-50,100]
- setTempo(double newTempo) 設置新的節拍tempo,源tempo=1.0,小于1則變慢;大于1變快
- setTempoChange(double newTempo) 在源tempo的基礎上,以百分比設置新的tempo[-50,100]
- 變調不變速
3. FFmpeg + SoundTouch 變調、變速
有了前面的實現,只需要在FFmepg解碼后,將解碼后的數據發送到 SoundTouch 中進行處理即可。有一點需要注意,FFmpeg解碼后的數據存放在類型為 uint8 的緩存中,在將sample發送給 SoundTouch 處理前,需要根據 SoundTouch 的 SAMPLETYPE 進行相應的轉換。本文使用的SAMPLETYPE的是S16,首先將 uint8 兩個字節組合一個S16(小端)
// 將解碼后的buffer(uint8*)轉換為soundtouch::SAMPLETYPE,也就是singed int 16
auto len = nb * dst_channels * av_get_bytes_per_sample(dst_format);
for (auto i = 0; i < len; i++)
{
touch_buffer[i] = (buffer[i * 2] | (buffer[i * 2 + 1] << 8));
}
首先計算緩存中的字節數,然后按照小端的方式組合為16為有符號整數。然后將轉換后的buffer傳送給 SoundTouch 即可。
s_touch.putSamples(touch_buffer, nb);
do
{
// 接收處理后的sample
nb = s_touch.receiveSamples(touch_buffer, 96000);
auto length = nb * dst_channels * av_get_bytes_per_sample(dst_format);
ofs.write((char*)touch_buffer, length);
pcm_data_size += length;
} while (nb != 0);
變調變速的處理結果如下圖:
頻譜圖,上圖為原始音頻的頻譜;下圖為使用 setPitch(0.1) 將pitch設為原始的10%得到的頻譜圖
波形圖,上圖為原始的波形圖;下圖為使用 setRateChange(-50.0) 設置速度減少50%得到的波形圖
4. 總結
本文使用FFmepg + SoundTouch相結合的方式,將音頻從視頻從提取出來,進行變調變速處理后保存為WAV文件。結合前面的學習總結,可以很容易的實現音頻的變調變速播放。
來自:http://www.cnblogs.com/wangguchangqing/p/6003087.html