116 lines
3.7 KiB
Python
Executable File
116 lines
3.7 KiB
Python
Executable File
#!/usr/bin/python
|
|
import struct, sys
|
|
from gnuradio.eng_option import eng_option
|
|
from optparse import OptionParser
|
|
|
|
"""
|
|
prototype P25 frame decoder
|
|
|
|
input: short frequency demodulated signal capture including at least one frame (output of c4fm_demod.py)
|
|
output: symbols of a single frame (plus some excess)
|
|
"""
|
|
|
|
parser = OptionParser(option_class=eng_option)
|
|
parser.add_option("-i", "--input-file", type="string", default="demod.dat", help="specify the input file")
|
|
parser.add_option("-s", "--samples-per-symbol", type="int", default=10, help="samples per symbol of the input file")
|
|
(options, args) = parser.parse_args()
|
|
|
|
# frame synchronization header (in form most useful for correlation)
|
|
frame_sync = [1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, -1]
|
|
minimum_span = 5 # minimum number of adjacent correlations required to convince us
|
|
data = open(options.input_file).read()
|
|
input_samples = struct.unpack('f1'*(len(data)/4), data)
|
|
sync_samples = [] # subset of input samples synchronized with respect to frame sync
|
|
symbols = [] # recovered symbols (including frame sync)
|
|
|
|
# return average (mean) of a list of values
|
|
def average(list):
|
|
total = 0.0
|
|
for num in list: total += num
|
|
return total / len(list)
|
|
|
|
# collect input sample statistics for normalization
|
|
total_value = 0.0
|
|
total_deviation = 0.0
|
|
count = 0
|
|
for sample in input_samples:
|
|
total_value += sample
|
|
total_deviation += abs(sample)
|
|
mean_value = total_value / len(input_samples)
|
|
mean_deviation = total_deviation / len(input_samples)
|
|
|
|
# correlate (multiply-accumulate) frame sync
|
|
first = 0
|
|
last = 0
|
|
interesting = 0
|
|
correlation_threshold = len(frame_sync) * mean_deviation
|
|
for i in range(len(input_samples) - len(frame_sync) * options.samples_per_symbol):
|
|
correlation = 0
|
|
for j in range(len(frame_sync)):
|
|
correlation += frame_sync[j] * (input_samples[i + j * options.samples_per_symbol] - mean_value)
|
|
#print i, correlation
|
|
if interesting:
|
|
if correlation < correlation_threshold:
|
|
if i >= (first + minimum_span):
|
|
last = i
|
|
break
|
|
else:
|
|
first = 0
|
|
interesting = 0
|
|
elif correlation >= correlation_threshold:
|
|
first = i
|
|
interesting = 1
|
|
|
|
# downsample to symbol rate (with integrate and dump)
|
|
if last:
|
|
# use center point of several adjacent correlations
|
|
center = first + ((last - first) // 2)
|
|
# grab samples for symbol thereafter
|
|
for i in range(center, len(input_samples), options.samples_per_symbol):
|
|
# "integrate and dump"
|
|
# Add up several (samples_per_symbol) adjacent samples to create a single
|
|
# (downsampled) sample as specified by the P25 CAI standard.
|
|
total = 0.0
|
|
start = i - (options.samples_per_symbol/2)
|
|
end = i + 1 + (options.samples_per_symbol/2)
|
|
for sample in input_samples[start:end]: total += sample
|
|
sync_samples.append(total)
|
|
#print i, sync_samples[-1]
|
|
|
|
# determine symbol thresholds
|
|
highs = []
|
|
lows = []
|
|
# Check out the frame sync samples since we are certain what symbols
|
|
# each one ought to be. The frame sync only uses half (the highest and
|
|
# lowest) of the four frequency deviations.
|
|
for i in range(len(frame_sync)):
|
|
if frame_sync[i] == 1:
|
|
highs.append(sync_samples[i])
|
|
else:
|
|
lows.append(sync_samples[i])
|
|
high = average(highs)
|
|
low = average(lows)
|
|
# Use these averages to establish thresholds between ranges of frequency deviations.
|
|
step = (high - low) / 6
|
|
low_threshold = low + step
|
|
middle_threshold = low + (3 * step)
|
|
high_threshold = low + (5 * step)
|
|
|
|
# assign each sample to a symbol
|
|
for sample in sync_samples:
|
|
if sample < low_threshold:
|
|
# dibit 0b11
|
|
symbols.append(3)
|
|
elif sample < middle_threshold:
|
|
# dibit 0b10
|
|
symbols.append(2)
|
|
elif sample < high_threshold:
|
|
# dibit 0b00
|
|
symbols.append(0)
|
|
else:
|
|
# dibit 0b01
|
|
symbols.append(1)
|
|
|
|
# hey, it's a start. . .
|
|
print symbols[:500]
|