FSK demodulation

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

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

    Das habe ich geändert und ich habe eine "Funktion" eingebaut, welche mir die MinRate und die MaxRate anzeigt. Die Werte liegen zwischen 8820 und 22050 (in einem Zeitraum von ca. 2 Minuten) trotz empfangener Telegramme. Da ist irgendwo noch der Wurm drin. Zwischenzeitlich habe ich den anderen Weg über die Zeit versucht:


    VB.NET-Quellcode

    1. rate = (Math.Abs(o) * 1000 * 1000) / sr
    2. If (rate > maxrate) Then maxrate = rate
    3. If (rate < minrate) Then If (rate > 0) Then minrate = rate
    4. If (rate > 400 And rate < 430) Then 'logisch 1
    5. test &= "1"
    6. ElseIf (rate > 260 And rate < 290) Then 'logisch 0
    7. test &= "0"
    8. End If


    Aber auch das passt nicht - es kommen weder 1en noch 0en.
    Habs hiermit versucht:

    VB.NET-Quellcode

    1. src = New CSCore.Streams.SoundInSource(cs)
    2. src.Read(buf, 0, buf.Length)
    3. For i = 0 To buf.GetUpperBound(0) - 4 Step 4
    4. ReDim Preserve tempBuf((i / 4) + i)
    5. tempBuf((i / 4) + i) = BitConverter.ToSingle(buf, i)
    6. tempCounter += 1
    7. If (tempCounter = cs.WaveFormat.SampleRate) Then
    8. _queue.Enqueue(tempBuf)
    9. ReDim tempBuf(0)
    10. tempCounter = 0
    11. End If
    12. Next
    Und hiermit:

    VB.NET-Quellcode

    1. AddHandler cs.DataAvailable, AddressOf datas
    2. Private Sub datas(sender As Object, e As CSCore.SoundIn.DataAvailableEventArgs)
    3. For i = 0 To e.Data.GetUpperBound(0) - 4 Step 4
    4. ReDim Preserve tempBuf((i / 4) + i)
    5. tempBuf((i / 4) + i) = BitConverter.ToSingle(e.Data, i)
    6. tempCounter += 1
    7. If (tempCounter = cs.WaveFormat.SampleRate) Then
    8. _queue.Enqueue(tempBuf)
    9. ReDim tempBuf(0)
    10. tempCounter = 0
    11. End If
    12. Next
    13. End Sub

    Upperbounds sind in "normalen", eindimensionalen .Net-Arrays stets Array.Length - 1. Nicht-Eindimensionale Arrays mit veränderter unterer Grenze sind nicht als T() darstellbar. ;) D.h. du kannst das weglassen.
    ReDim und ReDim Preserve sind veraltet und werden durch Array.Resize ersetzt, das sollte hier aber nicht notwendig sein.
    Die Abfrage tempCounter = cs.WaveFormat.SampleRate verstehe ich nicht ganz. Warum behältst du eine Sekunde drin?
    Ich würde sogar die Kopierroutine einfach streichen und die Algorithmen vereinen: buf(i) bzw. buf(i + 1) entsprechen eben dem i-ten Single-Eintrag im Puffer. Das kannst du eben mit BitConverter.ToSingle erledigen. Anschließend noch auf 0-Durchgänge überprüfen, wie ich's dir gezeigt hatte.

    Viele Grüße
    ~blaze~
    Mit tempCounter = cs.WaveFormat.SampleRate bewirke ich, dass ich grundsätzlich einen Puffer mit einer Sekunde analysiere. Du hattest doch erwähnt, dass 1 Single = 4 Byte oder täusche ich mich da? Deswegen mache ich diese Schleife und kopiere in einen temporären Puffer. Natürlich kann es auch sein, dass ich da einen Fehler mache und deswegen die Single-Werte nicht passen.
    Du solltest eigentlich wohl nur jedes 8. Byte ein Single abgreifen, da es sich um einen Stereo-Kanal handelt, oder?
    Ja, jedes Single besteht aus 4 Bytes, damit hast du recht. Der temporäre Puffer ist eigentlich überflüssig.
    Gib mal die Gleitkommazahlen grafisch aus und zeichne sie oder lade ein paar Daten der Gleitkommazahlen hoch. Die sollten sich immer zwischen -1 und 1 bewegen.

    Viele Grüße
    ~blaze~
    Hier mal 50 aufeinander folgende (berechnete) Single-Werte:

    Quellcode

    1. 0,1229858
    2. 0,08224487
    3. -0,03594971
    4. -0,02468872
    5. 0,001586914
    6. 0,03967285
    7. 0,06295776
    8. 0,09417725
    9. 0,1112976
    10. 0,1123047
    11. 0,1181641
    12. 0,1199646
    13. 0,1096497
    14. 0,07992554
    15. 0,02432251
    16. 0,0138855
    17. 0,04318237
    18. 0,07516479
    19. 0,1079712
    20. 0,1295166
    21. 0,1436768
    22. 0,1693726
    23. 0,166626
    24. 0,164917
    25. 0,1835938
    26. 0,1517944
    27. 0,04434204
    28. 0,002593994
    29. -0,002746582
    30. 0,01577759
    31. 0,04684448
    32. 0,05935669
    33. 0,04406738
    34. 0,01306152
    35. 0,01864624
    36. 0,05001831
    37. 0,08026123
    38. 0,1069641
    39. 0,0819397
    40. 0,05679321
    41. 0,072052
    42. 0,09375
    43. 0,05944824
    44. -0,03012085
    45. -0,04336548
    46. -0,01785278
    47. 0,01318359
    48. 0,01287842
    49. -0,08056641


    Diese Werte wurden bei "Rauschen" auf dem Line-In aufgezeichnet. Diese Werte stellen quasi kein Telegramm dar.
    Leider ist das nicht sehr aussagekräftig. Es wäre praktisch, eine echte Aufnahme oder Simulation zu erhalten. Das auslesen der Single-Werte ist übrigens weniger das Problem. Ausserdem: Rauschen sorgt dafür, dass der Nullstellenalgorithmus nicht korrekt arbeitet. Den müsste man dann anpassen, das Signal glätten o.ä. Mensch, das würde mir fast nen heiden Spaß machen... 8o

    Viele Grüße
    ~blaze~
    So.... Ich habe die Tage ein wenig gebastelt und Fehler gesucht und gefunden. Ich hatte einen Fehler in der Berechnung der Singlewerte (das mit dem tempBuffer hat nicht gepasst). Ich habe jetzt den Quellcode angepasst und bin eigentlich der Meinung, dass soweit alles passen sollte:

    VB.NET-Quellcode

    1. Dim queueData As New Queue(Of Byte())
    2. Dim q1 As New Queue(Of Single)
    3. Dim rate As Long
    4. Dim minrate As Long = 1000000
    5. Dim maxrate As Long = 0
    6. Dim o As Integer = Integer.MinValue
    7. Dim lastSgn As Single
    8. Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    9. If (Button2.Tag = 1) Then
    10. cs.Stop()
    11. Button2.Tag = 0
    12. Else
    13. o = Integer.MinValue
    14. Button2.Tag = 1
    15. cs.Device = devs(ComboBox1.SelectedIndex)
    16. cs.Initialize()
    17. cs.Start()
    18. AddHandler cs.DataAvailable, AddressOf datas
    19. Dim t As New Threading.Thread(AddressOf proccessData)
    20. t.Start()
    21. t = New Threading.Thread(AddressOf proc1)
    22. t.Start()
    23. End If
    24. End Sub
    25. Private Sub datas(sender As Object, e As CSCore.SoundIn.DataAvailableEventArgs)
    26. SyncLock queueData
    27. queueData.Enqueue(e.Data)
    28. End SyncLock
    29. End Sub
    30. Private Sub proccessData()
    31. Dim x As Single = 0
    32. While (Button2.Tag = 1 Or queueData.Count > 0)
    33. If (queueData.Count > 0) Then
    34. SyncLock queueData
    35. If (Button2.Tag <> 1 And queueData.Count = 0) Then Exit While
    36. Dim eData() As Byte = queueData.Dequeue
    37. Dim i As Integer
    38. For i = 0 To eData.Length - 1 Step (cs.WaveFormat.Channels * 4)
    39. wert = BitConverter.ToSingle(eData, i)
    40. SyncLock q1
    41. q1.Enqueue(wert)
    42. End SyncLock
    43. Next
    44. End SyncLock
    45. End If
    46. End While
    47. End Sub
    48. Private Sub proc1()
    49. Dim sr As Integer = cs.WaveFormat.SampleRate
    50. Dim c1200 As Integer = 0
    51. Dim c1800 As Integer = 0
    52. While (Button2.Tag = 1 Or q1.Count > 0)
    53. If (q1.Count > 0) Then
    54. SyncLock q1
    55. Dim wert1 As Single = q1.Dequeue
    56. If Math.Sign(lastSgn) <> Math.Sign(wert1) Then
    57. If o <> Integer.MinValue Then
    58. rate = sr \ o
    59. If Math.Abs(rate - 1200) <= NumericUpDown1.Value Then 'NumericUpDown1 entspricht RateEqualityThreshold (eingebaut um zur Laufzeit verschiedene Werte zu testen
    60. c1200 += 1
    61. c1800 = 0
    62. If (c1200 = 2) Then
    63. c1200 = 0
    64. test &= "1"
    65. End If
    66. End If
    67. If Math.Abs(rate - 1800) <= NumericUpDown1.Value Then 'NumericUpDown1 entspricht RateEqualityThreshold (eingebaut um zur Laufzeit verschiedene Werte zu testen
    68. c1800 += 1
    69. c1200 = 0
    70. If (c1800 = 3) Then
    71. c1800 = 0
    72. test &= "0"
    73. End If
    74. End If
    75. If (rate > maxrate) Then maxrate = rate
    76. If (rate < minrate) Then If (rate > 0) Then minrate = rate
    77. If (test.Length > 68) Then
    78. test = test.Substring(1, 68)
    79. End If
    80. Dim temp1 As String = test
    81. If (temp1.Substring(0, 20) = "11111111111100011010") Then
    82. threadDecoder = New Threading.Thread(AddressOf decoder)
    83. threadDecoder.Start(temp1.Substring(20, 48))
    84. End If
    85. End If
    86. o = 0
    87. End If
    88. lastSgn = wert1
    89. If o <> Integer.MinValue Then o += 1
    90. End SyncLock
    91. End If
    92. End While
    93. End Sub

    Nun ist es so, dass 1en und 0en "raus kommen", aber dennoch kein "sinnvolles Telegramm" dargestellt wird. Ich habe ein wenig mit dem RateEqualityThreshold (bzw. NumericUpDown) gespielt und es haut immer noch nicht hin. Teilweise sieht die Abfolge von 1en und 0en aus wie ein FMS-Telegramm - allerdings passt der Telegrammvorlauf und die Bitsynchronisation nicht. Ich habe den Verdacht, dass irgendwo noch ein kleiner winziger Fehler liegt.

    Testweise habe ich die Single-Werte als Wave in einer PictureBox dargstellt (was allerdings den Thread fast einschlafen lässt - die QueueCount steigt binnen 2 Sekunden auf über 100.000). Wenn FMS-Telegramme gesendet werden, dann ist in der PictureBox die Sinus-Welle schön zu erkennen.

    Gedankenzug von mir: in dem Moment wo die erste 1 (im Telegrammvorlauf - bestehend aus zwölf 1en) kommt entsteht eine Einschwingphase und beim lezten Bit vom Telegramm entsteht ein Abfall. Diese beiden "Sonderfälle" werden in der Berechnung nicht beachtet - oder täusche ich mich da? Nun kann man aber auch nicht klar definieren wie die "rate" für das Einschwingen und den Abfall aussehen muss, da man nie weiß welche Frequenz vor der ersten Frequenz (1200 Hz für logisch 1) gesendet wird.
    vb-paradise.de/index.php/User/2203-blaze/
    Aber dennoch bin ich / sind wir einen rießigen Schritt vorwärts gekommen - Danke @~blaze~

    Edit: Das Schlussbit ist irrelevant, da es sowieso nicht ausgewertet wird. Und für das erste Bit frage ich einfach beide Möglichkeiten ab - somit überprüfe ich, ob der Telegrammvorlauf "11111111111100011010" oder "01111111111100011010" ist. Die Sicherheit, ob es sich um ein FMS-Telegramm handelt, ergibt sich durch die CRC-Prüfung (welche allerdings noch programmiert werden muss - da muss ich noch ettliches lesen....)

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

    CRC ist bestimmt schon im Framework enthalten. Ich schau mir den Quellcode später an. Ich schätze aber tatsächlich, dass es durch die zusätzliche Nullstelle bei 1800hz kommt. Vielleicht benutzt du einfach eine Boolean-Variable, in die du speicherst, ob die nächste Nullstelle ausgewertet werden soll.

    Viele Grüße
    ~blaze~

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

    CRC ist in der CScore-Lib nicht enthalten. Die CRC-Prüfung erfolgt mittels der letzten 7 Bits im Telegramm. Das ist aber erstmal noch Zukunft. Testweise habe ich jetzt mal mein Handy mit einer Soundgenerator-App an den Line-In angeschlossen. Sende ich eine Frequenz in Höhe von 1200 Hz, dann wird diese nicht erkannt. Mache ich vielleicht doch einen Fehler bei der Berechnung der Single-Werte?
    Gibt nur crc8 und crc16 implementierungen in CSCore.
    Die sind aber eigentlich auch als nicht öffentlich gekennzeichnet und nicht für die Verwendung von außen gedacht.
    Ich denke @~blaze~ meinte mit Framework das .NET Framework, welches meines Wissens dies auch nicht hat. Dürfte aber auch keine Hexerei sein, da einen Algo aufzutreiben. Ist ja recht trivial.


    Opensource Audio-Bibliothek auf github: KLICK, im Showroom oder auf NuGet.
    @thefiloe Das eigentliche Problem ist eigentlich immernoch die richtige Analyse des Buffers. Weiter oben steht mein Quellcode. Wandel ich dich Bytes richtig in Single um? Gibt es vielleicht eine direkte Methode in CSCore um die Single-Werte zu bekommen? Ich gehe aktuell davon aus, dass in dem Buffer e.Data einzelne Samples im Abstand von 4 Bytes (umgerechnet 1 Single-Wert) enthält und dass immer rechts und dann links übertragen wird. Also 1 Block besteht aus 8 Bytes?
    Den Beitrag Nummer 58 habe ich entsprechend geändert. Das Mithören und Mitschneiden ist illegal für "normale" Bürger. Es gibt aber auch gewisse Außnahmen.

    @thefiloe: ich möchte dich bitten ein kurzes Beispiel zu geben, wie man mittels der CSCore-Lib die Single-Werte eines Samples ermittelt. Ich denke, dass ich es richtig mache - aber ich weiß es nicht. Ein Beispiel wäre gut, damit ich das mit meinem aktuellen Source vergleichen kann.

    Zur Zeit verwende ich diesen Code:

    VB.NET-Quellcode

    1. Dim stepper As Integer = (cs.WaveFormat.BlockAlign / cs.WaveFormat.Channels)
    2. Dim bufLeft(stepper - 1) As Byte
    3. Dim bufRight(stepper - 1) As Byte
    4. Dim eData() As Byte = queueData.Dequeue
    5. Dim temp123((eData.Length / cs.WaveFormat.BlockAlign) - 1) As Single
    6. For i = 0 To eData.Length - 1 - cs.WaveFormat.BlockAlign Step cs.WaveFormat.BlockAlign
    7. For j = 0 To stepper - 1
    8. bufLeft(j) = eData(i + j)
    9. bufRight(j) = eData((i + stepper) + j)
    10. Next
    11. wert = 0
    12. wert += BitConverter.ToSingle(bufLeft, 0)
    13. wert += BitConverter.ToSingle(bufRight, 0)
    14. wert = wert / 2
    15. temp123(i / cs.WaveFormat.BlockAlign) = wert
    16. Next
    17. SyncLock q1
    18. q1.Enqueue(temp123)
    19. End SyncLock

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

    So.... Ich bin einiges weiter gekommen. Nun ist es aber so, dass durch die Formel von @~blaze~ der "Frequenzübergang" nicht berücksichtigt wird. Das heißt gezwungenermaßen muss ich eine PLL einbauen. Jetzt habe ich schon einiges gelesen, habe es aber nicht geschafft, das Gelesene in VB.NET zu "übersetzen". Könnte mir hier bitte jemand weiter helfen eine PLL zu programmieren bzw. kennt jemand vielleicht eine andere Möglichkeit aus der Anzahl der Samples und der vorherig berechneten Frequenz auf die eigentliche Frequenz zu schließen?