big patch from Graham - many thx
This commit is contained in:
parent
187f5d180c
commit
93b19531ee
|
@ -54,28 +54,29 @@ the mail list.
|
|||
|
||||
EXTERNAL UDP AUDIO SERVER
|
||||
=========================
|
||||
Because the GR block no longer outputs audio samples the audio is routed
|
||||
via UDP instead. After starting rx.py in a separate terminal window run
|
||||
Starting rx.py with the "-w -W host" options directs udp audio data to
|
||||
be sent over the network to the specified remote host. It can then be
|
||||
received and played back with either of the following methods:
|
||||
1. Execute ./audio.sh on a remote machine equipped with python2.7,
|
||||
libasound.so.2 and the sockaudio.py file.
|
||||
-or-
|
||||
2. Execute the command:
|
||||
nc -kluvw 1 127.0.0.1 23456 | aplay -c1 -f S16_LE -r 8000
|
||||
|
||||
Notes:
|
||||
1. Each time rx.py is restarted you must also restart the audio server
|
||||
(change to the terminal window where the server is running and hit Ctrl-C,
|
||||
then up-arrow, then Enter).
|
||||
NOTE: audio underruns are to be expected when using nc | aplay as the
|
||||
pcm stream is interrupted every time a radio transmission ends. The
|
||||
sockaudio player is designed to handle this more gracefully, and generally
|
||||
only underruns due to high cpu utilization or reception/decoding errors.
|
||||
|
||||
2. When doing audio output it is no longer necessary to specify the
|
||||
"-V" option in rx.py. However for now as a hack it's necessary to
|
||||
give the "-w" (wireshark) rx.py option. If/when the hack is removed and
|
||||
wireshark is fixed it will no longer be necessary to use "-w". The "-2"
|
||||
option is still required when using phase 2/TDMA.
|
||||
INTERNAL AUDIO SERVER
|
||||
=====================
|
||||
Starting rx.py with the "-U" command line option enables an internal udp
|
||||
audio server which will play received audio through the default ALSA
|
||||
device. Optionally you may specify which ALSA device to use by setting
|
||||
the "-O audio_out" option along with "-U".
|
||||
|
||||
3. If the use of "aplay" in this manner causes no problems (including
|
||||
running in a VM, etc), the command will eventually be rolled into rx.py
|
||||
and it will no longer be necessary to run the server manually in this
|
||||
way. Reports are needed from VM users both with and without pulse, and
|
||||
Phase II/TDMA users
|
||||
|
||||
4. "aplay" is in package "alsa-utils" and "nc" in "netcat-openbsd"
|
||||
As of this writing (Aug 2017) it is still necessary to specify the "-w"
|
||||
(wireshark) option if using either the internal or external audio server.
|
||||
|
||||
PLOT MODES
|
||||
==========
|
||||
|
|
|
@ -33,11 +33,18 @@ _def_sps = 10
|
|||
|
||||
GNUPLOT = '/usr/bin/gnuplot'
|
||||
|
||||
FFT_AVG = 0.25
|
||||
FFT_BINS = 512
|
||||
|
||||
class wrap_gp(object):
|
||||
def __init__(self, sps=_def_sps):
|
||||
self.sps = sps
|
||||
self.center_freq = None
|
||||
self.relative_freq = 0.0
|
||||
self.width = None
|
||||
self.ffts = ()
|
||||
self.freqs = ()
|
||||
self.avg_pwr = np.zeros(FFT_BINS)
|
||||
self.buf = []
|
||||
|
||||
self.attach_gp()
|
||||
|
@ -82,49 +89,57 @@ class wrap_gp(object):
|
|||
self.buf = []
|
||||
plots.append('"-" with dots')
|
||||
elif mode == 'fft':
|
||||
ffbuf = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
|
||||
ffbuf = np.fft.fftshift(ffbuf)
|
||||
for i in xrange(len(ffbuf)):
|
||||
if self.center_freq and self.width:
|
||||
f = (self.center_freq - self.width / 2.0) / 1e6
|
||||
w = self.width / 1e6
|
||||
s += '%f\t%f\n' % (f + i*(w/BUFSZ), 20 * np.log10(np.abs(ffbuf[i])))
|
||||
else:
|
||||
s += '%f\n' % (20 * np.log10(np.abs(ffbuf[i])))
|
||||
self.ffts = np.fft.fft(self.buf * np.blackman(BUFSZ)) / (0.42 * BUFSZ)
|
||||
self.ffts = np.fft.fftshift(self.ffts)
|
||||
self.freqs = np.fft.fftfreq(len(self.ffts))
|
||||
self.freqs = np.fft.fftshift(self.freqs)
|
||||
if self.center_freq and self.width:
|
||||
self.freqs = ((self.freqs * self.width) + self.center_freq) / 1e6
|
||||
for i in xrange(len(self.ffts)):
|
||||
self.avg_pwr[i] = ((1.0 - FFT_AVG) * self.avg_pwr[i]) + (FFT_AVG * np.abs(self.ffts[i]))
|
||||
s += '%f\t%f\n' % (self.freqs[i], 20 * np.log10(self.avg_pwr[i]))
|
||||
s += 'e\n'
|
||||
self.buf = []
|
||||
plots.append('"-" with lines')
|
||||
self.buf = []
|
||||
|
||||
h= 'set terminal x11 noraise\n'
|
||||
background = 'set object 1 circle from screen 0,0 to screen 1,1 fillcolor rgb"black"\n'
|
||||
#background = 'set object 1 circle at screen 0,0 size screen 1 fillcolor rgb"black"\n' #FIXME!
|
||||
background = ''
|
||||
h+= 'set key off\n'
|
||||
if mode == 'constellation':
|
||||
h += background
|
||||
h+= background
|
||||
h+= 'set size square\n'
|
||||
h+= 'set xrange [-1:1]\n'
|
||||
h+= 'set yrange [-1:1]\n'
|
||||
elif mode == 'eye':
|
||||
h += background
|
||||
h+= background
|
||||
h+= 'set yrange [-4:4]\n'
|
||||
elif mode == 'symbol':
|
||||
h += background
|
||||
h+= background
|
||||
h+= 'set yrange [-4:4]\n'
|
||||
elif mode == 'fft':
|
||||
h+= 'unset arrow; unset title\n'
|
||||
h+= 'set xrange [%f:%f]\n' % (self.freqs[0], self.freqs[len(self.freqs)-1])
|
||||
h+= 'set yrange [-100:0]\n'
|
||||
h+= 'set xlabel "Frequency"\n'
|
||||
h+= 'set ylabel "Power(dB)"\n'
|
||||
h+= 'set grid\n'
|
||||
if self.center_freq:
|
||||
h += 'set title "%f"\n' % (self.center_freq / 1e6)
|
||||
arrow_pos = (self.center_freq - self.relative_freq) / 1e6
|
||||
h+= 'set arrow from %f, graph 0 to %f, graph 1 nohead\n' % (arrow_pos, arrow_pos)
|
||||
h+= 'set title "Tuned to %f Mhz"\n' % ((self.center_freq - self.relative_freq) / 1e6)
|
||||
dat = '%splot %s\n%s' % (h, ','.join(plots), s)
|
||||
self.gp.stdin.write(dat)
|
||||
return consumed
|
||||
|
||||
def set_center_freq(self, f):
|
||||
sys.stderr.write('set_center_freq: %s\n' % f)
|
||||
self.center_freq = f
|
||||
|
||||
def set_relative_freq(self, f):
|
||||
self.relative_freq = f
|
||||
|
||||
def set_width(self, w):
|
||||
sys.stderr.write('set_width: %f\n' % w)
|
||||
self.width = w
|
||||
|
||||
class eye_sink_f(gr.sync_block):
|
||||
|
@ -183,7 +198,7 @@ class fft_sink_c(gr.sync_block):
|
|||
if self.skip == 50:
|
||||
self.skip = 0
|
||||
in0 = input_items[0]
|
||||
self.gnuplot.plot(in0, 512, mode='fft')
|
||||
self.gnuplot.plot(in0, FFT_BINS, mode='fft')
|
||||
return len(input_items[0])
|
||||
|
||||
def kill(self):
|
||||
|
@ -191,6 +206,10 @@ class fft_sink_c(gr.sync_block):
|
|||
|
||||
def set_center_freq(self, f):
|
||||
self.gnuplot.set_center_freq(f)
|
||||
self.gnuplot.set_relative_freq(0.0)
|
||||
|
||||
def set_relative_freq(self, f):
|
||||
self.gnuplot.set_relative_freq(f)
|
||||
|
||||
def set_width(self, w):
|
||||
self.gnuplot.set_width(w)
|
||||
|
|
|
@ -65,7 +65,8 @@ from gr_gnuplot import fft_sink_c
|
|||
from gr_gnuplot import symbol_sink_f
|
||||
from gr_gnuplot import eye_sink_f
|
||||
|
||||
from terminal import curses_terminal
|
||||
from terminal import curses_terminal
|
||||
from sockaudio import socket_audio
|
||||
|
||||
#speeds = [300, 600, 900, 1200, 1440, 1800, 1920, 2400, 2880, 3200, 3600, 3840, 4000, 4800, 6000, 6400, 7200, 8000, 9600, 14400, 19200]
|
||||
speeds = [4800, 6000]
|
||||
|
@ -120,7 +121,9 @@ class p25_rx_block (gr.top_block):
|
|||
parser.add_option("-G", "--gain-mu", type="eng_float", default=0.025, help="gardner gain")
|
||||
parser.add_option("-N", "--gains", type="string", default=None, help="gain settings")
|
||||
parser.add_option("-O", "--audio-output", type="string", default="default", help="audio output device name")
|
||||
parser.add_option("-U", "--udp-player", action="store_true", default=False, help="enable built-in udp audio player")
|
||||
parser.add_option("-q", "--freq-corr", type="eng_float", default=0.0, help="frequency correction")
|
||||
parser.add_option("-d", "--fine-tune", type="eng_float", default=0.0, help="fine tuning")
|
||||
parser.add_option("-2", "--phase2-tdma", action="store_true", default=False, help="enable phase2 tdma decode")
|
||||
parser.add_option("-Z", "--decim-amt", type="int", default=1, help="spectrum decimation")
|
||||
(options, args) = parser.parse_args()
|
||||
|
@ -227,6 +230,12 @@ class p25_rx_block (gr.top_block):
|
|||
# attach terminal thread
|
||||
self.terminal = curses_terminal(self.input_q, self.output_q)
|
||||
|
||||
# attach audio thread
|
||||
if self.options.udp_player:
|
||||
self.audio = socket_audio("127.0.0.1", WIRESHARK_PORT, self.options.audio_output)
|
||||
else:
|
||||
self.audio = None
|
||||
|
||||
# setup common flow graph elements
|
||||
#
|
||||
def __build_graph(self, source, capture_rate):
|
||||
|
@ -236,7 +245,8 @@ class p25_rx_block (gr.top_block):
|
|||
|
||||
self.rx_q = gr.msg_queue(100)
|
||||
udp_port = 0
|
||||
if self.options.wireshark:
|
||||
|
||||
if self.options.udp_player or self.options.wireshark or (self.options.wireshark_host != "127.0.0.1"):
|
||||
udp_port = WIRESHARK_PORT
|
||||
|
||||
self.tdma_state = False
|
||||
|
@ -256,7 +266,7 @@ class p25_rx_block (gr.top_block):
|
|||
self.demod = p25_demodulator.p25_demod_fb(input_rate=capture_rate)
|
||||
else: # complex input
|
||||
# local osc
|
||||
self.lo_freq = self.options.offset
|
||||
self.lo_freq = self.options.offset + self.options.fine_tune
|
||||
if self.options.audio_if or self.options.ifile or self.options.input:
|
||||
self.lo_freq += self.options.calibration
|
||||
self.demod = p25_demodulator.p25_demod_cb( input_rate = capture_rate,
|
||||
|
@ -268,10 +278,6 @@ class p25_rx_block (gr.top_block):
|
|||
costas_alpha = self.options.costas_alpha,
|
||||
symbol_rate = self.symbol_rate)
|
||||
|
||||
udp_port = 0
|
||||
if self.options.wireshark:
|
||||
udp_port = WIRESHARK_PORT
|
||||
|
||||
num_ambe = 0
|
||||
if self.options.phase2_tdma:
|
||||
num_ambe = 1
|
||||
|
@ -377,20 +383,26 @@ class p25_rx_block (gr.top_block):
|
|||
freq = params['freq']
|
||||
offset = params['offset']
|
||||
center_freq = params['center_frequency']
|
||||
fine_tune = self.options.fine_tune
|
||||
|
||||
if self.options.hamlib_model:
|
||||
self.hamlib.set_freq(freq)
|
||||
elif params['center_frequency']:
|
||||
relative_freq = center_freq - freq
|
||||
if abs(relative_freq + self.options.offset) > self.channel_rate / 2:
|
||||
print '***unable to tune Local Oscillator to offset %d Hz' % (relative_freq + self.options.offset)
|
||||
print '***limit is one half of sample-rate %d = %d' % (self.channel_rate, self.channel_rate / 2)
|
||||
print '***request for frequency %d rejected' % freq
|
||||
|
||||
self.lo_freq = self.options.offset + relative_freq
|
||||
self.demod.set_relative_frequency(self.lo_freq)
|
||||
self.set_freq(center_freq + offset)
|
||||
#self.spectrum.set_baseband_freq(center_freq)
|
||||
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune not possible
|
||||
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
|
||||
self.set_freq(freq + offset) # direct tune instead
|
||||
else:
|
||||
self.lo_freq = self.options.offset + relative_freq + fine_tune
|
||||
if self.demod.set_relative_frequency(self.lo_freq): # relative tune successful
|
||||
self.set_freq(center_freq + offset)
|
||||
if self.fft_sink:
|
||||
self.fft_sink.set_relative_freq(self.lo_freq)
|
||||
else:
|
||||
self.lo_freq = self.options.offset + self.options.fine_tune # relative tune unsuccessful
|
||||
self.demod.set_relative_frequency(self.lo_freq) # reset demod relative freq
|
||||
self.set_freq(freq + offset) # direct tune instead
|
||||
else:
|
||||
self.set_freq(freq + offset)
|
||||
|
||||
|
@ -648,7 +660,10 @@ if __name__ == "__main__":
|
|||
except:
|
||||
sys.stderr.write('main: exception occurred\n')
|
||||
sys.stderr.write('main: exception:\n%s\n' % traceback.format_exc())
|
||||
tb.terminal.end_curses()
|
||||
if tb.terminal:
|
||||
tb.terminal.end_curses()
|
||||
if tb.audio:
|
||||
tb.audio.stop()
|
||||
tb.stop()
|
||||
if tb.kill_sink:
|
||||
tb.kill_sink.kill()
|
||||
|
|
|
@ -520,7 +520,7 @@ class rx_ctl (object):
|
|||
VC = 3
|
||||
self.states = _states
|
||||
|
||||
self.state = self.states.CC
|
||||
self.current_state = self.states.CC
|
||||
self.trunked_systems = {}
|
||||
self.frequency_set = frequency_set
|
||||
self.debug = debug
|
||||
|
@ -531,6 +531,8 @@ class rx_ctl (object):
|
|||
self.TGID_SKIP_TIME = 1.0 # TODO: make more configurable
|
||||
self.current_nac = None
|
||||
self.current_id = 0
|
||||
self.current_tgid = None
|
||||
self.current_slot = None
|
||||
self.TSYS_HOLD_TIME = 3.0 # TODO: make more configurable
|
||||
self.wait_until = time.time()
|
||||
self.configs = {}
|
||||
|
@ -728,7 +730,7 @@ class rx_ctl (object):
|
|||
return
|
||||
s = s[2:]
|
||||
if self.debug > 10:
|
||||
print "nac %x type %d at %f state %d len %d" %(nac, type, time.time(), self.state, len(s))
|
||||
print "nac %x type %d at %f state %d len %d" %(nac, type, time.time(), self.current_state, len(s))
|
||||
if (type == 7 or type == 12) and nac not in self.trunked_systems:
|
||||
if not self.configs:
|
||||
# TODO: allow whitelist/blacklist rather than blind automatic-add
|
||||
|
@ -750,7 +752,7 @@ class rx_ctl (object):
|
|||
mbt_data = (mbt_data << 8) + ord(c)
|
||||
opcode = (header >> 16) & 0x3f
|
||||
if self.debug > 10:
|
||||
print "type %d at %f state %d len %d/%d opcode %x [%x/%x]" %(type, time.time(), self.state, len(s1), len(s2), opcode, header,mbt_data)
|
||||
print "type %d at %f state %d len %d/%d opcode %x [%x/%x]" %(type, time.time(), self.current_state, len(s1), len(s2), opcode, header,mbt_data)
|
||||
updated += self.trunked_systems[nac].decode_mbt_data(opcode, header << 16, mbt_data << 32)
|
||||
|
||||
if nac != self.current_nac:
|
||||
|
@ -882,10 +884,14 @@ class rx_ctl (object):
|
|||
new_nac = None
|
||||
new_slot = None
|
||||
|
||||
if command == 'timeout' or command == 'duid15':
|
||||
if command == 'timeout':
|
||||
if self.current_state == self.states.CC:
|
||||
if self.debug > 0:
|
||||
sys.stderr.write("[%f] control channel timeout\n" % time.time())
|
||||
tsys.cc_timeouts += 1
|
||||
elif self.current_state != self.states.CC and curr_time - self.last_tdma_vf > self.P2_GRACE_TIME:
|
||||
if self.debug > 0:
|
||||
sys.stderr.write("[%f] voice timeout\n" % time.time())
|
||||
new_state = self.states.CC
|
||||
new_frequency = tsys.trunk_cc
|
||||
elif command == 'update':
|
||||
|
@ -895,15 +901,23 @@ class rx_ctl (object):
|
|||
desired_tgid = self.tgid_hold
|
||||
new_frequency, new_tgid, tdma_slot = tsys.find_talkgroup(curr_time, tgid=desired_tgid)
|
||||
if new_frequency:
|
||||
if self.debug > 0:
|
||||
sys.stderr.write("[%f] voice update: tg(%s), freq(%s), slot(%s)\n" % (time.time(), new_tgid, new_frequency, tdma_slot))
|
||||
new_state = self.states.TO_VC
|
||||
self.current_tgid = new_tgid
|
||||
new_slot = tdma_slot
|
||||
elif command == 'duid3' or command == 'tdma_duid3':
|
||||
elif command == 'tdma_duid3': # tdma termination, no channel release (MAC_HANGTIME)
|
||||
if self.current_state != self.states.CC:
|
||||
self.tgid_hold = self.current_tgid
|
||||
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
|
||||
self.wait_until = curr_time + self.TSYS_HOLD_TIME
|
||||
self.last_tdma_vf = curr_time
|
||||
elif command == 'duid3' or command == 'duid15' or command == 'tdma_duid15': # fdma/tdma termination with channel release
|
||||
if self.current_state != self.states.CC:
|
||||
new_state = self.states.CC
|
||||
new_frequency = tsys.trunk_cc
|
||||
elif command == 'duid0' or command == 'duid5' or command == 'duid10' or command == 'tdma_duid5':
|
||||
if self.state == self.states.TO_VC:
|
||||
if self.current_state == self.states.TO_VC:
|
||||
new_state = self.states.VC
|
||||
self.tgid_hold = self.current_tgid
|
||||
self.tgid_hold_until = max(curr_time + self.TGID_HOLD_TIME, self.tgid_hold_until)
|
||||
|
@ -917,7 +931,8 @@ class rx_ctl (object):
|
|||
self.tgid_hold = self.current_tgid
|
||||
self.tgid_hold_until = curr_time + 86400 * 10000
|
||||
self.hold_mode = True
|
||||
sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
|
||||
if self.debug > 0:
|
||||
sys.stderr.write ('set hold until %f tgid %s\n' % (self.tgid_hold_until, self.current_tgid))
|
||||
elif self.hold_mode is True:
|
||||
self.current_tgid = None
|
||||
self.tgid_hold = None
|
||||
|
|
|
@ -132,6 +132,7 @@ p25_frame_assembler_impl::general_work (int noutput_items,
|
|||
int rc = p2tdma.handle_frame();
|
||||
if (rc > -1)
|
||||
p25p2_queue_msg(rc);
|
||||
p1fdma.reset_timer(); // prevent P1 timeouts due to long TDMA transmissions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,6 +239,13 @@ p25p1_fdma::process_duid(uint32_t const duid, uint32_t const nac, uint8_t const
|
|||
// msg.reset();
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::reset_timer()
|
||||
{
|
||||
//update last_qtime with current time
|
||||
gettimeofday(&last_qtime, 0);
|
||||
}
|
||||
|
||||
void
|
||||
p25p1_fdma::rx_sym (const uint8_t *syms, int nsyms)
|
||||
{
|
||||
|
|
|
@ -64,6 +64,7 @@ namespace gr {
|
|||
p25p1_voice_decode p1voice_decode;
|
||||
|
||||
public:
|
||||
void reset_timer();
|
||||
void rx_sym (const uint8_t *syms, int nsyms);
|
||||
p25p1_fdma(const char* udp_host, int port, int debug, bool do_imbe, bool do_output, bool do_msgq, gr::msg_queue::sptr queue, std::deque<int16_t> &output_queue, bool do_audio_output);
|
||||
~p25p1_fdma();
|
||||
|
|
|
@ -118,7 +118,13 @@ int p25p2_tdma::process_mac_pdu(const uint8_t byte_buf[], unsigned int len)
|
|||
unsigned int opcode = (byte_buf[0] >> 5) & 0x7;
|
||||
unsigned int offset = (byte_buf[0] >> 2) & 0x7;
|
||||
// maps sacch opcodes into phase I duid values
|
||||
static const int opcode_map[8] = {3, 5, 3, 3, 5, 3, 3, 3};
|
||||
// 0, 5, 7 - Reserved
|
||||
// 1 - MAC_PTT
|
||||
// 2 - MAC_END_PTT
|
||||
// 3 - MAC_IDLE
|
||||
// 4 - MAC_ACTIVE
|
||||
// 6 - MAC_HANGTIME
|
||||
static const int opcode_map[8] = {3, 5, 15, 15, 5, 3, 3, 3};
|
||||
return opcode_map[opcode];
|
||||
// TODO: decode MAC PDU's
|
||||
}
|
||||
|
@ -174,6 +180,11 @@ int p25p2_tdma::handle_acch_frame(const uint8_t dibits[], bool fast)
|
|||
} else {
|
||||
crc_errors++;
|
||||
}
|
||||
// write a zero audio sample (2 bytes) at end of voice to trigger pcm drain
|
||||
if (((rc == 3) || (rc == 15)) && (write_sock > 0)) {
|
||||
memset(write_buf, 0, 2);
|
||||
sendto(write_sock, write_buf, 2, 0, (struct sockaddr *)&write_sock_addr, sizeof(write_sock_addr));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue