diff --git a/dependencies/build_dependencies b/dependencies/build_dependencies index 6984893..02f937a 100755 --- a/dependencies/build_dependencies +++ b/dependencies/build_dependencies @@ -14,12 +14,14 @@ sudo apt-get install -y \ # RedHat: #sudo dnf install pulseaudio-libs-devel gtk3-devel freeglut-devel - # These are necessary for building the python bindings for SoapySDR. # These must be installed before building SoapySDR for the python # bindings to be available on the system. sudo apt-get install -y python-dev swig +# These are required for building Soapy RTL-SDR +sudo apt-get install -y rtl-sdr librtlsdr-dev + # Build SoapySDR if ! [ -d SoapySDR ]; then @@ -43,10 +45,21 @@ pushd SoapySDRPlay mkdir build pushd build cmake .. -DCMAKE_BUILD_TYPE=Release - make + make -j + sudo make install + popd +popd +fi + +# Build SoapyRTLSDR plugin +if ! [ -d SoapyRTLSDR ]; then +git clone https://github.com/pothosware/SoapyRTLSDR.git +pushd SoapyRTLSDR + mkdir build + pushd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j sudo make install - sudo ldconfig - SoapySDRUtil --info popd popd fi @@ -69,6 +82,13 @@ pushd liquid-dsp popd fi +# Get mediamtx (no system package available) +mkdir -p mediamtx +pushd mediamtx + wget https://github.com/aler9/mediamtx/releases/download/v0.22.2/mediamtx_v0.22.2_linux_amd64.tar.gz + tar -xzvf mediamtx_v0.22.2_linux_amd64.tar.gz +popd + echo "Setup completed." >> build.log date >> build.log exit 0 diff --git a/microservice.py b/microservice.py index baa2499..3850743 100755 --- a/microservice.py +++ b/microservice.py @@ -3,14 +3,16 @@ import SoapySDR as soapy from radio import Radio -from flask import Flask, request, jsonify +from flask import Flask, jsonify from flasgger import Swagger + app = Flask(__name__) swag = Swagger(app) radios = {} + @app.route('/radio/report') def report(): """List radio devices available to the system. @@ -48,7 +50,7 @@ def connect(radio): for device in devices: if radio in device.values(): try: - radios[radio] = Radio(device) + radios[radio] = Radio(radio, device) return "", 200 except Exception as e: radios.pop(radio) @@ -81,7 +83,6 @@ def disconnect(radio): else: return "Radio not connected", 400 - @app.route('/radio//configure/') def configure(radio, frequency): """Tune the radio to a frequency. @@ -95,7 +96,7 @@ def configure(radio, frequency): type: string required: true - name: frequency - description: Frequency (in Hz) to tune the radio to. + description: Frequency (in Hz) to tune the radio to. Suffixing is also supported, ie 87.5M, 108.0M, etc. in: path type: string required: true @@ -106,16 +107,18 @@ def configure(radio, frequency): description: The specified radio is not connected. """ if radio in radios: - return jsonify(radios[radio].configure(frequency)) + if radios[radio].is_streaming(): + return 'no', 400 + else: + return jsonify(radios[radio].configure(frequency)) else: return "Radio not connected", 400 - @app.route('/radio//start') def start_stream(radio): - """Tune the radio to a frequency. - You must connect to the radio before attempting to configure it. - The radio must be configured before streaming can be started. + """Start the radio stream. + Once the stream has been started, connect to the stream at: + rtsp://[host]:8554/radio/[radio]/stream --- parameters: - name: radio @@ -132,9 +135,7 @@ def start_stream(radio): @app.route('/radio//end') def end_stream(radio): - """Tune the radio to a frequency. - You must connect to the radio before attempting to configure it. - The radio must be configured before streaming can be started. + """Terminate the radio stream. --- parameters: - name: radio @@ -151,8 +152,23 @@ def end_stream(radio): if __name__ == '__main__': + import subprocess + + rtsp_relay = subprocess.Popen( + [ + './dependencies/mediamtx/mediamtx', + './dependencies/mediamtx/mediamtx.yml' + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + app.run( host='0.0.0.0', threaded=True, debug=False - ) \ No newline at end of file + ) + + print('Killing RTSP relay...') + rtsp_relay.kill() + rtsp_relay.wait() # Necessary? diff --git a/radio.py b/radio.py index 9e71b92..cfc69d3 100644 --- a/radio.py +++ b/radio.py @@ -3,7 +3,6 @@ import SoapySDR as soapy import prefixed from formats import * import sys -import os import subprocess import struct @@ -11,9 +10,10 @@ import struct class Radio: FORMAT = 'CS16' SAMPLES = 8192 - PORT = 5002 + PORT = 8554 - def __init__(self, device_info): + def __init__(self, name, device_info): + self.name = name self.device_info = device_info self.run = False self.thread = None @@ -23,6 +23,9 @@ class Radio: raise RuntimeError("Failed to connect to radio device") def configure(self, frequency): + if self.is_streaming(): + raise RuntimeError("Cannot configure radio while a stream is active") + frequency = int(prefixed.Float(frequency)) sample_rate = 384000 bandwidth = 200000 @@ -41,6 +44,9 @@ class Radio: 'gain-mode': 'auto' if self.device.getGainMode(soapy.SOAPY_SDR_RX, 0) else 'manual', } + def is_streaming(self): + return (self.thread and self.thread.is_alive()) + def start_stream(self): if self.thread: raise RuntimeError('Stream thread is already running') @@ -77,7 +83,10 @@ class Radio: def _init_stream(self): self.playback = subprocess.Popen( - ['/usr/bin/ffmpeg', '-f', 's16le', '-ar', '32000', '-ac', '2', '-i', '-', '-f', 'rtp', f'rtp://0.0.0.0:{Radio.PORT}'], + [ + '/usr/bin/ffmpeg', '-f', 's16le', '-ar', '32000', '-ac', '2', '-i', '-', + '-f', 'rtsp', f"rtsp://localhost:{Radio.PORT}/radio/{self.name}/stream" + ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL @@ -100,8 +109,15 @@ class Radio: self.device.closeStream(self.stream) self.buffer = None + # .terminate()/.kill() followed by .wait() is required to properly clear + # killed zombie processes from the process table. + # https://stackoverflow.com/a/41961462 self.demod.kill() + self.demod.wait() + self.demod = None self.playback.kill() + self.playback.wait() + self.playback = None """ @@ -110,7 +126,7 @@ Quick and dirty test of the Radio class. if __name__ == '__main__': import time - sdr = Radio({'driver': 'sdrplay'}) + sdr = Radio('demo', {'driver': 'sdrplay'}) print('Configuring...') sdr.configure('105.5M')