FFmpeg + SoundTouch实现音频的变调变速

news/2024/7/10 20:54:48 标签: ffmpeg, python, 移动开发

本文使用FFmpeg + SoundTouch实现将音频解码后,进行变调变速处理,并将处理后的结果保存为WAV文件。
主要有以下内容:

  • 实现一个FFmpeg的工具类,保存多媒体文件所需的解码信息
  • 将解码后的音频保存为WAV文件
  • SoundTouch的使用指南

1.从视频文件中提取音频保存为WAV文件

本小节实现从视频文件中提取音频,解码并保存为WAV文件。
在使用FFmpeg解码时,一般的流程是:

  • 打开一个多媒体文件流
  • 得到媒体流信息
  • 查找视频、音频流的index
  • 根据流的index查找相应的的CODEC,打开AVCodecContext

进行完以上操作后,就得到解码所需的各种信息:AVFormateContextAVCodecContext以及对应流的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方法,关闭打开的AVFormatContextAVCodecContext等。
  • 字段 为解码所需的各类信息。

至于具体的实现,可参考前面的文章 ,在最后会提供本文使用的代码,这里不再多说。

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_SAMPLESSOUNDTOUCH_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]

ffmpeg-soundtouch-变调变速">3. FFmpeg + SoundTouch 变调、变速

有了前面的实现,只需要在FFmepg解码后,将解码后的数据发送到SoundTouch中进行处理即可。有一点需要注意,FFmpeg解码后的数据存放在类型为uint8的缓存中,在将sample发送给SoundTouch处理前,需要根据SoundTouchSAMPLETYPE进行相应的转换。本文使用的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);

变调变速的处理结果如下图:

439761-20161027104138875-109001963.png

频谱图,上图为原始音频的频谱;下图为使用setPitch(0.1)将pitch设为原始的10%得到的频谱图

439761-20161027104152687-306018684.png

波形图,上图为原始的波形图;下图为使用setRateChange(-50.0)设置速度减少50%得到的波形图

4. 总结

本文使用FFmepg + SoundTouch相结合的方式,将音频从视频从提取出来,进行变调变速处理后保存为WAV文件。结合前面的学习总结,可以很容易的实现音频的变调变速播放。
本文中的使用的代码:

  • CSDN下载 SoundTouch VS2013 Project 官网下的为VS2008版本,编译出来的静态链接库在VS2013使用一直出现LINK2019错误。
  • CSDN 下载FFmpeg + SoundTouch 变速变调 需要配置FFmpeg的开发环境
  • Github 学习过程使用的代码库。

http://www.niftyadmin.cn/n/1130168.html

相关文章

使用selenium抓取网易云音乐数据

今天通过一个简单的网易云音乐排行榜数据抓取与音乐下载实战案例&#xff0c;带着大家一起来学习如何抓取动态生成的页面内容。网易云音乐排行榜网址&#xff1a;https://music.163.com/#/discover/toplist&#xff0c;界面效果如下。 查看页面源代码&#xff0c;发现并没有这些…

keepalived+nginx 双主模型实现高可用服务

一、keepalived的工作原理keepalived是以VRRP协议为实现基础的&#xff0c;VRRP全称Virtual Router Redundancy Protocol&#xff0c;即虚拟路由冗协议。虚拟路由冗余协议&#xff0c;可以认为是实现路由器高可用的协议&#xff0c;即将N台提供相同功能的路由器组成一个虚拟路由…

回文(笔试题)

#include <stdio.h> /*函数名称:HuiWen *函数功能:产生字符A、B、C所组成的长度小于2n的全部互文 *返回类型:无返回值 *函数参数:p:字符指针&#xff0c;i&#xff1a;起始位置&#xff0c;n&#xff1a;长度 */ void HuiWen(char * p, int i, int n) { int j; if …

免安装绿色版本tomcat的问题

apache-tomcat-6.0.29&#xff0c;免安装的那种&#xff0c;解压缩就可用。但是启动的时候&#xff0c;tomcat一闪而过。在命令行中运行tomcat&#xff0c;会报the JAVA_HOME nor the JRE_HOME environment variable is defined 。 问题原因&#xff1a;较新版本的JDK&#xff…

Linux 笔记

http://man.linuxde.net/ centos中新建的非root用户是没有sudo的权限的&#xff0c;如果需要使用sudo权限必须在/etc/sudoers 中加入账户和权限&#xff0c;所以切换到root账号的时候只需要输入&#xff1a;su,加入root账号的密码即可。 在Ubuntu中&#xff0c;一般使用sudo命令…

Python数据分析与可视化案例解析

题目需求描述 已知两个Excel表格&#xff1a;学生基本信息表、期末考试成绩表分别用于存放学生的基本信息&#xff08;包括姓名、性别、班级&#xff09;和学生的期末成绩&#xff08;包括姓名、语文、数学、英语、总分&#xff09;&#xff0c;部分数据如下图所示&#xff08…

【HDOJ】2853 Assignment

最小费用最大流可解最优解。至于dif如何解&#xff0c;可以把w扩大100倍&#xff0c;如果mission编号和排列P相等则对w1&#xff0c;然后建立网络流。对结果取模100可以得到没有改变mission的company数目&#xff0c;用company数目减之可以得到dif. 1 /* 2853 */2 #include <…

从EVC4.0迁移到VS2005

////TITLE:// 从EVC4.0迁移到VS2005//AUTHOR:// norains//DATE:// Friday 21-December-2007//Environment:// EVC4.0 Windows CE 5.0 Standard SDK// EVC4.0 SDK-WINCE5.0-MIPSII// VS2005 SDK-WINCE5.0-MIPSII // 微软在兼容性方面做得可谓极致,不需要…