Compare commits
4 commits
master
...
Compressio
Author | SHA1 | Date | |
---|---|---|---|
d221755ef1 | |||
d7100d42fd | |||
bbad0a0a70 | |||
b2dbe95d2f |
6 changed files with 2001 additions and 51 deletions
113
Radio Tuner Notes.md
Normal file
113
Radio Tuner Notes.md
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
# Radio Tuner Notes
|
||||||
|
|
||||||
|
This is how frequencies in the FM spectrum are delegated
|
||||||
|
|
||||||
|
| Label | Frequency |
|
||||||
|
| --------- | --------- |
|
||||||
|
| Min | 87.5 MHz |
|
||||||
|
| Max | 108.0 MHz |
|
||||||
|
| Step Size | 100 KHz |
|
||||||
|
|
||||||
|
Since we cannot represent these values in the UI (using the dial element), we will instead use these values and multiply by $10^5$
|
||||||
|
|
||||||
|
| Label | Frequency |
|
||||||
|
| --------- | --------- |
|
||||||
|
| Min | 875 |
|
||||||
|
| Max | 1080 |
|
||||||
|
| Step Size | 1 |
|
||||||
|
|
||||||
|
## Message Format
|
||||||
|
I think I would like to implement the GNU-Radio Script as a client server application that binds to some `localhost` port and can operate with the following messages
|
||||||
|
|
||||||
|
| Message Format | Description |
|
||||||
|
| -------------- | ---------------------------------------------------------- |
|
||||||
|
| ?VER; | Outputs the current version of the server |
|
||||||
|
| ?STATUS; | Gets the status of the program |
|
||||||
|
| ?FREQ; | Queries the frequency that the program is currently set at |
|
||||||
|
| :FREQ=%f; | Sets the frequency according to the float %f |
|
||||||
|
| :QUIT; | Shuts down the server |
|
||||||
|
|
||||||
|
To test out the server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
telnet localhost 65432
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## BUGS/ENHANCEMENTS`
|
||||||
|
|
||||||
|
**Bug Fix:** Investigate different event handlers because the one that's currently used will send too many frequency set messages over to the server. (Maybe one that's like "when you release the dial...")
|
||||||
|
|
||||||
|
**Feature Request:** Favorite radio stations section
|
||||||
|
|
||||||
|
**Enhancement:** Listen again once the client closes the connection **[FIXED]**
|
||||||
|
|
||||||
|
**Enhancement:** Allow multiple connections to the socket and have the server handle it appropriately
|
||||||
|
|
||||||
|
**Enhancement:** Probably should more intelligently receive the number of bytes as opposed to setting it to an arbitrarily high number.
|
||||||
|
|
||||||
|
**Enhancement:** Maybe use local sockets? (Nah, this will prevent me from having the Pi contain the radio and controlling from my laptop) **[INVALID]**
|
||||||
|
|
||||||
|
**Bug Fix:** Send out full number instead of scientific notation. **[Not Needed]**
|
||||||
|
|
||||||
|
**Bug Fix:** Include appropriate error messages for when the client can't connect.
|
||||||
|
|
||||||
|
**Bug Fix:** Kick the client back to the connect screen when it disconnects.
|
||||||
|
|
||||||
|
## Connect Screen
|
||||||
|
|
||||||
|
In this screen the user can input the `IP address` and `Port` that the SDR server is hosted on.
|
||||||
|
|
||||||
|
From there, it will confirm if it can connect successfully and if so, it will present the dial screen.
|
||||||
|
|
||||||
|
There should be some sort of notification if the client loses connection. Maybe popping them back up into the connect screen.
|
||||||
|
|
||||||
|
## Sending Audio Over the Network
|
||||||
|
|
||||||
|
This requires the UDP Sink box in GNU Radio. Make sure you set the type to `float`.
|
||||||
|
|
||||||
|
To hear the output do the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
netcat -ul address port | aplay -c num_channels -t raw -r sample_rate -f FLOAT_LE -
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells netcat to listen to UDP on localhost:port and play it with ALSA with the above settings
|
||||||
|
|
||||||
|
My current configuration
|
||||||
|
|
||||||
|
| Name | Value |
|
||||||
|
| ------------------ | ------------- |
|
||||||
|
| Address | x.x.x.x |
|
||||||
|
| Port | 7654 |
|
||||||
|
| Number of Channels | 1 |
|
||||||
|
| Sample Rate | 48000 |
|
||||||
|
| Sample Type | Float |
|
||||||
|
| Endiness | Little Endian |
|
||||||
|
|
||||||
|
### Compressing Audio
|
||||||
|
|
||||||
|
Command to convert the raw input stream into a ogg stream in standard output with a bitrate limited to 32 kb/s
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffmpeg -f f32le -ar 48k -ac 1 -i pipe:0 -f ogg -ab 32k pipe:1
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
So we should have the GNURadio program still act as a server to be able to change channels, but this time write the raw audio to a file descriptor from which ffmpeg grabs and compresses it to an ogg stream that gets transmitted over the network.
|
||||||
|
|
||||||
|
Current command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ffmpeg -f f32le -ar 48k -ac 1 -i pipe:0 -f ogg -ab 32k pipe:1 < raw_audio_pipe | nc -l 127.0.0.1 6003
|
||||||
|
```
|
||||||
|
|
||||||
|
Grab audio from named pipe that gnuradio is outputting to, then convert it to ogg and compress it with a 32k bitrate and then send that audio over the network.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**TODO:** Convert the gnuradio blocks program to be a server again
|
16
RadioTuner.pro
Normal file
16
RadioTuner.pro
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
TEMPLATE = app
|
||||||
|
TARGET = RadioTuner
|
||||||
|
INCLUDEPATH += .
|
||||||
|
|
||||||
|
# The following define makes your compiler warn you if you use any
|
||||||
|
# feature of Qt which has been marked as deprecated (the exact warnings
|
||||||
|
# depend on your compiler). Please consult the documentation of the
|
||||||
|
# deprecated API in order to know how to port your code away from it.
|
||||||
|
DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
|
|
||||||
|
# Input
|
||||||
|
HEADERS += client.h
|
||||||
|
SOURCES += client.cpp main.cpp
|
||||||
|
RESOURCES += qml.qrc
|
||||||
|
|
||||||
|
QT += network qml quick
|
|
@ -14,7 +14,6 @@ Client::~Client() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::connect(QString hostname, int port) {
|
void Client::connect(QString hostname, int port) {
|
||||||
assert (port > 0);
|
|
||||||
socket->connectToHost(hostname, port);
|
socket->connectToHost(hostname, port);
|
||||||
socket->waitForConnected();
|
socket->waitForConnected();
|
||||||
std::cout << "Connected" << std::endl;
|
std::cout << "Connected" << std::endl;
|
||||||
|
|
1774
radio_tuner.grc
Normal file
1774
radio_tuner.grc
Normal file
File diff suppressed because it is too large
Load diff
24
start
Executable file
24
start
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/bash
|
||||||
|
compress=1
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
echo "Shutting Down"
|
||||||
|
rm ./raw_audio_pipe
|
||||||
|
kill $child_pid
|
||||||
|
}
|
||||||
|
|
||||||
|
mkfifo ./raw_audio_pipe
|
||||||
|
|
||||||
|
# Run Radio Server Program in Background
|
||||||
|
./tuner.py &
|
||||||
|
child_pid=$!
|
||||||
|
|
||||||
|
if [ $compress -eq 1 ]
|
||||||
|
then
|
||||||
|
ffmpeg -f f32le -ar 48k -ac 1 -i pipe:0 -f ogg -ab 32k pipe:1 < ./raw_audio_pipe | nc -kl 127.0.0.1 6003
|
||||||
|
|
||||||
|
else
|
||||||
|
tail -f raw_audio_pipe | nc -kl 127.0.0.1 6003
|
||||||
|
fi
|
||||||
|
|
||||||
|
cleanup
|
122
tuner.py
122
tuner.py
|
@ -3,12 +3,11 @@
|
||||||
##################################################
|
##################################################
|
||||||
# GNU Radio Python Flow Graph
|
# GNU Radio Python Flow Graph
|
||||||
# Title: Top Block
|
# Title: Top Block
|
||||||
# Generated: Sun May 12 22:10:45 2019
|
# Generated: Sat Jun 1 12:54:09 2019
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|
||||||
from gnuradio import analog
|
from gnuradio import analog
|
||||||
from gnuradio import audio
|
|
||||||
from gnuradio import blocks
|
from gnuradio import blocks
|
||||||
from gnuradio import eng_notation
|
from gnuradio import eng_notation
|
||||||
from gnuradio import filter
|
from gnuradio import filter
|
||||||
|
@ -21,6 +20,7 @@ import time
|
||||||
import socket
|
import socket
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class top_block(gr.top_block):
|
class top_block(gr.top_block):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -29,15 +29,17 @@ class top_block(gr.top_block):
|
||||||
##################################################
|
##################################################
|
||||||
# Variables
|
# Variables
|
||||||
##################################################
|
##################################################
|
||||||
self.samp_rate = samp_rate = 1536000
|
self.samp_rate = samp_rate = 2.2e6
|
||||||
self.frequency = frequency = 96900000
|
self.freq = freq = 101.5e6
|
||||||
|
self.audio_rate = audio_rate = 48000
|
||||||
|
self.audio_interp = audio_interp = 4
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# Blocks
|
# Blocks
|
||||||
##################################################
|
##################################################
|
||||||
self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' )
|
self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' )
|
||||||
self.rtlsdr_source_0.set_sample_rate(samp_rate)
|
self.rtlsdr_source_0.set_sample_rate(samp_rate)
|
||||||
self.rtlsdr_source_0.set_center_freq(frequency, 0)
|
self.rtlsdr_source_0.set_center_freq(freq, 0)
|
||||||
self.rtlsdr_source_0.set_freq_corr(0, 0)
|
self.rtlsdr_source_0.set_freq_corr(0, 0)
|
||||||
self.rtlsdr_source_0.set_dc_offset_mode(2, 0)
|
self.rtlsdr_source_0.set_dc_offset_mode(2, 0)
|
||||||
self.rtlsdr_source_0.set_iq_balance_mode(2, 0)
|
self.rtlsdr_source_0.set_iq_balance_mode(2, 0)
|
||||||
|
@ -48,22 +50,25 @@ class top_block(gr.top_block):
|
||||||
self.rtlsdr_source_0.set_antenna('', 0)
|
self.rtlsdr_source_0.set_antenna('', 0)
|
||||||
self.rtlsdr_source_0.set_bandwidth(0, 0)
|
self.rtlsdr_source_0.set_bandwidth(0, 0)
|
||||||
|
|
||||||
self.low_pass_filter_0 = filter.fir_filter_ccf(4, firdes.low_pass(
|
self.rational_resampler_xxx_0 = filter.rational_resampler_ccc(
|
||||||
1, samp_rate, 500000, 60000, firdes.WIN_KAISER, 6.76))
|
interpolation=audio_rate * audio_interp,
|
||||||
self.blocks_udp_sink_0 = blocks.udp_sink(gr.sizeof_float*1, '127.0.0.1', 7654, 1472, True)
|
decimation=int(samp_rate),
|
||||||
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate,True)
|
taps=None,
|
||||||
|
fractional_bw=None,
|
||||||
|
)
|
||||||
|
self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1, 'raw_audio_pipe', False)
|
||||||
|
self.blocks_file_sink_0.set_unbuffered(False)
|
||||||
self.analog_wfm_rcv_0 = analog.wfm_rcv(
|
self.analog_wfm_rcv_0 = analog.wfm_rcv(
|
||||||
quad_rate=384000,
|
quad_rate=audio_rate * audio_interp,
|
||||||
audio_decimation=8,
|
audio_decimation=audio_interp,
|
||||||
)
|
)
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# Connections
|
# Connections
|
||||||
##################################################
|
##################################################
|
||||||
self.connect((self.analog_wfm_rcv_0, 0), (self.blocks_udp_sink_0, 0))
|
self.connect((self.analog_wfm_rcv_0, 0), (self.blocks_file_sink_0, 0))
|
||||||
self.connect((self.blocks_throttle_0, 0), (self.low_pass_filter_0, 0))
|
self.connect((self.rational_resampler_xxx_0, 0), (self.analog_wfm_rcv_0, 0))
|
||||||
self.connect((self.low_pass_filter_0, 0), (self.analog_wfm_rcv_0, 0))
|
self.connect((self.rtlsdr_source_0, 0), (self.rational_resampler_xxx_0, 0))
|
||||||
self.connect((self.rtlsdr_source_0, 0), (self.blocks_throttle_0, 0))
|
|
||||||
|
|
||||||
def get_samp_rate(self):
|
def get_samp_rate(self):
|
||||||
return self.samp_rate
|
return self.samp_rate
|
||||||
|
@ -71,20 +76,64 @@ class top_block(gr.top_block):
|
||||||
def set_samp_rate(self, samp_rate):
|
def set_samp_rate(self, samp_rate):
|
||||||
self.samp_rate = samp_rate
|
self.samp_rate = samp_rate
|
||||||
self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
|
self.rtlsdr_source_0.set_sample_rate(self.samp_rate)
|
||||||
self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate, 500000, 60000, firdes.WIN_KAISER, 6.76))
|
|
||||||
self.blocks_throttle_0.set_sample_rate(self.samp_rate)
|
|
||||||
|
|
||||||
def get_frequency(self):
|
def get_freq(self):
|
||||||
return self.frequency
|
return self.freq
|
||||||
|
|
||||||
def set_frequency(self, frequency):
|
def set_freq(self, freq):
|
||||||
self.frequency = frequency
|
self.freq = freq
|
||||||
self.rtlsdr_source_0.set_center_freq(self.frequency, 0)
|
self.rtlsdr_source_0.set_center_freq(self.freq, 0)
|
||||||
|
|
||||||
|
def get_audio_rate(self):
|
||||||
|
return self.audio_rate
|
||||||
|
|
||||||
|
def set_audio_rate(self, audio_rate):
|
||||||
|
self.audio_rate = audio_rate
|
||||||
|
|
||||||
|
def get_audio_interp(self):
|
||||||
|
return self.audio_interp
|
||||||
|
|
||||||
|
def set_audio_interp(self, audio_interp):
|
||||||
|
self.audio_interp = audio_interp
|
||||||
|
|
||||||
|
def is_valid_frequency(frequency):
|
||||||
|
return frequency >= 87500000 and frequency <= 108000000
|
||||||
|
|
||||||
|
def parse_commands(conn, tb, commands):
|
||||||
|
quit = False
|
||||||
|
for command in commands:
|
||||||
|
if command == "":
|
||||||
|
continue
|
||||||
|
elif command[0] == '?': # Query Section
|
||||||
|
if command[1:] == "VER":
|
||||||
|
conn.sendall("Version 0.1\n")
|
||||||
|
elif command[1:] == "FREQ":
|
||||||
|
conn.sendall(str(tb.get_freq()) + '\n')
|
||||||
|
else:
|
||||||
|
print("UNKNOWN COMMAND: ", command)
|
||||||
|
elif command[0] == ':': # Setter Section
|
||||||
|
if command[1:5] == "FREQ":
|
||||||
|
print("FREQUENCY SET")
|
||||||
|
frequency = float(command[6:])
|
||||||
|
if is_valid_frequency(frequency):
|
||||||
|
tb.set_freq(frequency)
|
||||||
|
else:
|
||||||
|
print("ERROR: Invalid Frequency ", frequency)
|
||||||
|
elif command[1:] == "QUIT":
|
||||||
|
print("QUITTING")
|
||||||
|
quit = True
|
||||||
|
else:
|
||||||
|
print("UNKNOWN COMMAND: ", command)
|
||||||
|
else:
|
||||||
|
print("UNKNOWN COMMAND", command)
|
||||||
|
# Return state back to application
|
||||||
|
return {'quit': quit}
|
||||||
|
|
||||||
|
|
||||||
def main(top_block_cls=top_block, options=None):
|
def main(top_block_cls=top_block, options=None):
|
||||||
tb = top_block_cls()
|
tb = top_block_cls()
|
||||||
tb.start()
|
tb.start()
|
||||||
|
|
||||||
HOST = '127.0.0.1' # Loopback Address (Localhost)
|
HOST = '127.0.0.1' # Loopback Address (Localhost)
|
||||||
PORT = 65432 # Semi-Random Port that is non-priviledged (> 1023)
|
PORT = 65432 # Semi-Random Port that is non-priviledged (> 1023)
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
@ -103,34 +152,9 @@ def main(top_block_cls=top_block, options=None):
|
||||||
commands = re.split(";", data)
|
commands = re.split(";", data)
|
||||||
if data == "":
|
if data == "":
|
||||||
break
|
break
|
||||||
for command in commands:
|
state = parse_commands(conn, tb, commands)
|
||||||
if command == "":
|
if state['quit']:
|
||||||
continue
|
|
||||||
elif command[0] == '?':
|
|
||||||
# Query Section
|
|
||||||
if command[1:] == "VER":
|
|
||||||
conn.sendall("Version 0.1\n")
|
|
||||||
elif command[1:] == "FREQ":
|
|
||||||
conn.sendall(str(tb.get_frequency()) + '\n')
|
|
||||||
else:
|
|
||||||
print("UNKNOWN COMMAND: ", command)
|
|
||||||
elif command[0] == ':':
|
|
||||||
# Setter Section
|
|
||||||
if command[1:5] == "FREQ":
|
|
||||||
print("FREQUENCY SET")
|
|
||||||
frequency = float(command[6:])
|
|
||||||
if frequency >= 87500000 and frequency <= 108000000:
|
|
||||||
tb.set_frequency(frequency)
|
|
||||||
else:
|
|
||||||
print("ERROR: Invalid Frequency ", frequency)
|
|
||||||
elif command[1:] == "QUIT":
|
|
||||||
print("QUITTING")
|
|
||||||
quit = True
|
quit = True
|
||||||
else:
|
|
||||||
print("UNKNOWN COMMAND: ", command)
|
|
||||||
else:
|
|
||||||
print("UNKNOWN COMMAND", command)
|
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue