• 生成特定分贝的音频波形


    在处理音频的时候的有时候需要特定分贝(如-10dB)的音频波形,本文主要介绍如何生成特定分贝数的音频文件。有以下几个方面:

    • 简单的生成特定分贝的波形
      • 模拟频率和数字频率
      • 波形生成
    • 代码的封装
      • 正弦波、方形波、锯齿波、三角波的生成
      • 生成特定分贝特定形状的波形

    简单的生成特定分贝的波形

    波形可以通过一个周期内幅度值的变化来描述,所以要生成指定的波形就要知道两个量:周期(频率)和幅度的变化值。数字信号通常由模拟信号采样得到,而通常所说的频率也是模拟频率,所以首先要搞清楚模拟频率、数字频率,采样率这些量之间的关系。

    数字频率和模拟频率

    通常所说的频率为模拟频率,其单位为赫兹Hz,表示每秒信号变化的周期数。以单位圆为例,旋转一圈表示信号变化一个周期(产生一个正弦或者余弦波形),则模拟频率指的是每秒钟圆旋转的圈数。1000Hz,就是1秒钟圆旋转了1000圈(1秒钟有1000个正弦或者余弦曲线)。如下图:

    单位圆旋转一周,在水平方向产生一个正弦波;竖直方向产生一个余弦波。

    模拟角频率,仍然以单位圆为例,频率是单位时间内单位圆旋转的圈数,每旋转一圈单位圆旋转的角度是 (2 * pi)。 频率为f的波,表示一秒钟旋转f圈,角度变化就是 (2 * f * pi),故模拟角频率就是 (2 * f * pi rad /s)

    数字信号通常有模拟信号采样而来,采样频率 指的是单位时间内提取到的样本的个数,由奈奎斯特采样定理知道,要完全的保留模拟信号的信息,就需要采样频率大于等于模拟信号中最高频率的2倍。

    数字频率,更准确叫法应该是归一化角频率,其单位为弧度(rad),表达式为:(2 * pi * f / fs),其中f为频率,fs为采样率。物理意义为相邻两个采样点之间变化的弧度数。

    现在假设有个模拟的正弦信号x[t],其模拟频率为f=1000Hz,幅度为A,初始相位为0,则该信号的表达式为:(x[t] = A * sin(2pi * f * t) = Asin(2000*pi*t))
    以采样率fs = 5000对其进行采样,得到数字信号x[n],则采样得到的数字信号的表达式i为:(x[n] = A * sin(2pi * f / fs * t) = Asin(0.4pi*n))。可以看出数字频率为0.4pi,也就是每隔0.4pi弧度取得一个sample。初始相位为0,则该数字信号的幅度序列为:(Asin(0),Asin(0.4pi),Asin(0.4 * 2 * pi),Asin(0.4 * 3 * pi),...,Asin(0.4*n*pi))。这一系列离散的点组成的数字信号其对应的模拟的信号就是(x[t] = Asin(2000*pi*t))。也就说,要想生成特定频率,特定幅度(幅度和分贝有转换关系)的波形,只需要知道其数字频率就可以了。

    总结:
    模拟频率f表示单位时间内信号变化的周期数,单位是赫兹Hz;模拟角频率$Omega = 2f pi (,表示单位时间内信号变化的角度,单位是rad/s; 采样率fs表示单位时间内采样得到的样本数;数字频率,归一化角频率)omega=2fpi/fs$,表示采样时相邻两个样本间变化的弧度数。
    由以上可知,即使两个数字频率完全相同的数字信号,其对应的模拟信号缺不一定相同,还需要考虑到采样率。而且采样率是模拟信号和数字信号之间进行转换的桥梁。

    生成特定分贝的波形

    从上面可以知道,要生成指定频率和分贝的波形,需要两个量:

    • 数字频率,相邻两个采样点之间变化的弧度数。 该值可以由模拟频率和采样率得到 (2fpi / fs)
    • 幅度值,幅度值可通过分贝dB换算得到。 (dB = 20 * log(A) o A = 10 ^ {db/20}),这里幅值A归一化到[-1,1]。关于分贝和幅值之间的关系可以参考声音分贝的概念,dBSPL.dBm,dBu,dBV,dBFS

    现假设要生成-10dB,频率为1000Hz的正弦波形,其采样率为48000,有下面代码:

    double f = 1000;
    double fs = 48000
    double db = -10.0f;
    double duration = 10;
    double incr = 2 * pi * f / fs ;// 数字频率,也是相邻两个采样点的变化的弧度
    double A = powf(10,db / 20); // 波形的最大幅度值
    float* frame = new float[static_cast<int>(duration * fs)];
    for(int i=0; i < static_cast<int>(duration * fs); i ++)
        frame[i] = A * sin(i * incr);
    

    有了上面模拟频率和数字频率之间的转换关系后,上面代码还是比较简单明了的。首先通过模拟频率和采样率计算出数字频率,也就是相邻两个采样点之间的变化的弧度;然后,根据分贝数和幅度之间关系计算出波形的最大幅度值(这里说明下,音频的分贝计算通常取一段时间内(例如50ms)样本值的最大值(Peak值)。关于音频音量的度量,有机会会单独介绍)。最后,for循环计算各个sample的值,生成波形。如下图得到一个周期内的样本值:

    代码封装

    使用上面不到10行的代码就可以生成一个指定频率,指定分贝的正弦波形了。但是,上述代码实在太简单,下面就使用C++的类,将上面不到10行的代码编变成200行。

    生成各种形状的波

    标准的波形除了正弦波外,还有方形波、三角波、锯齿波等。如下图:

    首先,声明一个classOscillator,其功能就是根据频率和采样率以及选择的波形形状,连续的产生波形的sample值。有以下的字段:

        double sampleRate; // 采样率
        double twoPIdivSamplerate; // 2 * pi / sampleRate
        double curFreq; // 当前频率
        double curPhase; // 当前相位
        double incrSample; // 每个sample增长的值
    
        // 正弦波
        double sinetick(double freq)
        {
            auto val = sin(curPhase);
            updateFreq(freq);
            updatePhase();
            return val;
        }
    

    sinetick生成正弦波的sample。该函数需要波形的频率作为参数,在生成返回当前的sample后。根据传入的频率不同,更新相邻sample的变化值,为生成下一个sample做准备。

       void updateFreq(double freq)
        {
            if (curFreq != freq)
            {
                curFreq = freq;
                incrSample = twoPIdivSamplerate * freq;
            }
        }
        void updatePhase()
        {
            curPhase += incrSample;
            if (curPhase >= 2 * pi)
                curPhase -= 2 * pi;
            else if (curPhase < 0.0)
                curPhase += 2 * pi;
        }
    

    除了sinetick外,还有squaretick生成方形波的sample;triangletick,生成三角波的sample;sawtoothDownTick,生成向下的锯齿波的sample;sawtoothUpTick生成向上的锯齿波的sample。

    生成特定分贝的波形

    使用class Oscillator可以生成诸如(sin(2f/fs cdot pi))的波形,但是还缺少一个对波形幅值的缩放系数,来生成特定分贝的波形。下面再定一个class AudioGenerator,该类的主要功能是能够 生成不同形状的指定分贝的波形,对于sample的类型也有三种选择:16位的有符号整型、32位有符号整型以及单精度浮点数。

        float GenerateFloat_32(double decibel, double freq, WavformType wavType = WavformType::SIN)
        {
            auto amplitude = powf(10.0, decibel / 20); // 幅度
            double val;
            val = value(freq, wavType);
            amplitude *= val;
            if (amplitude > 1.0f)
                amplitude = 1.0f;
            else if (amplitude < -1.0f)
                amplitude = -1.0f;
            return static_cast<float>(amplitude);
        }
    

    上面方法是生成单精度浮点数的sample。首先根据分贝数,计算得到波形的最大幅度值;value函数根据选择波形形状的不同,调用Oscialltor中的不同波形的生成方法,对得到的sample使用前面最大幅值进行缩放。

    使用以及wav文件的保存

    代码基本已经完成了,接下来就是将生成的波形保存为wav文件了。对于wav文件读写,在前面有个介绍C++标准库实现WAV文件读写,但是后来在使用SoundTouch这个变调变速的库的时候,发现其带的WavOutFileWavInFile用着挺方便的,这是就是用其来保存wav文件 。

       int db = -10;
        float amplitude = powf(10.0, static_cast<float>(db) / 20);
        int f = 1000; // 信号的模拟频率为1000Hz
        int fs = 48000; // 采样频率为48000Hz
        int duration = 10; //生成10s的信号
        AudioGenerator gen(fs);
        WavOutFile *outFile;
        float *outFrame = new float[duration * fs];
        outFile = new WavOutFile("-10db_sin.wav", fs, 32, 1);
        for (int i = 0; i < fs * duration; i++)
            outFrame[i] = gen.GenerateFloat_32(-10, f);
        outFile->write(outFrame, fs * duration);
        delete outFile;
    

    代码很简单,就不做过多的解释了。生成-10dB各种波形的结果

    总结

    本文主要介绍了如何生成指定分贝的标准信号,正弦波、方形波、三角波、锯齿波等。对于波形的生成,首先要弄清楚模拟频率和数字频率之间的关系。

    • 模拟频率f,单位时间内信号变化的周期数
    • 模拟角频率,单位时间内信号变化的弧度,单位 rad/s
    • 采样率fs,单位时间内采样得到的样本数
    • 数字频率 (2f/fs cdot pi),相邻两个样本间变化的弧度数。

    本文使用的源代码 http://download.csdn.net/detail/brookicv/9715390

  • 相关阅读:
    windows下前端开发工具遇到的问题总结(yeoman bower grunt)
    gurnt js css 压缩合并
    PyCharm中的Console调整字体大小
    PyCharm中的Console自动换行
    requests模块demo
    http状态码
    查找入职员工时间排名倒数第三的员工所有信息
    查找最晚入职员工的所有信息
    xrange与range之间的区别
    给出每个员工每年薪水涨幅超过5000的员工编号emp_no、薪水变更开始日期from_date以及薪水涨幅值salary_growth,并按照salary_growth逆序排列。
  • 原文地址:https://www.cnblogs.com/wangguchangqing/p/6197590.html
Copyright © 2020-2023  润新知