I am trying to write a Python script that can demodulate an FSK modulated audio file and return the data encoded in the audio. The data being transmitted is GPS NMEA strings which are embedded as the audio channel in video files. Basically, text is encoded with FSK modulation, and I am trying to retrieve the text using Python. The device I am using to encode the data can also decode it, so I have been able to generate the correct output, but I need to be able to do it using software.

I have done some background reading to introduce myself to signal processing and FSK, and I have looked at example scripts, but I feel a little lost in the specific application of building my own demodulator in software.

I managed to write a Python script that runs successfully, although the output is incorrect. The correct output derived from the encoding/decoding device has 8,280 raw binary (0 and 1) characters, the Python output has 1,344,786. I think I am missing a symbol synchronizer, but I'm not sure how this works.

My question now is: how can I add symbol synchronization to the script and/or symbol timing? Are there better examples or explanations of how to do FSK demodulation in Python? I would appreciate any feedback or direction. Thank you.

EDIT: I have changed my original script. Now there is a problem where the mark and space frequencies (1200 Hz and 2200 Hz, respectively) have the same shape when plotted, and the output of the script is all 1s. The script works by bandpass filtering each frequency, comparing them, and deciding a 1 when mark is greater than space (based on this article).

Here's a spectrogram (from Baudline) of the audio file: enter image description here

EDIT: This is my updated script with as suggested in comments:

import scipy.signal.signaltools as sigtool
from scipy import signal
from scipy.signal import butter, lfilter, blackman
import wave
import struct
import numpy as np
import matplotlib.pyplot as plt
from scipy.fftpack import rfft, rfftfreq

mark_f = 1200
space_f = 2200
Fs = 44100

waveFile = wave.open("Sample4_160224_mono.wav", "r")
length = waveFile.getnframes()
sig = []
for i in range(0, length):
    waveData = waveFile.readframes(1)
    data = struct.unpack("<h", waveData)

# Bandpass filter mark and space frequencies:

low_bpass = signal.remez(5, [.1,.109,.11,.13,.131,.14],[0,1,0])
mark_filtered = signal.lfilter(low_bpass, 1, sig)

high_bpass = signal.remez(5, [.2,.209,.2100,.23,.231,.2400],[0,1,0])
space_filtered = signal.lfilter(high_bpass, 1, sig)

# Envelope detector on mark and space bandpass filtered signals:

mark_env = np.abs(sigtool.hilbert(mark_filtered))
space_env = np.abs(sigtool.hilbert(space_filtered))

# Compare and decision:

seq = range(0, len(mark_env))
rx = []
for i in seq:
    if mark_env[i] > space_env[i]:
    if mark_env[i] < space_env[i]:
    if mark_env[i] == space_env[i]:
  • 1
    $\begingroup$ Do you have an example file you could share? It looks like you need to design a demodulator for the FSK signal. It would probably help you if you plot the first 100 points (to get a feel for what the signal looks like). Also, you can look up the GPS NMEA specifications to get a better understanding of what he signal looks like. $\endgroup$
    – jeremy
    Apr 6 '16 at 23:24
  • $\begingroup$ Unfortunately I can't share an audio file right now, but I added a plot of some of the data points and a spectrogram of the file. I thought the differentiator and envelope detector demodulated the signal -- is that incorrect? $\endgroup$
    – Evan
    Apr 8 '16 at 16:55
  • $\begingroup$ Try implementing bandpass filters (at each FSK frequency) and then envelope detectors as in here on page 6: link You should come out with different signals and be able to differentiate between 1s and 0s $\endgroup$
    – jeremy
    Apr 8 '16 at 22:22
  • $\begingroup$ Check out github.com/darksidelemm/alltheFSKs $\endgroup$
    – bcattle
    Feb 13 '17 at 8:29
  • $\begingroup$ I provided an answer here on demodulating FSK that may be of interest: electronics.stackexchange.com/questions/293706/… As far as timing synchronization look into the Gardner Loop as an option. $\endgroup$ Dec 29 '18 at 19:58

This project had fsk demodulation implemented( actually, they reuse another open source project). I think it would be much easy for you to build python bridge to it, then to recreate all of it from scratch.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.