您好,登錄后才能下訂單哦!
小編給大家分享一下C#如何使用NAudio實現音頻可視化,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
預覽:
捕捉聲卡輸出:
實現音頻可視化, 第一步就是獲得音頻采樣, 這里我們選擇使用計算機正在播放的音頻作為采樣源進行處理:
NAudio 中, 可以借助 WasapiLoopbackCapture 來進行捕捉:
WasapiLoopbackCapture cap = new WasapiLoopbackCapture(); cap.DataAvailable += (sender, e) => // 錄制數據可用時觸發此事件, 參數中包含音頻數據 { float[] allSamples = Enumerable // 提取數據中的采樣 .Range(0, e.BytesRecorded / 4) // 除以四是因為, 緩沖區內每 4 個字節構成一個浮點數, 一個浮點數是一個采樣 .Select(i => BitConverter.ToSingle(e.Buffer, i * 4)) // 轉換為 float .ToArray(); // 轉換為數組 // 獲取采樣后, 在這里進行詳細處理 } cap.StartRecording(); // 開始錄制
分離左右通道:
獲取完采樣后, 我們還需要對采樣進行一點小處理, 因為捕獲的數據是分通道的, 一般是左右聲道:
// 設定我們已經將剛剛的采樣保存到了變量 AllSamples 中, 類型為 float[] int channelCount = cap.WaveFormat.Channels; // WasapiLoopbackCapture 的 WaveFormat 指定了當前聲音的波形格式, 其中包含就通道數 float[][] channelSamples = Enumerable .Range(0, channelCount) .Select(channel => Enumerable .Range(0, AllSamples.Length / channelCount) .Select(i => AllSamples[channel + i * channelCount]) .ToArray()) .ToArray();
取通道平均值
將采樣分為一個個通道的采樣后, 我們可以將其合并, 取平均值, 以便于繪制:
// 設定我們已經將分開了的采樣保存到了變量 ChannelSamples 中, 類型為 float[][] // 例如通道數為2, 那么左聲道的采樣為 ChannelSamples[0], 右聲道為 ChannelSamples[1] float[] averageSamples = Enumerable .Range(0, AllSamples.Length / channelCount) .Select(index => Enumerable .Range(0, channelCount) .Select(channel => ChannelSamples[channel][index]) .Average()) .ToArray();
繪制時域圖象:
處理剛剛的采樣后, 你可以直接將其作為數據繪制到窗口中, 這即是時域圖象, 這里使用最簡單的折線繪制.
// 設定 g 為窗口的 Graphics 對象, windowHeight 為窗口的顯示區域高度 // 設定通道采樣平均值為 AverageSamples, 類型為 float[] Point[] points = AverageSamples .Select((v, i) => new Point(i, windowHeight - v)) .ToArray(); // 將數據轉換為一個個的坐標點 g.DrawLines(Pens.Black, points); // 連接這些點, 畫線
傅里葉變換:
NAudio 中還提供了快速傅里葉變換的方法, 通過傅里葉變換, 可以將時域數據轉換為頻域數據, 也就是我們所說的頻譜
// 我們將對 AverageSamples 進行傅里葉變換, 得到一個復數數組 // 因為對于快速傅里葉變換算法, 需要數據長度為 2 的 n 次方, 這里進行 float log = Math.Ceiling(Math.Log(AverageSamples.Length, 2)); // 取對數并向上取整 int newLen = (int)Math.Pow(2, log); // 計算新長度 float[] filledSamples = new float[newLen]; Array.Copy(AverageSamples, filledSamples, AverageSamples.Length); // 拷貝到新數組 Complex[] complexSrc = filledSamples .Select(v => new Complex(){ X = v }) // 將采樣轉換為復數 .ToArray(); FastFourierTransform(false, log, complexSrc); // 進行傅里葉變換 // 變換之后, complexSrc 則已經被處理過, 其中存儲了頻域信息
分析頻域信息:
對于傅里葉變換的頻域信息, 需要稍加處理才可以方便的使用, 首先是提取有用的信息:
// NAudio 的傅里葉變換結果中, 似乎不存在直流分量(這使我們的處理更加方便了), 但它也是有共軛什么的(也就是數據左右對稱, 只有一半是有用的) // 仍然使用剛剛的 complexSrc 作為變換結果, 它的類型是 Complex[] Complex[] halfData = complexSrc .Take(complexSrc.Length / 2) .ToArray(); // 一半的數據 float[] dftData = halfData .Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y)) // 取復數的模 .ToArray(); // 將復數結果轉換為我們所需要的頻率幅度 // 其實, 到這里你完全可以把這些數據繪制到窗口上, 這已經算是頻域圖象了, 但是對于音樂可視化來講, 某些頻率的數據我們完全不需要 // 例如 10000Hz 的頻率, 我們完全沒必要去繪制它, 取 最小頻率 ~ 2500Hz 足矣 // 對于變換結果, 每兩個數據之間所差的頻率計算公式為 采樣率/采樣數, 那么我們要取的個數也可以由 2500 / (采樣率 / 采樣數) 來得出 int count = 2500 / (cap.WaveFormat.SampleRate / filledSamples.Length); float[] finalData = dftData.Take(count).ToArray();
繪制頻域圖象:
得到上面分析后的 finalData 后, 我們就可以直接繪制出來了, 這次使用柔和的曲線繪制
// 設定 g 為窗口的 Graphics 對象, height 為窗口高度 PointF[] points = finalData .Select((v, i) => new PointF(i, height - v)) .ToArray(); g.DrawCurve(Pens.Purple, points); // Graphics 可以直接繪制曲線
更優的繪制:
上面的時域和頻域圖象, 我們都是一股腦的將數據的索引作為 X 坐標, 窗口高度減去數據值作為 Y 坐標, 有兩個突出的問題:
數據可能無法填滿窗口的寬度或者超出窗口的寬度范圍
數據太大時, 也會導致繪制的線條超出窗口高度
第一個問題好解決, 直接使索引所占數據長度的百分比恰好等于 X 坐標相對于窗口寬度的百分比即可:
\[x = index \div dataLength * windowWidth\]
對于第二個問題, 有兩個解決方案, 一是直接為數據加權重, 例如統一乘 0.5, 使數據減小一節, 二就是套一個函數, 例如 log 函數, 畢竟 log 函數在較高自變量的情況下, 因變量的變化趨勢越來越小, 我們只需要對這個 log 函數進行稍加處理, 就可以直接應用到數據變換數據上, 使其不超出窗口繪圖區域
另外, 我們也可以平滑頻譜顯示(指動畫變換), 它的原理大概是這樣:
例如這次進行傅里葉變換的結果是: {0, 100, 50}
,
下一次傅里葉變換的結果是: {100, 0, 0}
,
可以得出, 增量為: {100, -100, -50}
,
在更新變換結果時, 我們不再直接將新的結果替換舊的結果, 而是在舊的結果的基礎上, 加上增量×權重
例如權重是 0.5
時, 那么實際增量是: {50, -50, -25}
,
那么實際新的值是: {50, 50, 25}
,
如果下一次變換的結果還是 {100, 0, 0}
, 那我們再次從 {50, 50, 25}
向新值逼近, 權重仍然是 0.5
, 那么實際增量是: {25, -25, -12.5}
,
注意到了嗎? 這次的增量是上次增量的一半, 這正好是一個減速運動, 而且新值與舊值的差越大, 變化的就越快, 而它們會不斷重合, 因而速度不斷變慢, 形成減速運動的頻譜圖.
以上是“C#如何使用NAudio實現音頻可視化”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。