Fixed RTP streaming - it is now provided by an RTSP relay MediaMTX

This commit is contained in:
Jono Targett 2023-05-12 11:33:26 +09:30
parent 52c298d799
commit d2a84a9f8f
3 changed files with 74 additions and 22 deletions

View File

@ -14,12 +14,14 @@ sudo apt-get install -y \
# RedHat: # RedHat:
#sudo dnf install pulseaudio-libs-devel gtk3-devel freeglut-devel #sudo dnf install pulseaudio-libs-devel gtk3-devel freeglut-devel
# These are necessary for building the python bindings for SoapySDR. # These are necessary for building the python bindings for SoapySDR.
# These must be installed before building SoapySDR for the python # These must be installed before building SoapySDR for the python
# bindings to be available on the system. # bindings to be available on the system.
sudo apt-get install -y python-dev swig 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 # Build SoapySDR
if ! [ -d SoapySDR ]; then if ! [ -d SoapySDR ]; then
@ -43,10 +45,21 @@ pushd SoapySDRPlay
mkdir build mkdir build
pushd build pushd build
cmake .. -DCMAKE_BUILD_TYPE=Release 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 make install
sudo ldconfig
SoapySDRUtil --info
popd popd
popd popd
fi fi
@ -69,6 +82,13 @@ pushd liquid-dsp
popd popd
fi 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 echo "Setup completed." >> build.log
date >> build.log date >> build.log
exit 0 exit 0

View File

@ -3,14 +3,16 @@
import SoapySDR as soapy import SoapySDR as soapy
from radio import Radio from radio import Radio
from flask import Flask, request, jsonify from flask import Flask, jsonify
from flasgger import Swagger from flasgger import Swagger
app = Flask(__name__) app = Flask(__name__)
swag = Swagger(app) swag = Swagger(app)
radios = {} radios = {}
@app.route('/radio/report') @app.route('/radio/report')
def report(): def report():
"""List radio devices available to the system. """List radio devices available to the system.
@ -48,7 +50,7 @@ def connect(radio):
for device in devices: for device in devices:
if radio in device.values(): if radio in device.values():
try: try:
radios[radio] = Radio(device) radios[radio] = Radio(radio, device)
return "", 200 return "", 200
except Exception as e: except Exception as e:
radios.pop(radio) radios.pop(radio)
@ -81,7 +83,6 @@ def disconnect(radio):
else: else:
return "Radio not connected", 400 return "Radio not connected", 400
@app.route('/radio/<radio>/configure/<frequency>') @app.route('/radio/<radio>/configure/<frequency>')
def configure(radio, frequency): def configure(radio, frequency):
"""Tune the radio to a frequency. """Tune the radio to a frequency.
@ -95,7 +96,7 @@ def configure(radio, frequency):
type: string type: string
required: true required: true
- name: frequency - 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 in: path
type: string type: string
required: true required: true
@ -106,16 +107,18 @@ def configure(radio, frequency):
description: The specified radio is not connected. description: The specified radio is not connected.
""" """
if radio in radios: 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: else:
return "Radio not connected", 400 return "Radio not connected", 400
@app.route('/radio/<radio>/start') @app.route('/radio/<radio>/start')
def start_stream(radio): def start_stream(radio):
"""Tune the radio to a frequency. """Start the radio stream.
You must connect to the radio before attempting to configure it. Once the stream has been started, connect to the stream at:
The radio must be configured before streaming can be started. rtsp://[host]:8554/radio/[radio]/stream
--- ---
parameters: parameters:
- name: radio - name: radio
@ -132,9 +135,7 @@ def start_stream(radio):
@app.route('/radio/<radio>/end') @app.route('/radio/<radio>/end')
def end_stream(radio): def end_stream(radio):
"""Tune the radio to a frequency. """Terminate the radio stream.
You must connect to the radio before attempting to configure it.
The radio must be configured before streaming can be started.
--- ---
parameters: parameters:
- name: radio - name: radio
@ -151,8 +152,23 @@ def end_stream(radio):
if __name__ == '__main__': if __name__ == '__main__':
import subprocess
rtsp_relay = subprocess.Popen(
[
'./dependencies/mediamtx/mediamtx',
'./dependencies/mediamtx/mediamtx.yml'
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
app.run( app.run(
host='0.0.0.0', host='0.0.0.0',
threaded=True, threaded=True,
debug=False debug=False
) )
print('Killing RTSP relay...')
rtsp_relay.kill()
rtsp_relay.wait() # Necessary?

View File

@ -3,7 +3,6 @@ import SoapySDR as soapy
import prefixed import prefixed
from formats import * from formats import *
import sys import sys
import os
import subprocess import subprocess
import struct import struct
@ -11,9 +10,10 @@ import struct
class Radio: class Radio:
FORMAT = 'CS16' FORMAT = 'CS16'
SAMPLES = 8192 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.device_info = device_info
self.run = False self.run = False
self.thread = None self.thread = None
@ -23,6 +23,9 @@ class Radio:
raise RuntimeError("Failed to connect to radio device") raise RuntimeError("Failed to connect to radio device")
def configure(self, frequency): def configure(self, frequency):
if self.is_streaming():
raise RuntimeError("Cannot configure radio while a stream is active")
frequency = int(prefixed.Float(frequency)) frequency = int(prefixed.Float(frequency))
sample_rate = 384000 sample_rate = 384000
bandwidth = 200000 bandwidth = 200000
@ -41,6 +44,9 @@ class Radio:
'gain-mode': 'auto' if self.device.getGainMode(soapy.SOAPY_SDR_RX, 0) else 'manual', '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): def start_stream(self):
if self.thread: if self.thread:
raise RuntimeError('Stream thread is already running') raise RuntimeError('Stream thread is already running')
@ -77,7 +83,10 @@ class Radio:
def _init_stream(self): def _init_stream(self):
self.playback = subprocess.Popen( 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, stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL stderr=subprocess.DEVNULL
@ -100,8 +109,15 @@ class Radio:
self.device.closeStream(self.stream) self.device.closeStream(self.stream)
self.buffer = None 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.kill()
self.demod.wait()
self.demod = None
self.playback.kill() self.playback.kill()
self.playback.wait()
self.playback = None
""" """
@ -110,7 +126,7 @@ Quick and dirty test of the Radio class.
if __name__ == '__main__': if __name__ == '__main__':
import time import time
sdr = Radio({'driver': 'sdrplay'}) sdr = Radio('demo', {'driver': 'sdrplay'})
print('Configuring...') print('Configuring...')
sdr.configure('105.5M') sdr.configure('105.5M')