CSCore - Highperformance Audiobibliothek

    • Release
    • Open Source

    Es gibt 589 Antworten in diesem Thema. Der letzte Beitrag () ist von simpelSoft.

      Das was du willst ist eine Fouriertransformation. Dies gibt es ja. Dazu um diese zu berechnen brauchst du jedoch den Datenstrom der einzelnen Samples. Wenn du diesen von einem kompletten Gerät haben möchtest, musst du dies zuerst aufnehmen(über Loopbackrecording). Deshalb müsste ich zuerst wissen von was genau du diese Fourieranalyse willst. Aber damit wir uns auch wirklich verstehen. Du willst sowas wie auf dem Screenshot hier(ist ne Demo von CSCore - werd demnächst mal hochladen):s1.directupload.net/images/131010/3lw9l2wo.png


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Ich habe zur Geräteauswahl einfach mal den Code von hier benutzt: cscore.codeplex.com/discussions/454485
      Danach habe ich einen Timer gestartet, welcher über die Funktion GetChannelsPeakValues von dem in dem Code deklarierten AudioMeter abruft. Hier mal der Code:

      VB.NET-Quellcode

      1. Imports CSCore
      2. Imports CSCore.CoreAudioAPI
      3. Public Class Form1
      4. Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
      5. Debug.WriteLine(String.Join(" | ", AudioMeter.GetChannelsPeakValues().Select(Function(s) s.ToString("0.00")).ToArray))
      6. End Sub
      7. Private AudioMeter As AudioMeterInformation
      8. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
      9. 'AudioMeter festlegen
      10. Using Enumerator As New MMDeviceEnumerator()
      11. Using Devices As MMDeviceCollection = Enumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active)
      12. Dim Device As MMDevice = Devices(0)
      13. AudioMeter = AudioMeterInformation.FromDevice(Device)
      14. End Using
      15. End Using
      16. Timer1.Start()
      17. End Sub
      18. End Class

      Das auf dem Screenshot scheint übrigens das richtige zu sein :).
      Naja gut. Um an die Frequenzen zu kommen musst du eine Kombination aus dem was ich vorhin Myrax gepostet habe und dem [OpenSource][C#] CSCore - C-Sharp-Sound - Highperformance Audiobibliothek. Anstatt von Effekten wie Myrax musst du die FFTAggregator Klasse verwenden(CSCore.DSP namespace). Diese feuert ein Event FFTCalculated. Dort drinnen befinden sich komplexe Zahlen mit den jeweiligen Werten. Für dich interessant ist jedoch immer nur die erste Hälfte der Werte. Heißt 1024/2 = die ersten 512 Werte. Ich würde dir natürlich auch ein Beispiel liefern, jedoch bin ich gerade bei einem anderen Projekt etwas in Zeitdruck und deshalb musst du dich noch etwas gedulden(ich schau mal vll. finde ich heute noch kurz Zeit).


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Ich habe es gerade mal versucht, leider verstehe ich überhaupt nicht, was genau ich tun soll :S (kenne mich mit dem Thema gar nicht aus). Könntest du mir vielleicht ein konkreteres Codebeispiel geben, welches auch für einen Anfänger auf dem Gebiet verständlich ist?
      Du siehst ja das Codebeispiel, welches thefiloe mir gegeben hat. Dort ist ein Echo-Effekt drinne. Doch statt dem Echo-Effekt erstellst du eine Instanz der Klasse FFTAggregator, zu finden im CSCore.DSP-Namespace). Da hast du ein Event, welche die angeforderten Werte beinhalten. Wichtig, wie thefiloe bereits sagte, nur die Hälfte der Werte benutzen, der Rest ist uninteressant. Ich geh mal davon aus, du weißt, wie man einen Eventhandler hinzufügt, richtig?
      Natürlich weiß ich, wie das mit den Events funktioniert :D. Jedoch weiß ich nicht, wie ich meine IWaveSource erstellen soll. Ich habe es mal so probiert:

      VB.NET-Quellcode

      1. Dim SoundIn As ISoundIn = Nothing
      2. Dim SoundInSource As IWaveSource = New SoundInSource(SoundIn)
      3. Private WithEvents Aggregator As FFTAggregator = New FFTAggregator(SoundInSource)

      Jedoch startet mir so das Programm nicht mehr, eine InvalidOperationException tritt auf:

      InnerException schrieb:

      "Der Wert darf nicht NULL sein.
      Parametername: soundIn"

      Somit ist klar, dass an meiner Initialisation von SoundIn nicht stimmt, dass ich es mit Nothing als Wert initialisiere. Jetzt weiß ich halt nicht, woher ich mein ISoundIn herbekomme.
      Außerdem ist mir nicht ganz klar, was ich dann mit den 512 Werten machen soll. Zum Testen habe ich zwar mal diesen Code geschrieben, jedoch funktioniert er ja wegen dem oben genannten Fehler nicht:

      VB.NET-Quellcode

      1. Private Sub Aggregator_FFTCalculated(sender As Object, e As FFTCalculatedEventArgs) Handles Aggregator.FFTCalculated
      2. 'Die ersten 512 Werte abspeichern und den Real in einen Single umwandeln
      3. Dim Values As IEnumerable(Of Single) = e.Data.Take(512).Select(Function(c) c.Value)
      4. 'Testausgabe, Form: 'Output: {0.23, 0.00, 0.01, [...] }'
      5. Debug.WriteLine(String.Format("Output: {{{0}}}", String.Join(", ", Values.Select(Function(s) s.ToString("0.00")).ToArray)))
      6. End Sub
      Könntest du mir das vielleicht etwas genauer erklären? Wie ich schon vorher erwähnt habe, ich kenne mich mit der Technik hinter dem ganzen Soundzeugs nicht wirklich aus.
      Also das ISoundIn interface wird von allen Aufnahmesachen implementiert(ähnlich wie SoundOut). Jetzt kommt es darauf an was du aufnehmen möchtest. Wie bereits richtiger weise gesagt wurde ich WasapiLoopbackCapture für die Aufnahme eines Ausgabegerätes zuständig(z.B. Boxen, Kopfhörer,...). Wenn du soundIn Nothing zuweist ist es natürlich verständlich, dass ein Fehler auftritt. Der Trick besteht darin, der soundIn variable etwas zuzuweisen von wem aus Daten gelesen werden können. Dies könnte z.b. eben das WasapiLoopbackCapture sein oder aber auch einfach nur ein Micro. Im einfachsten Fall würde es reichen soundIn = New WasapiLoopbackCapture() zu verwenden. Anschließend musst du nur noch Initialize() zum initialisieren der Aufnahme aufrufen. Zum Starten der Aufnahme noch Start() aufrufen und das teil wird Daten liefern.

      Konkret heißt dies um eine Aufnahme des Defaultausgabegerätes zu starten brauchst du folgendes:

      VB.NET-Quellcode

      1. soundIn = New WasapiLoopbackCapture()
      2. soundIn.Initialize()
      3. soundIn.Start()


      Nicht vergessen beim Stoppen oder Beenden des Programmes die Ressourcen wieder freizugeben:

      VB.NET-Quellcode

      1. soundIn.Stop()
      2. soundIn.Dispose()


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Ok, ich habe es jetzt mal so versucht, jedoch kriege ich gar keinen Output ?(:

      VB.NET-Quellcode

      1. Private Sub Aggregator_FFTCalculated(sender As Object, e As FFTCalculatedEventArgs)
      2. 'Die ersten 512 Werte abspeichern und den Real in einen Single umwandeln
      3. Dim Values As IEnumerable(Of Single) = e.Data.Take(512).Select(Function(c) c.Value)
      4. 'Testausgabe, Form: 'Output: {0.23, 0.00, 0.01, [...] }'
      5. Debug.WriteLine(String.Format("Output: {{{0}}}", String.Join(", ", Values.Select(Function(s) s.ToString("0.00")).ToArray)))
      6. End Sub
      7. Dim SoundIn As ISoundIn
      8. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
      9. SoundIn = New WasapiLoopbackCapture
      10. SoundIn.Initialize()
      11. SoundIn.Start()
      12. Dim SoundInSource = New SoundInSource(SoundIn)
      13. Dim Aggregator = New FFTAggregator(SoundInSource)
      14. AddHandler Aggregator.FFTCalculated, AddressOf Aggregator_FFTCalculated
      15. End Sub
      16. Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
      17. SoundIn.Stop()
      18. SoundIn.Dispose()
      19. End Sub

      Was mache ich jetzt noch falsch?
      Das ist richtig. Dazu muss man die Lib etwas tiefer verstehen. Sie basiert quasi auf Komponenten welche du verketten kannst. Jede Komponente wird nacheinander abgearbeitet. So wird aus einer Binären MP3-Datei ein PCM Stream, dann kommt ein Effekt drauf, ... bis es zum Lautsprecher geht. Doch der Knackpunkt ist, dass man weiß, wie dieser ganze Prozess ausgelöst wird. Und zwar wird er ausgelöst indem man die Read-Methode einer solchen Komponente aufruft. Bei einer Ausgabe geschieht dies automatisch und der Anwender bekommt nichts davon mit. Hier musst dies selbst machen. Erst dann werden die aufgenommenen Daten durch die ganzen Komponenten geschläust und somit auch die Events gefeuert.
      Dies klingt jedoch alles viel komplizierter als es tatsächlich ist. Um dies zu machen füge am Besten einfach einen EventHandler der SoundIn variable hinzu(dem DataAvailable-Event). Dieses wird gefeuert, wenn neue Daten da sind(alternativ kannst du das auch mit einem Timer machen, jedoch ist das Event wesentlich sauberer und auch performanter). Ein Beispiel dazu findest du hier:
      cscore.codeplex.com/SourceCont…es/Recorder/MainWindow.cs

      Ich hab mal versucht das so halbwegs grob für dich anzupassen(nur in notepad):

      VB.NET-Quellcode

      1. Private _buffer As Byte()
      2. Private Sub Aggregator_FFTCalculated(sender As Object, e As FFTCalculatedEventArgs)
      3. 'Die ersten 512 Werte abspeichern und den Real in einen Single umwandeln
      4. Dim Values As IEnumerable(Of Single) = e.Data.Take(512).Select(Function(c) c.Value)
      5. 'Testausgabe, Form: 'Output: {0.23, 0.00, 0.01, [...] }'
      6. Debug.WriteLine(String.Format("Output: {{{0}}}", String.Join(", ", Values.Select(Function(s) s.ToString("0.00")).ToArray)))
      7. End Sub
      8. Dim SoundIn As ISoundIn
      9. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
      10. SoundIn = New WasapiLoopbackCapture
      11. SoundIn.Initialize()
      12. Dim SoundInSource = New SoundInSource(SoundIn)
      13. Dim Aggregator = New FFTAggregator(SoundInSource)
      14. _buffer = New Byte(Aggregator.WaveFormat.BytesPerSecond) {}
      15. AddHandler SoundInSource.DataAvailable, AddressOf OnNewData
      16. AddHandler Aggregator.FFTCalculated, AddressOf Aggregator_FFTCalculated
      17. SoundIn.Start() 'würde ich erst starten, wenn alles fertig init. ist
      18. End Sub
      19. Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
      20. SoundIn.Stop()
      21. SoundIn.Dispose()
      22. End Sub
      23. Private Sub OnNewData(sender As Object, e As DataAvailableEventArgs)
      24. While _source.Read(_buffer, 0, _buffer.Length) > 0
      25. 'mit den daten musst du nichts machen(es sei denn du willst sie speichern etc...)
      26. End While
      27. End Sub


      Ansonsten sieht das Ganze aber schon recht gut aus.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Aso sorry. Das habe ich nicht ausgebessert(habe teile des Beispiels auf Codeplex konvertiert). In deinem Fall musst du dies durch deinen Aggregator ersetzen(also den als Private deklarieren damit du dort auf ihn zugreifen kannst oder LambdaExpressions verwenden). Generell zum Verständnis. Sagen wir du hast 3 Elemente: MP3-Datei -> Echo-Effekt -> Equalizer. Liest du vom Echo-Effekt so wird erst von der MP3-Datei gelesen und dann das Echo angewandt und du bekommst als Resultat nen Teil der MP3 + Echo. Liest du vom Equalizer so hast das selbe wie beim Echo nur + nen Equalizer. Liest du nur von der MP3 so hast du eben nur die MP3-Daten ohne alles. So ist es hier auch. Liest du vom Aggregator so wird automatisch erst von der SoundInSource gelesen, welche wiederum Daten von der Aufnahme puffert. Anschließend wird ne Fouriertransformation mit den gelesenen Daten gemacht, und ist diese fertig wird ein Event gefeuert -> das bekommst du wiederum.

      PS: Wenn du das in ner Konsole ausgibst wird das recht unperformant. Derzeit ist eine Defaultanzahl der Bänder von 1024 eingestellt. Das heißt, dass alle 1024 Samples ne Transformation fertig ist. Bei ner Samplerate von 44100 ist das in der Sekunde 44100 / 1024 mal. Jetzt gibst du jeweils 512 Resultate aus -> 44100 / 1024 * 512 -> wird recht viel für Echtzeit :D. Aber zum debuggen kannste mal drinnen lassen. Dann siehst du ob es klappt.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Ok, ich hab das nun so abgeändert:

      VB.NET-Quellcode

      1. Private Sub Aggregator_FFTCalculated(sender As Object, e As FFTCalculatedEventArgs)
      2. 'Die ersten 512 Werte abspeichern und den Real in einen Single umwandeln
      3. Dim Values As IEnumerable(Of Single) = e.Data.Take(512).Select(Function(c) c.Value)
      4. 'Testausgabe, Form: 'Output: {0.23, 0.00, 0.01, [...] }'
      5. Debug.WriteLine(String.Format("Output: {{{0}}}", String.Join(", ", Values.Select(Function(s) s.ToString("0.00")).ToArray)))
      6. End Sub
      7. Private Buffer() As Byte
      8. Private Sub SoundInSource_OnNewData(sender As Object, e As DataAvailableEventArgs)
      9. While Aggregator.Read(Buffer, 0, Buffer.Length) > 0
      10. 'Mit den Daten einfach nichts machen
      11. End While
      12. End Sub
      13. Dim SoundIn As ISoundIn
      14. Dim SoundInSource As SoundInSource
      15. Private WithEvents Aggregator As FFTAggregator
      16. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
      17. SoundIn = New WasapiLoopbackCapture
      18. SoundIn.Initialize()
      19. SoundInSource = New SoundInSource(SoundIn)
      20. Dim Aggregator = New FFTAggregator(SoundInSource)
      21. Buffer = New Byte(Aggregator.WaveFormat.BytesPerSecond) {}
      22. AddHandler SoundInSource.DataAvailable, AddressOf SoundInSource_OnNewData
      23. AddHandler Aggregator.FFTCalculated, AddressOf Aggregator_FFTCalculated
      24. SoundIn.Start()
      25. End Sub
      26. Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
      27. SoundIn.Stop()
      28. SoundIn.Dispose()
      29. End Sub

      Jetzt kommt allerdings beim Start eine Exception:

      InnerException schrieb:


      bei CSCore.SoundIn.WasapiCapture.CaptureProc()
      bei System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
      bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
      bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
      bei System.Threading.ThreadHelper.ThreadStart()
      Wow. Das ist definitiv neu. Hast du etwas mehr details? Ansonsten vll. den ganzen Code dann versuche ich das mal schnell und such dir schnell ne Lösung.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Problem gelöst. Lag an deinem Code :P.
      Ändere

      VB.NET-Quellcode

      1. Dim Aggregator = New FFTAggregator(SoundInSource)

      zu

      VB.NET-Quellcode

      1. Aggregator = New FFTAggregator(SoundInSource)


      Localscope hat immer Vorrang und somit wird Aggregator nichts zugewiesen. Da dies alles im WasapiCaptureThread ausgeführt wird, fängt dies auch WasapiCapture ab.


      Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
      Hab wohl vergessen, das Dim wegzumachen. Funktioniert jetzt jedenfalls :). Hast du vielleicht noch irgendeinen Tipp, wie ich die Werte auf ein paar einzelne reduzieren kann (Durchschnitt nehmen?) und vielleicht noch einen, wie ich das noch auf Links/Rechts zuordnen kann?