Portapack应用开发教程(十八)NavTex接收 B

解调做完后,我开始研究解码。

我找到了这个资源不错。可以用来解码音频文件里的navtex文字信息。

GitHub - pd0wm/navtex: Simple Navtex decoder

它用的ipynb,我不习惯,转换为了如下的python代码。

import numpy as np
import scipy
import scipy.signal
from scipy.io import wavfile
import matplotlib.pyplot as plt
from navtex import ALPHABET_FIGS, ALPHABET_LTRS


# Load audio files
FILENAME = 'audio/navtex_44k_32bit.wav'
sample_rate, data = wavfile.read(FILENAME)
data = 1. * np.array(data) # Convert to floats

# If stero take left channel
if len(data.shape) > 1 and data.shape[1] == 2:
    data = data[:, 1]

# Filter DC
data -= np.average(data)
print (sample_rate, data.shape)

# Approximate center frequency by taking strongest frequency
f = np.abs(np.fft.fft(data))
fs = np.fft.fftfreq(data.size, d=1.0/sample_rate)

# Take positive frequencies
f = f[:int(f.size/2)]
fs = fs[:int(fs.size/2)]

center_freq = fs[np.argmax(f)]
plt.plot(fs, f)
plt.xlim([center_freq-500, center_freq+500])
plt.xlabel('Freq [hz]')
print ("strongest frequency", center_freq)

# NAVTEX is modulated using FSK (frequency shift keying) which we have to demodulate first

# Find acceptable frequency range
f = center_freq # Use common center frequency. Can also use frequency found above
f_min = (f-500.) / sample_rate
f_max = (f+500.) / sample_rate
f = f / sample_rate


# Simple PLL to recover frequency without using slow FFTs
prev_start_t = 0
fs = []
for t in range(1, len(data)):
    x = np.sin((t - prev_start_t) * 2 * np.pi * f) 
    
    # On zero crossing of received signal check phase of internal oscillator
    if data[t] > 0 and data[t-1] < 0:
        prev_start_t = t
        
        # Adjust frequency proportional to phase offset
        f -= (100.0 / sample_rate) * x
        
        # Clip to acceptable frequency range
        f = min(f, f_max)
        f = max(f, f_min)
        
    fs.append(f)
    
fs = np.array(fs)

#for it in fs:
#    print (it)


N = int(40 * (1./100) * sample_rate)

plt.plot(fs)
plt.xlim([0, N])

# Extract bits from demodulated frequency. NAVTEX is transmitted at 100 bits/sec

avg = np.average(fs) # Center frequency
expected_len = sample_rate / 100.  # 100 Baud

prev_change = 0
bits = []

for i in range(5, len(fs)):
    di = i - prev_change
    num_bits = round(di / expected_len)
    
    if num_bits > 0.5:
        if fs[i] > avg and fs[i-1] < avg: # Rising, previous was 0
            bits += int(num_bits) * [0]
            prev_change = i
        elif fs[i] < avg and fs[i-1] > avg: # Falling, previous was 1
            bits += int(num_bits) * [1]
            prev_change = i
        
bits = np.array(bits)

# More information about the decoding process can be found here: https://arachnoid.com/JNX/
# Summarized:
# 1) Symbols are 7 bit, always 4 ones, 3 zeroes
# 2) Shift through data until symbols have 4 ones, 3 zeroes
# 3) Seperate two datastreams, indicated by [alpha] and [rep]
# 4) There are two alphabets, one for letters one for symbols. Switch on [ltrs] & [figs]
# 5) Accept characters if they are equal in both streams
# TODO: Error correction

N = 7
shift = 0

cur_alphabet_A = ALPHABET_LTRS
cur_alphabet_B = ALPHABET_LTRS

msg_a = ""
msg_b = ""
msgs_a = []
msgs_b = []

