1. 程式人生 > 其它 >左右聲道測試音訊_[譯]使用Go播放音訊:立體聲

左右聲道測試音訊_[譯]使用Go播放音訊:立體聲

技術標籤:左右聲道測試音訊

在上一篇文章中我們編寫了程式碼來更改 wave 檔案的幅度。

現在,我們將看一下如何通過調節聲像將單聲道 wave 檔案轉換為立體聲 wave 檔案,並探索 WAVE 檔案格式如何在內部表示該檔案。

頻道

WAVE 檔案中的原始音訊資料由多個幀組成。目前,我們稱它們為“樣本”,儘管嚴格來講這並不完全正確。實際上,當我們假設一個單聲道音訊檔案時,原始音訊資料中的單個浮動僅對應於一個樣本。

當你有多個頻道時,單個“樣本”可以包含多個幀。由於每個頻道都需要在任何給定的時間點播放特定的“幀”。

在 WAVE 檔案格式中,頻道是交錯的。例如,立體聲檔案的佈局應如下所示:

f3473f2c790cf9ee708a017cb19a0f32.png
image

在這裡,每個樣本都由兩個幀組成。這樣,1 和 2 構成樣本 1,3 和 4 構成樣本 2,依此類推。

程式由於音訊檔案中的fmt塊而知道如何解析原始音訊資料,這些塊指定了原始音訊資料中存在的頻道數。wave 檔案中的最大頻道數實際上高達 65,536,對於音訊資料而言實際上沒有任何意義。

一些常見的是:

  • 1 頻道:單聲道
  • 2 頻道:立體聲
  • 3 頻道:立體聲+中央聲道
  • 4 頻道:四聲道
  • 5 頻道:“環繞聲”

為了方便起見,我們主要處理單聲道和立體聲檔案。它們不僅是最常用的,而且還使我們可以更方便的測試程式碼。

音訊調節(Panning)

那麼什麼是 pan?平移音訊訊號時,實際上是在左側或右側使音訊訊號“更大聲”。通常在DAW[1]

中由“自動化軌道”表示,其值-1 到 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 的範圍內。這樣我們觀察到以下值:

位置左聲道右聲道
00.50.5
1 個01 個
-11 個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