音频特效实践

Posted blueberry_mu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音频特效实践相关的知识,希望对你有一定的参考价值。

音频特效

基础概念

波形图

波形图横轴是时间,纵轴为强度。

频谱图

在波形图中选取一段时间的音频,快速傅立叶变换(FFT)可以得到频谱图。
频谱图的横轴为频率,纵轴为强度。

语谱图

语谱图横轴为时间,纵轴为频率,颜色表示强度。
它是由STFT(短时傅里叶变换)得到。可以理解它是将时域的数据按固定的窗口分段进行FFT变换,然后叠加在一起。
可以把它理解为一个三位图形,但是用二维的形式呈现给了用户,即x轴为时间,y轴为频率,z轴为强度。

以上图形是使用音频处理软件Audacity得到。

傅立叶变换

  • 傅立叶变换可以将音频的时域数据转为频域数据。
  • 数字信号则使用DFT(离散傅立叶变换)来处理。
  • 但是因为DFT的计算量较大,实现一般会用FFT(快速傅立叶变换)。
  • 快速傅立叶变换的实现。
    • https://rosettacode.org/wiki/Fast_Fourier_transform#C.2B.2B
    • 一个android实现:https://github.com/paramsen/noise

效果器

均衡器(Equalizer)

均衡器可以提高/降低一段音频信号中某段频率的强度。比如我想比较喜欢一首歌的低频部分。我想将它的低频部分强度放大。则可以通过均衡器来调节低频区域的强度。
它的原理是将音频从时域进行FFT转换为频域,然后根据用户选择的中心频率,调节对应频率的强度,之后再进行FFT逆变换回时域信号。

软件中也会内置一些均衡器。

压缩效果器(Compressor)

压缩效果器,是指在时域上对声音强度所进行的一个处理。当音频的音量剧增的时候,会自动将音量调小一点。

  • 阈值,当声音强度大于这个值时,之后的声音没增加 x 强度,则输出的强度只会增加 x / 比率。 比如压缩比为2:1 ,没当声音增加 2,经过压缩则只增加 1.
  • 比率,压缩的比率。
  • 建立时间,用于决定压缩器在超过阈值多久后触发压缩器来工作。
  • 释放时间,当声音低于阈值多长时间后,触发器停止工作。

混响效果器(Reverb)

实际生活中人听到的声音很多都是经过多次反射进入人耳的。你在一间教室,一个山谷、或者其他场所听到的声音都会有所不同。混响效果就是模拟这种环境。

效果器的实现

sox

  • pc上的编译及使用
  1. 下载源码
git clone git://git.code.sf.net/p/sox/code sox
  1. 配置
autoreconf -i

编写sh脚本

#!/bin/bash
CWD=`pwd`
LOCAL=$CWD
./configure \\
--prefix="$LOCAL/pc_lib" \\
--enable-static \\
--disable-shared \\
--disable-openmp \\
--without-libltdl \\
--without-coreaudio 
  1. 运行shell脚本
  2. 构建及安装
make && make install 
  1. 使用sox
    构建完成之后可以在pc_lib目下下找到可执行文件sox.
    如执行均衡器:
sox audio.wav audio_output.wav equalizer 100.1 1.5q 3 equalizer 120 2.0q -19

上面的命令可以将audio.wav以100.1 位中心频率,宽为1.5q,强度提升3. 以及 120为中心频率宽度为 2.0q,强度降低19.
宽度为 $xq 表示的频宽。根据q可以计算出一个O(八度),一个O体现在频率上是2倍的频率。比如一个音调比另一个音调高一个八度,在频率上就是这个音调是另一个音调的2倍。

  • android上使用sox。
  1. 交叉编译
  2. 配置文件
#!/bin/bash
CWD=`pwd`
LOCAL=$CWD

NDK=~/Library/Android/sdk/ndk/22.1.7171670
NDK_SYSROOT=$NDK/toolchains/llvm/prebuilt/darwin-x86_64/sysroot
export TOOLCHAIN=$NDK/toolchains/llvm/prebuilt/darwin-x86_64
export TARGET=armv7a-linux-androideabi
# Set this to your minSdkVersion.
export API=28
# Configure and build.
export AR=$TOOLCHAIN/bin/llvm-ar
export CC=$TOOLCHAIN/bin/$TARGET$API-clang
export AS=$CC
export CXX=$TOOLCHAIN/bin/$TARGET$API-clang++
export LD=$TOOLCHAIN/bin/ld
export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
export STRIP=$TOOLCHAIN/bin/llvm-strip
./configure \\
--prefix="$LOCAL/android_lib" \\
--host $TARGET \\
--with-pic \\
--disable-openmp \\
--without-libltdl
make
  1. cmake 配置
cmake_minimum_required(VERSION 3.18)
project(audioeffect)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE  Debug)
set(CMAKE_VERBOSE_MAKEFILE ON)

set(PREBUILT_DIR $CMAKE_SOURCE_DIR/../../../prebuilt/armv7a)

message(STATUS "include $PREBUILT_DIR/include")

file(GLOB SOURCES "*.cpp" "*.h" )

add_library(audioeffect SHARED
        $SOURCES
        )

add_library(sox STATIC IMPORTED GLOBAL)
set_target_properties(sox PROPERTIES IMPORTED_LOCATION  $PREBUILT_DIR/lib/libsox.a)
set_target_properties(sox PROPERTIES INTERFACE_INCLUDE_DIRECTORIES  $PREBUILT_DIR/include)

