欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#使用 NAudio 實(shí)現(xiàn)音頻可視化的方法

 更新時(shí)間:2021年05月10日 08:35:06   作者:SlimeNull  
這篇文章主要介紹了[C#] 使用 NAudio 實(shí)現(xiàn)音頻可視化的相關(guān)資料,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

預(yù)覽:

捕捉聲卡輸出:

實(shí)現(xiàn)音頻可視化, 第一步就是獲得音頻采樣, 這里我們選擇使用計(jì)算機(jī)正在播放的音頻作為采樣源進(jìn)行處理:

NAudio 中, 可以借助 WasapiLoopbackCapture 來(lái)進(jìn)行捕捉:

WasapiLoopbackCapture cap = new WasapiLoopbackCapture();
cap.DataAvailable += (sender, e) =>      // 錄制數(shù)據(jù)可用時(shí)觸發(fā)此事件, 參數(shù)中包含音頻數(shù)據(jù)
{
    float[] allSamples = Enumerable      // 提取數(shù)據(jù)中的采樣
        .Range(0, e.BytesRecorded / 4)   // 除以四是因?yàn)? 緩沖區(qū)內(nèi)每 4 個(gè)字節(jié)構(gòu)成一個(gè)浮點(diǎn)數(shù), 一個(gè)浮點(diǎn)數(shù)是一個(gè)采樣
        .Select(i => BitConverter.ToSingle(e.Buffer, i * 4))  // 轉(zhuǎn)換為 float
        .ToArray();    // 轉(zhuǎn)換為數(shù)組
    // 獲取采樣后, 在這里進(jìn)行詳細(xì)處理
}
cap.StartRecording();   // 開始錄制

分離左右通道:

獲取完采樣后, 我們還需要對(duì)采樣進(jìn)行一點(diǎn)小處理, 因?yàn)椴东@的數(shù)據(jù)是分通道的, 一般是左右聲道:

// 設(shè)定我們已經(jīng)將剛剛的采樣保存到了變量 AllSamples 中, 類型為 float[]
int channelCount = cap.WaveFormat.Channels;   // WasapiLoopbackCapture 的 WaveFormat 指定了當(dāng)前聲音的波形格式, 其中包含就通道數(shù)
float[][] channelSamples = Enumerable
    .Range(0, channelCount)
    .Select(channel => Enumerable
        .Range(0, AllSamples.Length / channelCount)
        .Select(i => AllSamples[channel + i * channelCount])
        .ToArray())
    .ToArray();

取通道平均值

將采樣分為一個(gè)個(gè)通道的采樣后, 我們可以將其合并, 取平均值, 以便于繪制:

// 設(shè)定我們已經(jīng)將分開了的采樣保存到了變量 ChannelSamples 中, 類型為 float[][]
// 例如通道數(shù)為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();

繪制時(shí)域圖象:

處理剛剛的采樣后, 你可以直接將其作為數(shù)據(jù)繪制到窗口中, 這即是時(shí)域圖象, 這里使用最簡(jiǎn)單的折線繪制.

// 設(shè)定 g 為窗口的 Graphics 對(duì)象, windowHeight 為窗口的顯示區(qū)域高度
// 設(shè)定通道采樣平均值為 AverageSamples, 類型為 float[]
Point[] points = AverageSamples
    .Select((v, i) => new Point(i, windowHeight - v))
    .ToArray();   // 將數(shù)據(jù)轉(zhuǎn)換為一個(gè)個(gè)的坐標(biāo)點(diǎn)
g.DrawLines(Pens.Black, points);   // 連接這些點(diǎn), 畫線

傅里葉變換:

NAudio 中還提供了快速傅里葉變換的方法, 通過(guò)傅里葉變換, 可以將時(shí)域數(shù)據(jù)轉(zhuǎn)換為頻域數(shù)據(jù), 也就是我們所說(shuō)的頻譜

// 我們將對(duì) AverageSamples 進(jìn)行傅里葉變換, 得到一個(gè)復(fù)數(shù)數(shù)組

// 因?yàn)閷?duì)于快速傅里葉變換算法, 需要數(shù)據(jù)長(zhǎng)度為 2 的 n 次方, 這里進(jìn)行
float log = Math.Ceiling(Math.Log(AverageSamples.Length, 2));   // 取對(duì)數(shù)并向上取整
int newLen = (int)Math.Pow(2, log);                             // 計(jì)算新長(zhǎng)度
float[] filledSamples = new float[newLen];
Array.Copy(AverageSamples, filledSamples, AverageSamples.Length);   // 拷貝到新數(shù)組
Complex[] complexSrc = filledSamples
    .Select(v => new Complex(){ X = v })        // 將采樣轉(zhuǎn)換為復(fù)數(shù)
    .ToArray();
