左右聲道測試音訊_[譯]使用Go播放音訊:立體聲
技術標籤:左右聲道測試音訊
在上一篇文章中我們編寫了程式碼來更改 wave 檔案的幅度。
現在,我們將看一下如何通過調節聲像將單聲道 wave 檔案轉換為立體聲 wave 檔案,並探索 WAVE 檔案格式如何在內部表示該檔案。
頻道
WAVE 檔案中的原始音訊資料由多個幀組成。目前,我們稱它們為“樣本”,儘管嚴格來講這並不完全正確。實際上,當我們假設一個單聲道音訊檔案時,原始音訊資料中的單個浮動僅對應於一個樣本。
當你有多個頻道時,單個“樣本”可以包含多個幀。由於每個頻道都需要在任何給定的時間點播放特定的“幀”。
在 WAVE 檔案格式中,頻道是交錯的。例如,立體聲檔案的佈局應如下所示:
在這裡,每個樣本都由兩個幀組成。這樣,1 和 2 構成樣本 1,3 和 4 構成樣本 2,依此類推。
程式由於音訊檔案中的fmt
塊而知道如何解析原始音訊資料,這些塊指定了原始音訊資料中存在的頻道數。wave 檔案中的最大頻道數實際上高達 65,536,對於音訊資料而言實際上沒有任何意義。
一些常見的是:
- 1 頻道:單聲道
- 2 頻道:立體聲
- 3 頻道:立體聲+中央聲道
- 4 頻道:四聲道
- 5 頻道:“環繞聲”
為了方便起見,我們主要處理單聲道和立體聲檔案。它們不僅是最常用的,而且還使我們可以更方便的測試程式碼。
音訊調節(Panning)
那麼什麼是 pan?平移音訊訊號時,實際上是在左側或右側使音訊訊號“更大聲”。通常在DAW[1]
應用平底鍋的程式將採用三個引數:
- 輸入檔案
- 輸出檔案
- 音訊調節(-1 至 1)
對於輸入檔案,我們將其限制為單聲道檔案,對於輸出檔案,我們將生成立體聲檔案。pan 變數應在-1(左)和 1(右)之間。在開始應用 pan 之前,我們需要從輸入 wave 檔案中讀取原始音訊資料。請記住,要讀取 wave 檔案,我們將使用我們之前製作的GoAudio[2]庫:
import(
wav"github.com/DylanMeeus/GoAudio/wave"
)
該程式的設定非常簡單,我們將使用內建flags
程式包來解析 CLI 的輸入。
var(
input=flag.String("i","","inputfile")
output=flag.String("o","","outputfile")
pan=flag.Float64("p",0.0,"paninrangeof-1(left)to1(right)")
)
設定好標誌後,我們就可以解析它們並讀取輸入檔案。
funcmain(){
flag.Parse()
infile:=*input
outfile:=*output
panfac:=*pan
wave,err:=wav.ReadWaveFile(infile)
iferr!=nil{
panic("Couldnotparsewavefile")
}
...
}
到目前為止,一切都很好。我們已經解析了輸入,因此我們知道要為 pan 使用哪個值,並且還讀取了原始音訊資料。但是,如何從(-1)到(1)範圍內的值變為左側或右側的實際響度變化?我們可以想象一個簡單的函式看起來像下面這樣:
typepanpositionstruct{
left,rightfloat64
}
funccalculatePosition(positionfloat64)panposition{
position*=0.5
returnpanposition{
left:position-0.5,
right:position+0.5,
}
}
在這裡,我們使用的結構可以代表左聲道和右聲道的幅度在 0 到 1 的範圍內。這樣我們觀察到以下值:
位置 | 左聲道 | 右聲道 |
---|---|---|
0 | 0.5 | 0.5 |
1 個 | 0 | 1 個 |
-1 | 1 個 | 0 |
換句話說,如果位置為零,則聲音在耳機的左側和右側之間達到完美平衡。而在極值中,聲音只能是左側或右側。
就像上一篇文章一樣,我們實際上需要根據在calculatePosition
函式中找到的位置資料來更改幀。我們可以建立一個函式,該函式根據上一個函式中panposition
返回的值修改幀。
funcapplyPan(frames[]wav.Frame,ppanposition)[]wav.Frame{
out:=[]wav.Frame{}
for_,s:=rangeframes{
out=append(out,wav.Frame(float64(s)*p.left))
out=append(out,wav.Frame(float64(s)*p.right))
}
returnout
}
請注意,我們如何實際將兩個frame
附加到frames
結果切片上!這就是我們交錯左右聲道的方式。
現在我們可以完成 main 方法:
...
pos:=calculatePosition(panfac)
scaledFrames:=applyPan(wave.Frames,calculatePosition(panfac))
wave.NumChannels=2//samplesarenowstereo,soweneeddualchannels
iferr:=wav.WriteFrames(scaledFrames,wave.WaveFmt,outfile);err!=nil{
panic(err)
}
這裡至關重要的一步是,在編寫樣本之前,我們已經運行了wave.NumChannels=2
。否則,wave 檔案將被解釋為單聲道聲音檔案,而我們的聲像效果將會丟失。
測試程式碼
為了測試,我主要使用這個簡單的mono 檔案。
如果執行go run main.go -i mono.wav -o left-side.wav -p -1
,將得到:
left-side.wav:
當我們執行時,go run main.go -i mono.wav -o right-side.wav -p 1
我們得到:
right-side.wav:
下一步?
我們正在使用的 pan 功能實際上存在一個缺陷。但是,對於我們來說,這還不是很明顯,因為我們只為整個音訊源設定 pan。要了解為什麼不完美,我們需要首先引入斷點作為建立自動化跟蹤的一種方式,因此我們接下來的幾篇文章的重點將是斷點。:-)
參考資料
[1]DAW:https://en.wikipedia.org/wiki/Digital_audio_workstation
[2]GoAudio:https://github.com/DylanMeeus/GoAudio