Fixed RTP streaming - it is now provided by an RTSP relay MediaMTX
This commit is contained in:
parent
52c298d799
commit
d2a84a9f8f
28
dependencies/build_dependencies
vendored
28
dependencies/build_dependencies
vendored
@ -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
|
||||||
|
|||||||
@ -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?
|
||||||
|
|||||||
26
radio.py
26
radio.py
@ -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')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user