在處理音頻的時候的有時候需要特定分貝(如-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(2\pi * f * t) = Asin(2000*\pi*t)\)
以采樣率fs = 5000對其進行采樣,得到數字信號x[n],則采樣得到的數字信號的表達式i為:\(x[n] = A * sin(2\pi * f / fs * t) = Asin(0.4pi*n)\)。可以看出數字頻率為0.4pi,也就是每隔0.4pi弧度取得一個sample。初始相位為0,則該數字信號的幅度序列為:\(Asin(0),Asin(0.4\pi),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=2f\pi/fs$,表示采樣時相鄰兩個樣本間變化的弧度數。
由以上可知,即使兩個數字頻率完全相同的數字信號,其對應的模擬信號缺不一定相同,還需要考慮到采樣率。而且采樣率是模擬信號和數字信號之間進行轉換的橋梁。
生成特定分貝的波形
從上面可以知道,要生成指定頻率和分貝的波形,需要兩個量:
- 數字頻率,相鄰兩個采樣點之間變化的弧度數。 該值可以由模擬頻率和采樣率得到 \(2f\pi / fs\)
- 幅度值,幅度值可通過分貝dB換算得到。 \(dB = 20 * \log(A) \to 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
這個變調變速的庫的時候,發現其帶的WavOutFile
和WavInFile
用着挺方便的,這是就是用其來保存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\),相鄰兩個樣本間變化的弧度數。