target_link_libraries(audioeffect PRIVATE log PRIVATE sox)

  1. 实现一个均衡器

#include "Equalizer.h"
#include "logger.h"

#include <utility>
#include <fstream>

void Equalizer::setInput(string input) 
    logi("%s,%s", "setInput", input.c_str());
    input_ = std::move(input);


void Equalizer::setOutput(string output) 
    logi("%s,%s", "setOutput", output.c_str());
    output_ = std::move(output);


void Equalizer::start() 
    assert(sox_init() == SOX_SUCCESS);

    in_ = sox_open_read(input_.c_str(), nullptr, nullptr, nullptr);
    out_ = sox_open_write(output_.c_str(), &in_->signal, nullptr, nullptr, nullptr, nullptr);
    chain_ = sox_create_effects_chain(&in_->encoding, &out_->encoding);

    // 输入
    sox_effect_t *e = sox_create_effect(sox_find_effect("input"));
    char *args[10];
    args[0] = (char *) in_;
    logi("in paths is %s:", args[0]);

    int result = sox_effect_options(e, 1, args);
    logi("effect input result is %d", result);
    assert(result == SOX_SUCCESS);
    sox_add_effect(chain_, e, &in_->signal, &in_->signal);
    free(e);

    // 均衡器
    e = sox_create_effect(sox_find_effect("equalizer"));
    char *frequency = "300";
    char *bandwidth = "1.25q";
    char *gain = "3";
    char *equalizer_chars[] = frequency, bandwidth, gain;
    result = sox_effect_options(e, 3, equalizer_chars);
    if (result != SOX_SUCCESS) 
        logi("option equalizer failed : error code is %d", result);
        assert(result == SOX_SUCCESS);
    
    sox_add_effect(chain_, e, &in_->signal, &out_->signal);
    free(e);

    e = sox_create_effect(sox_find_effect("equalizer"));
    frequency = "300";
    bandwidth = "1.25q";
    gain = "30";
    equalizer_chars[0] = frequency;
    equalizer_chars[1] = bandwidth;
    equalizer_chars[2] = gain;
    result = sox_effect_options(e, 3, equalizer_chars);

    if (result != SOX_SUCCESS) 
        logi("option equalizer failed : error code is %d", result);
        assert(result == SOX_SUCCESS);
    
    sox_add_effect(chain_, e, &in_->signal, &out_->signal);
    free(e);

    // 输出
    e = sox_create_effect(sox_find_effect("output"));
    args[0] = (char *) out_;
    result = sox_effect_options(e, 1, args);
    if (result != SOX_SUCCESS) 
        logi("option output effect failed : error code is %d", result);
        assert(result == SOX_SUCCESS);
    
    sox_add_effect(chain_, e, &out_->signal, &out_->signal);
    free(e);

    sox_flow_effects(chain_, nullptr, nullptr);
    sox_delete_effects_chain(chain_);
    sox_close(out_);
    sox_close(in_);
    sox_quit();

sox的设计是一个责任链。使用时,先创建出一个chain,然后创建出需要的effect添加到chain中。上述的例子中为模式为:

input ----> equalizer1 -------> equalizer2 ----------> output

最终可以对比处理过后的音频和原音频的差异。
我这个例子中原音频的频谱为:

处理后为:

遇到的一些问题记录

  1. 编译器不支持Rewind-Pipe,根据注释提示,删掉#error FIX NEEDED HERE就行
formats.c:425:4: error: FIX NEEDED HERE
  #error FIX NEEDED HERE
   ^
6 warnings and 1 error generated.
make[1]: *** [libsox_la-formats.lo] Error 1

改法:

#elif defined _MSC_VER || defined _WIN32 || defined _WIN64 || \\
      defined _ISO_STDIO_ISO_H || defined __sgi
  fp->_ptr = fp->_base;
#else
  /* To fix this #error, either simply remove the #error line and live without
   * file-type detection with pipes, or add support for your compiler in the
   * lines above.  Test with cat monkey.wav | ./sox --info - */
  // #error FIX NEEDED HERE
  #define NO_REWIND_PIPE
  (void)fp;
#endif
  1. 找不到glob、globfree符号。这2个文件是在api28之后引入的,所有需要讲android api 调整为28及以上
copying selected object files to avoid basename conflicts...
  CCLD     sox
ld: error: undefined symbol: glob
>>> referenced by sox.c:2606
>>>               sox.o:(main)

ld: error: undefined symbol: globfree
>>> referenced by sox.c:2612
>>>               sox.o:(main)
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [sox] Error 1
make: *** [all-recursive] Error 1

参考

https://zhuanlan.zhihu.com/p/19763358
http://sox.sourceforge.net/
https://github.com/paramsen/noise
https://howtoeq.wordpress.com/2010/10/07/q-factor-and-bandwidth-in-eq-what-it-all-means/
https://developer.android.com/ndk/guides/other_build_systems

以上是关于音频特效实践的主要内容,如果未能解决你的问题,请参考以下文章

音频特效实践

python 实现wav的波形显示(时域和频域)

音频基础学习三——声音的时频谱

Python绘制语谱图+时域波形

时域缩小2倍,频域怎么变

Win7桌面显示音乐波动 - 音频频谱分析仪