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通道,采样率不变,具体关于音频格式的转换可参考:FFmpeg学习4:音频格式转换。
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文件头的相关字段。具体关于WAV的文件格式及其读写方法可参考RIFF和WAVE音频文件格式和C++标准库实现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文件。结合前面的学习总结,可以很容易的实现音频的变调变速播放。
本文中的使用的代码:
- CSDN下载 SoundTouch VS2013 Project 官网下的为VS2008版本,编译出来的静态链接库在VS2013使用一直出现LINK2019错误。
- CSDN 下载FFmpeg + SoundTouch 变速变调 需要配置FFmpeg的开发环境
- Github 学习过程使用的代码库。
FFmpeg + SoundTouch实现音频的变调变速的更多相关文章
- C# 使用ffmpeg.exe进行音频转换完整demo-asp.net转换代码
C# 使用ffmpeg.exe进行音频转换完整demo-asp.net转换代码 上一篇说了在winform下进行调用cmd.exe执行ffmpeg.exe进行音频转换完整demo.后来我又需要移植这个 ...
- 最简单的基于FFMPEG+SDL的音频播放器 ver2 (采用SDL2.0)
===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...
- 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)
===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...
- .net 使用ffmpeg.exe进行音频转码
#region 音频转换 private int AudioIntervalTime = 100, iAudio = 0; private string strPath = "D:\\web ...
- C# 使用ffmpeg.exe进行音频转换完整demo
今天在处理微信的开发接口时候,发现微信多媒体上传接口中返回的音频格式是amr.坑人的是现在大部分的web 播放器,不支持amr的格式播放.试了很多方法都不行. 没办法,只要找一个妥协的解决方案:将am ...
- ffmpeg编解码音频AAC
本次项目的需求:手机端和PC端共享同一个音视频网络源. 所以编解码需要满足手机上编码和解码原来PC端的音视频流. 这里先封装安卓手机端音频的编解码. 编译工作依然是在linux下 ubuntu 12. ...
- ffmpeg 多个音频合并 截取 拆分
1 多个mp3文件合并成一个mp3文件 一种方法是连接到一起 ffmpeg64.exe -i "concat:123.mp3|124.mp3" -acodec copy outpu ...
- EasyDarwin开源音频解码项目EasyAudioDecoder:基于ffmpeg的安卓音频(AAC、G726)解码库(第一部分,ffmpeg-android的编译)
ffmpeg是一套开源的,完整的流媒体解决方案.基于它可以很轻松构建一些强大的应用程序.对于流媒体这个行业,ffmpeg就像圣经一样的存在.为了表达敬意,在这里把ffmpeg官网的一段简介搬过来,ff ...
- ffmpeg学习笔记-音频播放
前文讲到音频解码,将音频解码,并且输入到PCM文件,这里将音频通过AudioTrack直接输出 音频播放说明 在Android中自带的MediaPlayer也可以对音频播放,但其支持格式太少 使用ff ...
随机推荐
- Python高手之路【一】初识python
Python简介 1:Python的创始人 Python (英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种解释型.面向对象.动态数据类型的高级程序设计语言,由荷兰人Guido ...
- win7下利用ftp实现华为路由器的上传和下载
win7下利用ftp实现华为路由器的上传和下载 1. Win7下ftp的安装和配置 (1)开始->控制面板->程序->程序和功能->打开或关闭Windows功能 (2)在Wi ...
- ubuntu15.04 nginx1.6.5 配置虚拟主机
1 在/etc/hosts 添加host 2 在/etc/nginx/nginx.conf中查看http里的include ****** /*.conf的路径,在此路径下添加一个新的******. ...
- [每日Linux]Linux下xsell和xftp的使用
实验缘由: 1.xsell在Linux下的作用就是远程登录的一个界面,也就是实现访问在Windows下访问Linux服务器的功能.之前在数据挖掘实验中因为自己电脑的内存不够,曾经使用过实验室的服务器跑 ...
- NYOJ 998
这道题是欧拉函数的使用,这里简要介绍下欧拉函数. 欧拉函数定义为:对于正整数n,欧拉函数是指不超过n且与n互质的正整数的个数. 欧拉函数的性质:1.设n = p1a1p2a2p3a3p4a4...pk ...
- NYOJ 455
1.应该交代清楚,参加宴会的人不知道一共有多少顶帽子.假如知道有n顶帽子的话,第一次开灯看见有n-1只,自然就知道自己是第n顶黑帽子,所以应该是这n个人在第一次关灯就打自己脸,不过这么一来就没意思了, ...
- ABP源码分析二:ABP中配置的注册和初始化
一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...
- Asp.Net跨平台:Ubuntu14.0+Mono+Jexus+Asp.Net
Asp.Net跨平台的文章园子里有很多,这里给自己搭建的情况做一下总结,方便以后查看. 参考网站: http://www.linuxdot.net/(Linux DotNET大本营 ) http ...
- 拥抱.NET Core,如何开发一个跨平台类库 (1)
在此前的文章中详细介绍了使用.NET Core的基本知识,如果还没有看,可以先去了解“拥抱.NET Core,学习.NET Core的基础知识补遗”,以便接下来的阅读. 在本文将介绍如何配置类库项目支 ...
- ubuntu15 coreclr
看了很多文章心里痒痒,也下载个ubuntu想发布个asp.net5试试,自然是下载的最新版本15.结果涉及dnu restore,dnx...什么的都没反应,切换为mono就正常,奇怪了,按说core ...