alpha_shift = 0
phased = False
phased_count = 0
i = 0
while i*N + shift < len(bits):
    b = bits[i*N + shift:(i+1)*N + shift]
    s = "".join(map(str, b))
    s = s[::-1]
    h = int(s, 2)
    if s.count('1') != 4:
        phased_count -= 1
        
        if phased_count < 10:
            shift += 1
            continue 
    else:
        phased_count += 1
        phased_count = min(50, phased_count)
            
    # Phasing signals, reset receiver
    if h == 0xf: # [alpha]
        alpha_shift = i % 2
        cur_alphabet_A = ALPHABET_LTRS
        
        if len(msg_a.replace('_', '')) > 10:
            msgs_a.append(msg_a)
            msgs_b.append(msg_b)
            msg_a = ""
            msg_b = ""
        msg_a = ""
            
    elif h == 0x66: # [rep]
        alpha_shift = (i+1) % 2
        cur_alphabet_B = ALPHABET_LTRS
        if len(msg_a.replace('_', '')) > 10:
            msgs_a.append(msg_a)
            msgs_b.append(msg_b)
            msg_a = ""
            msg_b = ""
        msg_b = ""

    # Regular symbols
    if i % 2 == alpha_shift:
        dec = cur_alphabet_A[h]
        if dec == '[ltrs]':
            cur_alphabet_A = ALPHABET_LTRS
        elif dec == '[figs]':
            cur_alphabet_A = ALPHABET_FIGS
        elif not dec.startswith('['):
            msg_a += dec
    else:
        dec = cur_alphabet_B[h]
        if dec == '[ltrs]':
            cur_alphabet_B = ALPHABET_LTRS
        elif dec == '[figs]':
            cur_alphabet_B = ALPHABET_FIGS
        elif not dec.startswith('['):
            msg_b += dec
    i += 1
                
i = 0
for msg_a, msg_b in zip(msgs_a, msgs_b):
    print (i)
    i += 1
    msg = ""
    for a, b in zip(msg_a, msg_b):
        if a == b:
            msg += a
        else:
            if a == '_' and b != '_':
                msg += b
            elif a != '_' and b == '_':
                msg += a
            else:
                msg += "_"
    print ("A: ", msg_a)
    print ("B: ", msg_b)
    print ("final: ", msg)
    print ("-" * 80)
from collections import defaultdict
ALPHABET_LTRS = defaultdict(lambda: '_')
ALPHABET_FIGS = defaultdict(lambda: '_')
ALPHABET_LTRS.update({
    0x0f: "[alpha]",
    0x17: "J",
    0x1b: "F",
    0x1d: "C",
    0x1e: "K",
    0x27: "W",
    0x2b: "Y",
    0x2d: "P",
    0x2e: "Q",
    0x33: "[beta]",
    0x35: "G",
    0x36: "[figs]",
    0x39: "M",
    0x3a: "X",
    0x3c: "V",
    0x47: "A",
    0x4b: "S",
    0x4d: "I",
    0x4e: "U",
    0x53: "D",
    0x55: "R",
    0x56: "E",
    0x59: "N",
    0x5a: "[ltrs]",
    0x5c: " ",
    0x63: "Z",
    0x65: "L",
    0x66: "[rep]",
    0x69: "H",
    0x6a: "[]",
    0x6c: "\n",
    0x71: "O",
    0x72: "B",
    0x74: "T",
    0x78: "[cr]"
})
ALPHABET_FIGS.update({
    0x0f: "[alpha]",
    0x17: "'",
    0x1b: "!",
    0x1d: ":",
    0x1e: "(",
    0x27: "2",
    0x2b: "6",
    0x2d: "0",
    0x2e: "1",
    0x33: "[beta]",
    0x35: "&",
    0x36: "[figs]",
    0x39: ".",
    0x3a: "/",
    0x3c: ";",
    0x47: "-",
    0x4b: "[bell]",
    0x4d: "8",
    0x4e: "7",
    0x53: "$",
    0x55: "4",
    0x56: "3",
    0x59: ",",
    0x5a: "[ltrs]",
    0x5c: " ",
    0x63: "\"",
    0x65: ")",
    0x66: "[rep]",
    0x69: "#",
    0x6a: "[]",
    0x6c: "\n",
    0x71: "9",
    0x72: "?",
    0x74: "5",
    0x78: "[cr]"
})

它还推荐了一个网站

Free NAVTEX decoder / ナブテックスデコーダーフリーダウンロード

可以把文字信息转为navtex格式的音频。

不过音频的格式不太一样。

