FSK demodulation

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 108 Antworten in diesem Thema. Der letzte Beitrag () ist von ~blaze~.

    FSK demodulation

    Hallo Leute,

    ich bin neu hier und habe auch gleich ein Problem..... Ich empfange über den Line-In ein Soundsignal welches binäre Daten enthält. Diese Daten werden mittels FSK übertragen. Ich habe jetzt schon einiges über dieses Thema gelesen bin aber immernochnicht schlauer geworden. Ich möchte gerne das Soundsignal in die Binärdaten umwandeln. Die Frequenz für logisch 1 ist 1200 Hz und für logisch 0 1800 Hz. Die Baudrate beträgt 1200 bit/s. Aktuell nutze ich die Bass.dll um die Daten auszuwerten (was mir allerdings nicht gelingt). Kann mir hier jemand helfen bzw. einen Denkanstoß geben? Vielleicht kann mir auch jemand FSK nicht ganz so wissenschaftlich erklären. Verstanden habe ich bis jetzt, dass die beiden Frequenzen für 1 oder 0 stehen. Aber wieso Phasenverschiebung und so ein Zeug?

    Ich hoffe mir kann jemand helfen.

    LG Dietzi
    Wie weit bist du denn? Was hast du alles versucht? Was klappt nicht?

    Meine Idee waere die Amplituden der Frequenzen kontinuierlich auszulesen und zu verarbeiten.

    VB.NET-Quellcode

    1. Dim fft(2047) As Single
    2. Bass.BASS_ChannelGetData(stream, fft, BASSData.BASS_DATA_FFT4096)
    3. Dim index As Integer = Utils.FFTFrequency2Index(100, 4096, 44100)
    4. Dim value As Single = fft(index)


    bass.radio42.com/help/html/a13…b94-81c4-a4fdf21bd463.htm
    bass.radio42.com/help/html/f54…831-5192-be959ee41cbc.htm
    And i think to myself... what a wonderfuL World!
    Was genau möchtest du denn speichern? Was FSK ist, weiß ich garnicht. Wenn es um die Rohdaten des Signals geht, also Raw-Audio, dann speichert man die unter Windows als WAV-Datei.

    FSK: en.wikipedia.org/wiki/Frequency-shift_keying

    Gruß,

    Klaus
    Wie im Eröffnungsthread geschrieben möchte ich das Soundsignal in die Binärdaten umwandeln. Was FSK ist weiß ich. Ich scheitere lediglich daran die FMS-Telegramme aus den Soundsignalen zu deodieren

    Edit: die Auswertung soll in Echtzeit erfolgen. Das speichern in eine wav-Datei ist überflüssig und nicht gewollt
    Hi
    im Link von VB3-Guru wurde auf den Goertzel Algorithmus verwiesen. Kannst du damit nichts anfangen?
    Alternativ und einfacher: Um das Spektrum eines Signals zu ermitteln, kann die FFT verwendet werden, wie Eddy demonstriert hat. Anhand dessen könntest du in den einzelnen Zeitabschnitten die Frequenz bestimmen und hättest dann eben 0-en und 1-en, wenn ich die Bilder richtig interpretiert habe.

    Die BASS-Dll brauchst du dazu nicht unbedingt.

    Viele Grüße
    ~blaze~
    Wie mache ich das ohne die BASS-Dll? Mit Soundprogrammierung beschäftige ich mich jetzt zum ersten Mal. Eines meiner Probleme ist auch die Messung in den entsprechenden Zeitabschnitten. Mit einem normalen Timer kommt man bei einer Baudrate von 1200 nicht weit. Ein Zeitabschnitt ist 833 ms lang.

    Edit: aktuell versuche ich das Zeitproblem mti dem QueryPerformanceTimer zu lösen
    Du erhältst normalerweise irgendeine Funktion der Art
    s(t) = sin(t*2pi*f(t) + p), wobei p: Phase und f die Frequenz zum Zeitpunkt t ist. t ist also die Zeit, zu der das Signal bestimmt wird

    Jetzt ist die Vorgehensweise so:
    Die Baudrate gibt dir die Zahl der Werte die in 1 Sekunde übertragen werden. Ein Zeitabschnitt ist 833 ms lang, d.h. ein Block sollte 0,833s * 1200 Symbole/Sekunde = 999,6 Symbole, also 1000 quasi Messwerte lang sein, oder?
    Du kannst jetzt wie folgt vorgehen:
    - A: Du liest aus dem Datenstrom immer 1000 Werte und analysierst diese (korrekt, aber siehe Anmerkung unten und zeitverzögert)
    - B: Du liest aus dem Datenstrom immer ein paar Werte und analysierst diese bereits, da sie im ganzen Zeitabschnitt gleiche Daten liefern sollten (näher an Echtzeit, aber dafür möglicherweise inkorrekt)
    A wäre nicht 100% richtig. Du hast nicht 1000, sondern nur 999,6 Symbole, d.h. die Zahl der Symbole zum Zeitpunkt t wäre möglicherweise auch nur 999, wenn du korrekt arbeiten willst. Das hinge davon ab, ob ((1000-999,6) * t) mod 1 <= 0,5 wäre oder so... Aber das kannst du theoretisch wohl erst mal ignorieren. Ggf. kam man auch nur auf die 833ms, da das aus der Division von 1000 Symbolen pro Sekunde rauskam.

    Danach kann man bei A wohl eine der folgenden Möglichkeiten verwenden:
    - Die Zahl der Hochpunkte oder Tiefpunkte zählen und durch deren Häufigkeit auf die Frequenz rückschließen (wenn keine Überlagerung vorhanden ist, wie bei s(t))
    - Die Eingabewerte mit der FFT auf deren Spektrum analysieren und dort die Frequenz(-en) ablesen

    Bei B ist die Idee, über die Steigung auf die Werte zu schließen (das beschriebene funktioniert aber NUR für ein nicht-überlagertes Signal). D.h. du betrachtest s'(t) und stellst dort ein Gleichungssystem für je ein paar Messwerte-Paare auf, das du danach löst (je mehr, desto genauer, aber 1 Paar genügt theoretisch, wenn das Signal 100% korrekt ist und die Gleitkommaarithmetik nicht wäre. Es müssen immer zwei Wertepaare in einer Periode gewählt werden. Je weiter auseinander, desto besser). Nach der Lösung des Gleichungssystems hast du ebenfalls die Frequenz. Die Phase kannst du aus dem 1. Messwert im Block ermitteln (und ggf. der Steigung).

    Ich würde übrigens A wählen. Es ist übrigens auch nicht so schwierig, wie es sich beim ersten mal Lesen anhören wird.
    Übrigens: Ich habe zu keinem einzigen Zeitpunkt die Zeit gebraucht. Wenn du sie aber doch brauchen solltest, verwende bspw. die System.Diagnostics.Stopwatch. Die kann aber keine Events (kannst du dir aber mit einem Thread und System.Threading.Thread.Sleep zusammenbasteln, indem du eben immer 833ms minus der für die Berechnungen gebrauchten Zeit wartest oder sowas... Zum Messen der Zeit kannst du dann eben die Stopwatch verwenden)

    Oft ist es übrigens hilfreich, die Zeitabschnitte zusammenzufassen, d.h. bspw. einen gemeinsamen Nenner der Frequenzen zu wählen und alle Messwerte innerhalb eines derartigen Messwertblocks zusammenzufassen (z.B. den Durchschnitt zu bilden). Sozusagen reduzierst du die Messwertrate von 1200 auf bspw. 300 oder 120 oder sowas. Damit lässt sich teilweise einfacher arbeiten (aber B fällt weg. :D)

    Die Steigung würdest du einfach so ermitteln:
    (Messwert1 - Messwert0) / deltaT, wobei deltaT die Zeit zwischen den beiden Messwerten angibt und Messwert0 und Messwert1 zwei aufeinanderfolgende Werte sind.

    Viele Grüße
    ~blaze~
    Danke für deine ausführliche Antwort. Hab grad bemerkt, dass ich einen Tippfehler hatte.... nicht 833 ms sondern 833 µs. Also 0,83333333 ms (1000ms / (1200kbit/s))

    Bei A verstehe ich nicht wieso ich die Zeit nicht brauchen sollte.... Ich muss doch jeweils die Amplitude pro Symbol (also alle 833 µs) und pro Frequenz erkennen und daraus dann auf binär 1 oder 0 schließen oder verstehe ich das falsch?
    Kannst du die Daten denn gepuffert empfangen? Bspw. als Array oder so?
    Ansonsten kannst du einfach eine While-Schleife die Daten abfragen lassen (und ggf. selber in ein Array puffern, wenn es keine 100% Echtzeit erfordert). Dabei würde ich so vorgehen, dass auf eine Signaländerung reagiert wird, d.h. du vergleichst immer mit dem Vorgängerwert und reagierst erst, wenn eine Änderung des Signals geschehen ist. Ich weiß nicht auswendig, welche Präzision (d)eine Stopwatch hergibt, aber du kannst sie auch als Zeitgeber einsetzen. Beachte auch, dass Multitasking-Systeme Verzögerungen haben, d.h. wirkliche Echtzeit erhältst du nur, wenn dein Programm auf Echtzeit einen Cpu-Kern besetzt.

    Bevor ich hier unnötige Sachen erläutere: Welche Frequenz haben denn die Signale, die du empfängst? Wenn die nicht allzu nah an der Baudrate liegen, könnte es möglich sein, dass du gar nicht erst alle Werte empfangen musst, sondern du auch ein paar auslassen kannst. Oder gibt's eine Verwendung für die?

    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    Es geht um solche Telegramme: muenster.de/~mbarenh/old/retttxt/fms.htm

    Im Moment arbeite ich noch mit der BASS-DLL. Aber mittlerweile glaube ich, dass mir diese nicht schnell genug die FFT-Daten bringt (also dass nicht schnell genug das Array mit Daten gefütter wird und dementsprechend immer falsche Ergebnisse raus kommen). Ich will mir jetzt mal DirectX anschauen. Evtl kann ich da die Daten in Echtzeit und vollständig puffern. Was ich bei der BASS-DLL noch zur Verfügung habe ist ein Record-Proccess in dem ein Buffer angegeben ist, dessen Größe sich aber immer wieder ändert. Die Dokumentation von der BASS-DLL lässt auch zu wünschen übrig. Wie würdest du die Daten abgreifen (vom Line-In)?

    LG Dietzi
    Oh stimmt, du empfängst es ja über den Line-In.
    Die Bass-Dll wäre leider tatsächlich nicht meine Wahl. Ich würde es tatsächlich über XAudio2 oder WASAPI machen, aber müsste mich erst einlesen, was da besser geeignet ist. Ich würde jeweils die dort vorgestellten Caputre-Methoden verwenden, um das eingehende Signal zu untersuchen und jeweils einen Block als Array einlesen und in einem anderen Thread auswerten (bzw. eigentlich umgekehrt: Auf dem Nebenthread einlesen und abspeichern, auf dem Hauptthread auswerten). Im Prinzip erhältst du eine Nachrichtenschlange. Die FFT zu implementieren, ist nicht schwer, das Auslesen des Audiosignals selbst auch nicht.

    Meine Vorgehensweise wäre insgesamt die Folgende:
    Nebenthread:
    - Einen neuen, kleinen Puffer erstellen, der bspw. die Information von 256ms abspeichern kann
    - Audiodaten in den Puffer einlesen
    - Den Puffer an einen anderen Thread schicken (Queue)
    Anmerkung: Es ist wichtig, dass alle Daten verlustfrei rekonstruiert werden, d.h. ich würde eine Stopwatch verwenden, um abgeschnittene Segmente zu finden (Stellen, die beim Einlesen ausgelassen wurden, falls es sowas gibt).

    Hauptthread:
    - Auf neue eingehende Nachrichten warten
    - Nachrichten aus der Nachrichtenschlange entfernen und verarbeiten

    Ggf. hilft dir auch @thefiloe weiter, der hat eine Audio-Library entwickelt. Ich habe selber mit Audio Capturing nicht viel am Hut. Habe ich zwar mal gemacht, aber ich weiß nicht mehr ganz, ob das Zeitabschnitte auslassen konnte... XAudio2 konnte auf jeden Fall in zwei Puffer abwechselnd lesen, sodass das vorzeitige Überschreiben bei neuen Daten zumindest genug verzögert wurde, um eine Auswertung zu ermöglichen.

    Viele Grüße
    ~blaze~
    Eine Queue in diesem Sinne habe ich mir schon gebaut. Das Problem ist aber dieses "abgeschnittene Segmente finden". Mein Ansatz ist aktuell so: ich lese die Amplituden von 1200 Hz und 1800 Hz. Wenn die Werte passen, dann wird an einen String entweder 1 oder 0 angehangen und das erste Zeichen abgeschnitten sodass der String immer eine Länge von 68 hat. Danach überprüfe ich die ersten 20 Zeichen auf Telegrammvorlauf und Blocksynchronisation. Wenn diese 20 Zeichen quasi gleich "11111111111100011010" sind, dann schicke ich die restlichen 48 Zeichen in die Queue. Nur kommt das ganze gar nicht bis zur Queue, weil die 1en und 0en nicht erkannt werden (vermutlich weil die BASS-DLL zu langsam ist). Ich werde mir jetzt aber auch die beiden von dir genannten Methoden anschauen. 833µs sind nicht lange.....
    Jetzt steig' ich nicht mehr ganz durch. Warum genau schneidest du das erste Zeichen ab?

    Ich glaube nicht, dass die BASS-Dll zu langsam ist. Vielleicht sind noch nicht alle Daten da, die du benötigst? Die 833µs musst du nicht messen. Ich hab' nochmal nachgedacht und es wird beim Soundcapturing natürlich alles aufgenommen, was reinkommt, egal, ob zu viel Zeit vergangen ist. Es kommt lediglich auf die Puffergröße an, die sollte groß genug sein, aber auch hinreichend klein, sodass keine zu große Latenz entsteht. Und du solltest natürlich auch das Programm synchronisieren. Bspw. so:

    Die Idee ist, dass eben der Nebenthread die ganze Zeit Daten holt und der Hauptthread sie verarbeitet. Der Nebenthread hat also NUR die Aufgabe, die Daten zu holen und zwar zeitlich korrekt, sodass er möglichst keine Daten auslässt. Sobald ein neues Datenpaket vorhanden ist, informiert er den Hauptthread darüber und verarbeitet es. Nach Abschluss sämtlicher Tätigkeiten soll, wenn der Benutzer Running auf False setzt (z.B. IDisposable.Dispose), soll das Programm beendet werden.

    Ich hoffe, das Programm ist dir nicht zu schwer verständlich geschrieben. Es beschreibt jetzt erst mal nur, wie das mit der Queue funktionieren würde. Mir ist leider gerade aufgegangen, dass es sinnvoller wäre, einen fortlaufenden Single-Datenstrom zu verwenden, anstatt lauter Single-Arrays.... m( Sorry. Mit einer leichten Modifikation kannst du das aber machen, ggf. setz' ich mich nachher nochmal ran.

    Gemeinsame Klasse:

    VB.NET-Quellcode

    1. Private _queue As Queue(Of Single())
    2. 'Nebenthread
    3. Private Sub DataReceiver()
    4. Dim sw = Stopwatch.StartNew()
    5. Dim interval As Integer = ... 'Sinnvolle Latenz in Millisekunden angeben, die Latenz definiert auch die Größe je Puffer des aufnehmenden Geräts
    6. While Running 'Running ist die Abbruchsbedingung, beendet also den Nebenthread und den Hauptthread
    7. Dim c = sw.ElapsedMilliseconds
    8. //Sounddaten puffern und in Queue einreihen
    9. SyncLock _queue
    10. _queue.Enqueue(LoadSoundData())
    11. Monitor.PulseAll(_queue)
    12. End SyncLock
    13. 'Das Intervall, das gewartet werden soll bestimmt sich aus der während obiger Operationen vergangenen Zeit (die i.A. sehr kurz ist, also sehr nah an Echtzeit)
    14. Dim iv As Integer = interval - CInt(sw.ElapsedMilliseconds - c)
    15. If iv < 0 Then
    16. iv = 0
    17. 'Tritt ein, wenn mehr Zeit vergangen ist, als die Latenz zulässt.
    18. Throw New InvalidOperationException("Device synchronousity lost.")
    19. End If
    20. Thread.Sleep(iv)
    21. End While
    22. End Sub
    23. 'Hauptthread
    24. Private Sub DataProcessing()
    25. Dim break As Boolean
    26. While True
    27. Dim itm As Single()
    28. SyncLock _queue
    29. 'Warte, bis ein neues Item drin ist oder Running sich auf False setzt
    30. While _queue.Count = 0
    31. 'Wenn Running False ist, wird abgebrochen. Das tritt dann ein, wenn nicht länger Daten verarbeitet werden sollen (z.B. Programmbeendigung)
    32. 'Alle bisher eingereihten Items sollen noch verarbeitet werden, bevor die Verarbeitungsroutine beendet wird
    33. If Not Running Then
    34. break = True 'Break beendet die äußere Schleife (kann nicht mit Exit While angesteuert werden)
    35. Exit While
    36. End If
    37. 'Warte, bis Pulse bzw. PulseAll auf der _queue aufgerufen wird
    38. Monitor.Wait(_queue)
    39. End While
    40. 'Wenn Running False ist
    41. If break Then Exit While
    42. itm = _queue.Dequeue()
    43. End SyncLock
    44. 'Hier die Daten verarbeiten
    45. Analyze(itm)
    46. End While
    47. End Sub

    Um das ganze in einen fortlaufenden Datenstrom umzuschreiben, würde ich dir empfehlen, eine Methode zu ergänzen, die die Queue sinnvoll in ein Array umkopiert oder sowas, ich würde aber sogar beim bestehenden Konstrukt bleiben. Am besten verwendest du vielleicht eine LinkedList, statt der Queue und entfernst vorne und fügst hinten an.
    Beachte, dass du zwei Puffer verwendest, in die das Gerät abwechselnd den Sound lädt, bzw. wenn das System das nicht zulässt, einen einzigen, den du aber als zwei aufeinanderfolgende Puffer betrachtest (d.h. du wertest die 1. Hälfte aus, sobald diese vollständig ist, danach die zweite und wieder von vorne.
    Vielleicht schaut sich nochmal jemand das an, der sich mit den aktuellen FW-Versionen besser auskennt, ich bin mit Async/Await zusammen mit Yield nicht bewandert.

    Viele Grüße
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    Orgs...
    willst du das machen um zu lernen wie FSK funktioniert, oder hast du echte Nutzdaten, die du dekodieren willst?

    Um welche Modulation/Protokoll handelt es sich? Ein bekanntes, ala AFSK1200 oder POCSAG1200?
    Ich würde eig. in jedem Falle dazu raten, dass du dir ein multimon-ng kompilierst, und mit dem VB-Programm einfach nur 16bit LE Audio-Samples in multimon einkippst.

    Viele Grüße,
    Manawyrm

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Manawyrm“ ()

    Die Queue ist kein Problem. Das Problem ist die Ermittlung ob eine 1 oder eine 0 geschrieben werden soll.

    Ich möchte FMS-Telegramme dekodieren. Die Übertragung wird mittels "kohärenter Unterträger-FSK" gemacht. Wenn ich alles richtig verstanden habe bedeutet das, dass wenn die Amplitude von 1200 Hz größer als die von 1800 Hz ist und gleichzeitig ein Signal auf 1200 Hz anliegt eine 1 geschrieben wird und Bei 1800 Hz eine 0.

    Edit: @-blaze-
    Du hast mir weiter oben die Formel s(t) = sin(t*2pi*f(t) + p) gebracht. Woher kommt t und woher kommt p?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „dietzi“ ()

    Hab jetzt mal mit verschiedenen Möglichkeiten "gecaptured" und habe Zeitmessungen durchgeführt. Alle Methoden bringen ein Update nach 15 bis 30 ms. Das ist definitiv zu langsam wenn alle 0,833 ms ausgewertet werden muss. Es muss doch eine Möglichkeit geben direkt über die System-DLL's von Windows einen Stream in Echtzeit zu lesen. Diese 15 bis 30 ms sind für mich persönlich ein Anzeichen dafür, dass in den jeweiligen Funktionen der Buffer per Windows-Timer gefüllt wird. Und dieser Timer kann nicht schneller als 30 ms arbeiten.....
    Ich habe mir das einmal angesehen :)

    Multimon-NG einmal from Source bauen, dann hat man (wie ich) ein schönes Linux-Binary, oder ansonsten ein Windows-Binary.
    Ich mache FM-Demodulation per rtl_fm, du kannst das aber auch problemlos z.B. von der Soundkarte nehmen.

    Viele Grüße,
    Manawyrm

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Manawyrm“ ()