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) {
|
||||
assert (port > 0);
|
||||
socket->connectToHost(hostname, port);
|
||||
socket->waitForConnected();
|
||||
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
|
124
tuner.py
124
tuner.py
|
@ -3,12 +3,11 @@
|
|||
##################################################
|
||||
# GNU Radio Python Flow Graph
|
||||
# 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 audio
|
||||
from gnuradio import blocks
|
||||
from gnuradio import eng_notation
|
||||
from gnuradio import filter
|
||||
|
@ -21,6 +20,7 @@ import time
|
|||
import socket
|
||||
import re
|
||||
|
||||
|
||||
class top_block(gr.top_block):
|
||||
|
||||
def __init__(self):
|
||||
|
@ -29,15 +29,17 @@ class top_block(gr.top_block):
|
|||
##################################################
|
||||
# Variables
|
||||
##################################################
|
||||
self.samp_rate = samp_rate = 1536000
|
||||
self.frequency = frequency = 96900000
|
||||
self.samp_rate = samp_rate = 2.2e6
|
||||
self.freq = freq = 101.5e6
|
||||
self.audio_rate = audio_rate = 48000
|
||||
self.audio_interp = audio_interp = 4
|
||||
|
||||
##################################################
|
||||
# Blocks
|
||||
##################################################
|
||||
self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' )
|
||||
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_dc_offset_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_bandwidth(0, 0)
|
||||
|
||||
self.low_pass_filter_0 = filter.fir_filter_ccf(4, firdes.low_pass(
|
||||
1, samp_rate, 500000, 60000, firdes.WIN_KAISER, 6.76))
|
||||
self.blocks_udp_sink_0 = blocks.udp_sink(gr.sizeof_float*1, '127.0.0.1', 7654, 1472, True)
|
||||
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate,True)
|
||||
self.rational_resampler_xxx_0 = filter.rational_resampler_ccc(
|
||||
interpolation=audio_rate * audio_interp,
|
||||
decimation=int(samp_rate),
|
||||
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(
|
||||
quad_rate=384000,
|
||||
audio_decimation=8,
|
||||
quad_rate=audio_rate * audio_interp,
|
||||
audio_decimation=audio_interp,
|
||||
)
|
||||
|
||||
##################################################
|
||||
# Connections
|
||||
##################################################
|
||||
self.connect((self.analog_wfm_rcv_0, 0), (self.blocks_udp_sink_0, 0))
|
||||
self.connect((self.blocks_throttle_0, 0), (self.low_pass_filter_0, 0))
|
||||
self.connect((self.low_pass_filter_0, 0), (self.analog_wfm_rcv_0, 0))
|
||||
self.connect((self.rtlsdr_source_0, 0), (self.blocks_throttle_0, 0))
|
||||
self.connect((self.analog_wfm_rcv_0, 0), (self.blocks_file_sink_0, 0))
|
||||
self.connect((self.rational_resampler_xxx_0, 0), (self.analog_wfm_rcv_0, 0))
|
||||
self.connect((self.rtlsdr_source_0, 0), (self.rational_resampler_xxx_0, 0))
|
||||
|
||||
def get_samp_rate(self):
|
||||
return self.samp_rate
|
||||
|
@ -71,20 +76,64 @@ class top_block(gr.top_block):
|
|||
def set_samp_rate(self, samp_rate):
|
||||
self.samp_rate = 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):
|
||||
return self.frequency
|
||||
def get_freq(self):
|
||||
return self.freq
|
||||
|
||||
def set_frequency(self, frequency):
|
||||
self.frequency = frequency
|
||||
self.rtlsdr_source_0.set_center_freq(self.frequency, 0)
|
||||
def set_freq(self, freq):
|
||||
self.freq = freq
|
||||
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):
|
||||
tb = top_block_cls()
|
||||
tb.start()
|
||||
|
||||
HOST = '127.0.0.1' # Loopback Address (Localhost)
|
||||
PORT = 65432 # Semi-Random Port that is non-priviledged (> 1023)
|
||||
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)
|
||||
if data == "":
|
||||
break
|
||||
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_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
|
||||
else:
|
||||
print("UNKNOWN COMMAND: ", command)
|
||||
else:
|
||||
print("UNKNOWN COMMAND", command)
|
||||
|
||||
state = parse_commands(conn, tb, commands)
|
||||
if state['quit']:
|
||||
quit = True
|
||||
conn.close()
|
||||
s.close()
|
||||
|
||||
|
|
Loading…
Reference in a new issue