网页编码器生成的是16bit 11.025kHz的音频。ipynb项目需要使用的是32bit 44kHz的音频。另外,我的gnuradio和portapack用的都是8bit 48kHz的音频。这个格式问题都可以用audacity解决。

然后我先用网页生成全部是R的信息、全部是O的信息,以及RO相间的信息。再用ipynb项目解都能解成功。我还用gnuradio看了这些音频的波型,在尝试找规律。

这是解码结果:

(44100, (338800,))
('strongest frequency', 1804.0909090909088)
0
('A: ', 'RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR')
('B: ', 'RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR')
('final: ', 'RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR')
--------------------------------------------------------------------------------

(48000, (368762,))
('strongest frequency', 1804.0904431584599)
0
('A: ', 'OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO')
('B: ', 'OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO')
('final: ', 'OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO')
--------------------------------------------------------------------------------

(44100, (338800,))
('strongest frequency', 1804.0909090909088)
0
('A: ', 'ROROROROROROROROROROROROROROROROROR')
('B: ', 'ROROROROROROROROROROROROROROROROROROR')
('final: ', 'ROROROROROROROROROROROROROROROROROR')
--------------------------------------------------------------------------------

以下分别是纯r和纯o、ro相间的波型图的最后部分,注意最后一段与内容无关的波型。

另外,由于生成的音频频率偏高,所以重新调整了滤波器中心频率,为了波型看着更好,还调整了滤波器带宽。流图如下:

我大致看了python代码和协议的描述。(上一篇文章中文里的4B3Y,是4个0,3个1是错的)。

每个字符是7bit,会有4个1和3个0。可以用这个特性来判断字符从哪一位开始。每个bit是1的时候就是高电平,0的时候就是低电平。

另外,字符会重复传输,用来验证传输有没有出错。

重复方法如下:

原字符串:ABCDEFGHI

实际字符串:ABCADBECFDGEHFIGHI

这样对全是RRRRRRRRRRRRRRR或者全是OOOOOOOOOOO的字符串,第二个特性没有效果。只需要关注第一个特性。

R <->  1010101

O <->  1110001

如此一来RRRRRRRR的字符串,在空气中还是RRRRRRR,对应每个bit是:

101010110101011010101,可以看到规律是时不时会出现两个连着的1,对应一个较长的高电平,然后左右各有3次的快速01切换。与第一张实际解调波型完全一致。

OOOOOOO的字符串,在空气中还是OOOOOOO,对应每个bit是:

11100011110001111000111100011110001,可以看到规律是4个1和3个0不停重复,对应很长的高电平和比较长的低电平,周期性出现。这与第二章实际解调波型也是一致的。

针对原始信息是RORORORORORORO,发到空中后是:

RORROORROORROORROR

对应每个bit是:

1010101111000110101011010101111000111100011010101101010111100011110001

我对比了第三张图,一开始怎么都对不上,后来发现要左右反一反才能对上:

1000111100011110101011010101100011110001111010101101010110001111010101

然后还要把一开头单独的RO去掉,这样才符合这串数据中间任意段落的情况:

10001111000111101010110101011000111100011110101011010101

这样上面这段就跟流图波型对上了。

所以总结下来除了之前知道的每个字符对应的bit,以及字符的重复方式需要注意外,还有流图里波型左右顺序要和01相反。就类似big endian和little endian的问题。至于纯R和纯O没发现这个问题,是因为它们的编码和波型本就左右对称。

通过本篇文章可以确定解调出的方波,高电平就是1,低电平就是0。

要区分连续0和连续1,只需要求采样率48kHz和波特率100之比,这样480个采样点对应1个bit。

我们只要找出上升沿和下降沿,然后计算出两个边沿之间的采样点,把采样点数量除以480,就能知道这两个边沿之间有多少个连续0或连续1。

恢复出0101的数据后,再去找从哪个位置开始分隔,分隔出的每7bit数据里有4个1和3个0,并至少持续40个有效数据。

最后,还要把两个序列区分出来,也就是说要把RROORROORROO,分开为两列RORORORORO,然后验证这两列数据是否相等(帮助判断通信质量)。这两列数据的开头有0x0f和0x66会告诉你他们属于A列还是B列。

猜你喜欢

转载自blog.csdn.net/shukebeta008/article/details/127171794
B
a^b
A/B