FastFourierTransform(false, log, complexSrc);   // 進(jìn)行傅里葉變換

// 變換之后, complexSrc 則已經(jīng)被處理過(guò), 其中存儲(chǔ)了頻域信息

分析頻域信息:

對(duì)于傅里葉變換的頻域信息, 需要稍加處理才可以方便的使用, 首先是提取有用的信息:

// NAudio 的傅里葉變換結(jié)果中, 似乎不存在直流分量(這使我們的處理更加方便了), 但它也是有共軛什么的(也就是數(shù)據(jù)左右對(duì)稱, 只有一半是有用的)
// 仍然使用剛剛的 complexSrc 作為變換結(jié)果, 它的類型是 Complex[]

Complex[] halfData = complexSrc
    .Take(complexSrc.Length / 2)
    .ToArray();    // 一半的數(shù)據(jù)
float[] dftData = halfData
    .Select(v => Math.Sqrt(v.X * v.X + v.Y * v.Y))  // 取復(fù)數(shù)的模
    .ToArray();    // 將復(fù)數(shù)結(jié)果轉(zhuǎn)換為我們所需要的頻率幅度

// 其實(shí), 到這里你完全可以把這些數(shù)據(jù)繪制到窗口上, 這已經(jīng)算是頻域圖象了, 但是對(duì)于音樂(lè)可視化來(lái)講, 某些頻率的數(shù)據(jù)我們完全不需要
// 例如 10000Hz 的頻率, 我們完全沒(méi)必要去繪制它, 取 最小頻率 ~ 2500Hz 足矣
// 對(duì)于變換結(jié)果, 每?jī)蓚€(gè)數(shù)據(jù)之間所差的頻率計(jì)算公式為 采樣率/采樣數(shù), 那么我們要取的個(gè)數(shù)也可以由 2500 / (采樣率 / 采樣數(shù)) 來(lái)得出
int count = 2500 / (cap.WaveFormat.SampleRate / filledSamples.Length);
float[] finalData = dftData.Take(count).ToArray();

繪制頻域圖象:

得到上面分析后的 finalData 后, 我們就可以直接繪制出來(lái)了, 這次使用柔和的曲線繪制

// 設(shè)定 g 為窗口的 Graphics 對(duì)象, height 為窗口高度
PointF[] points = finalData
    .Select((v, i) => new PointF(i, height - v))
    .ToArray();
g.DrawCurve(Pens.Purple, points);    // Graphics 可以直接繪制曲線

更優(yōu)的繪制:

上面的時(shí)域和頻域圖象, 我們都是一股腦的將數(shù)據(jù)的索引作為 X 坐標(biāo), 窗口高度減去數(shù)據(jù)值作為 Y 坐標(biāo), 有兩個(gè)突出的問(wèn)題:

  • 數(shù)據(jù)可能無(wú)法填滿窗口的寬度或者超出窗口的寬度范圍
  • 數(shù)據(jù)太大時(shí), 也會(huì)導(dǎo)致繪制的線條超出窗口高度

第一個(gè)問(wèn)題好解決, 直接使索引所占數(shù)據(jù)長(zhǎng)度的百分比恰好等于 X 坐標(biāo)相對(duì)于窗口寬度的百分比即可:

\[x = index \div dataLength * windowWidth\]

對(duì)于第二個(gè)問(wèn)題, 有兩個(gè)解決方案, 一是直接為數(shù)據(jù)加權(quán)重, 例如統(tǒng)一乘 0.5, 使數(shù)據(jù)減小一節(jié), 二就是套一個(gè)函數(shù), 例如 log 函數(shù), 畢竟 log 函數(shù)在較高自變量的情況下, 因變量的變化趨勢(shì)越來(lái)越小, 我們只需要對(duì)這個(gè) log 函數(shù)進(jìn)行稍加處理, 就可以直接應(yīng)用到數(shù)據(jù)變換數(shù)據(jù)上, 使其不超出窗口繪圖區(qū)域

另外, 我們也可以平滑頻譜顯示(指動(dòng)畫變換), 它的原理大概是這樣:

  • 例如這次進(jìn)行傅里葉變換的結(jié)果是: {0, 100, 50},
  • 下一次傅里葉變換的結(jié)果是: {100, 0, 0},
  • 可以得出, 增量為: {100, -100, -50},
  • 在更新變換結(jié)果時(shí), 我們不再直接將新的結(jié)果替換舊的結(jié)果, 而是在舊的結(jié)果的基礎(chǔ)上, 加上增量×權(quán)重
  • 例如權(quán)重是 0.5 時(shí), 那么實(shí)際增量是: {50, -50, -25},
  • 那么實(shí)際新的值是: {50, 50, 25},
  • 如果下一次變換的結(jié)果還是 {100, 0, 0}, 那我們?cè)俅螐?{50, 50, 25} 向新值逼近, 權(quán)重仍然是 0.5, 那么實(shí)際增量是: {25, -25, -12.5},

注意到了嗎? 這次的增量是上次增量的一半, 這正好是一個(gè)減速運(yùn)動(dòng), 而且新值與舊值的差越大, 變化的就越快, 而它們會(huì)不斷重合, 因而速度不斷變慢, 形成減速運(yùn)動(dòng)的頻譜圖.

更多內(nèi)容:

更多關(guān)于 NAudio 的使用, 可以看這篇文章:[C#] NAudio 的各種常見使用方式 播放 錄制 轉(zhuǎn)碼 音頻可視化

項(xiàng)目已開源:

關(guān)于本文章涉及的大部分內(nèi)容, 均在 github.com/SlimeNull/AudioTest 倉(cāng)庫(kù)中的 Null.AudioVisualizer 項(xiàng)目中有寫. (注釋妥當(dāng)了)

其實(shí)音頻可視化我老早就想做了, 但是本人數(shù)學(xué)不是非常的好, 不過(guò)最后總算是堅(jiān)持下來(lái)了, 弄出來(lái)了啊, 心情老激動(dòng)了

到此這篇關(guān)于[C#] 使用 NAudio 實(shí)現(xiàn)音頻可視化的文章就介紹到這了,更多相關(guān)C#音頻可視化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#函數(shù)式編程中的遞歸調(diào)用之尾遞歸詳解

    C#函數(shù)式編程中的遞歸調(diào)用之尾遞歸詳解

    這篇文章主要介紹了C#函數(shù)式編程中的遞歸調(diào)用詳解,本文講解了什么是尾遞歸、尾遞歸的多種方式、尾遞歸的代碼實(shí)例等內(nèi)容,需要的朋友可以參考下
    2015-01-01
  • C# MemoryStream類案例詳解

    C# MemoryStream類案例詳解

    這篇文章主要介紹了C# MemoryStream類案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • C#中環(huán)境變量示例詳解

    C#中環(huán)境變量示例詳解

    環(huán)境變量是操作系統(tǒng)中存儲(chǔ)的一種機(jī)制,用于保存與操作系統(tǒng)環(huán)境和應(yīng)用程序運(yùn)行相關(guān)的配置信息,在 C# 中,可以使用 Environment.GetEnvironmentVariable 方法來(lái)獲取特定環(huán)境變量的值,下面給大家介紹C#中環(huán)境變量示例代碼,一起看看吧
    2024-05-05
  • .NET Core開發(fā)之配置詳解

    .NET Core開發(fā)之配置詳解

    這篇文章給大家分享了.NET Core開發(fā)中相關(guān)配置的知識(shí)點(diǎn)內(nèi)容,有需要的朋友們可以參考下。
    2018-08-08
  • C#并行庫(kù)Task類介紹

    C#并行庫(kù)Task類介紹

    這篇文章介紹了C#并行庫(kù)Task類,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • C# yield在WCF中的錯(cuò)誤使用(二)

    C# yield在WCF中的錯(cuò)誤使用(二)

    這篇文章主要介紹了C# yield在WCF中的錯(cuò)誤使用(二),本文講解的內(nèi)容據(jù)說(shuō)是99%的開發(fā)人員都有可能犯的錯(cuò)誤,需要的朋友可以參考下
    2015-04-04
  • C#開發(fā)微信門戶及應(yīng)用(5) 用戶分組信息管理

    C#開發(fā)微信門戶及應(yīng)用(5) 用戶分組信息管理

    這篇文章主要為大家詳細(xì)介紹了C#開發(fā)微信門戶及應(yīng)用第五篇,用戶分組信息管理,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • WinForm實(shí)現(xiàn)自定義右下角提示效果的方法

    WinForm實(shí)現(xiàn)自定義右下角提示效果的方法

    這篇文章主要介紹了WinForm實(shí)現(xiàn)自定義右下角提示效果的方法,涉及WinForm自定義提示效果的實(shí)現(xiàn)方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-08-08
  • C# 創(chuàng)建高精度定時(shí)器的示例

    C# 創(chuàng)建高精度定時(shí)器的示例

    這篇文章主要介紹了C# 創(chuàng)建高精度定時(shí)器的示例,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下
    2021-02-02
  • C#實(shí)現(xiàn)拼圖游戲

    C#實(shí)現(xiàn)拼圖游戲

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)拼圖游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07

最新評(píng)論