From b84f02dd8913708b97a9404503f645e0fc965d0a Mon Sep 17 00:00:00 2001 From: Jono Targett Date: Wed, 14 Jun 2023 11:49:52 +0930 Subject: [PATCH] A bit of cleanup for the dummy audio before MR --- fileradio.py | 20 +++++++------ microservice.py | 19 +++++------- radio.py | 51 ++++++++++++-------------------- streamer.py | 23 +++++++++++++++ tuuube.py | 78 +++++++++++++++++++++++-------------------------- 5 files changed, 96 insertions(+), 95 deletions(-) diff --git a/fileradio.py b/fileradio.py index d225214..66ae818 100644 --- a/fileradio.py +++ b/fileradio.py @@ -4,25 +4,26 @@ import time import sys import os + class FileRadio(Streamer): - PORT = 8554 + REST_PATH = 'sample' def __init__(self, path): + super().__init__() self.path = path self.basename = os.path.basename(self.path) self.name, self.ext = os.path.splitext(self.basename) - super().__init__() - - def _stream_path(self): - return f":{FileRadio.PORT}/sample/{self.name}" - def _stream_thread(self): self.playback = subprocess.Popen( [ - '/usr/bin/ffmpeg', '-re', '-stream_loop', '-1', '-i', - f"{self.path}", '-c', 'copy', - '-f', 'rtsp', f"rtsp://localhost{self._stream_path()}" + '/usr/bin/ffmpeg', + '-re', # http://trac.ffmpeg.org/wiki/StreamingGuide#The-reflag + '-stream_loop', '-1', # Loop the stream indefinitely + '-i', self.path, + '-c', 'copy', + '-f', 'rtsp', + self.stream_address('localhost') ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, @@ -42,6 +43,7 @@ class FileRadio(Streamer): self.playback = None + if __name__ == '__main__': fr = FileRadio('./data/sampleaudio/taunt.mp3') fr.start_stream() diff --git a/microservice.py b/microservice.py index 6c0dac2..9c8e49e 100755 --- a/microservice.py +++ b/microservice.py @@ -194,19 +194,8 @@ def radio_info(radio): return str(e), 400 -# Youtube shit tubes = {} -@app.route('/tuuube/report') -def tuuube_report(): - """List the tuuube streams opened and their streaming status. - --- - responses: - 200: - description: JSON - """ - return jsonify({id:{'active':tube.is_streaming(),'playing':tube.is_playing()} for id,tube in tubes.items()}) - @app.route('/tuuube//start') def start_tuuube_stream(id): """Start streaming from a youtube source. @@ -215,7 +204,13 @@ def start_tuuube_stream(id): --- parameters: - name: id - description: The youtube video ID. That is the part following the `watch?v=` in the URL. For example, dQw4w9WgXcQ. + description: + The youtube video ID. That is the part following the `watch?v=` in the URL. For example, `dQw4w9WgXcQ`.\n + Other good options are - \n + `BaW_jenozKc`, yt_dlp package test video.\n + `b2je8uBlgFM`, stereo audio test.\n + `LDU_Txk06tM`, crab rave, a commonly used audio fidelity test.\n + `sPT_epMLkwQ`, Kilsyth CFA major factory fire dispatch radio traffic. in: path type: string required: true diff --git a/radio.py b/radio.py index c36f949..24d0578 100644 --- a/radio.py +++ b/radio.py @@ -7,19 +7,19 @@ import subprocess import struct from soapyhelpers import * from samplerates import * +from streamer import Streamer, is_alive -class Radio: +class Radio(Streamer): + REST_PATH = 'radio' FORMAT = 'CS16' SAMPLES = 8192 - PORT = 8554 def __init__(self, name, device_info): + super().__init__() + self.name = name self.device_info = device_info - self.run = False - self.thread = None - self.device = soapy.Device(device_info) if self.device is None: raise RuntimeError("Failed to connect to radio device") @@ -83,31 +83,9 @@ class Radio: 'uarts': self.device.listUARTs(), } - def is_streaming(self): - return True if (self.thread and self.thread.is_alive()) else False - - def start_stream(self): - if self.is_streaming(): - raise RuntimeError('Stream thread is already running') - - self.run = True - self.thread = Thread(target=self._stream_thread, daemon=True, args=()) - self.thread.start() - - def end_stream(self): - if self.thread is None: - raise RuntimeError('No stream thread to terminate') - - self.run = False - self.thread.join() - self.thread = None - def _stream_thread(self): self._init_stream() - def is_alive(subprocess): - return (subprocess.poll() is None) - while self.run: # Check that the child processes are still running if (not is_alive(self.demod)) or (not is_alive(self.playback)): @@ -136,8 +114,12 @@ class Radio: def _init_stream(self): self.playback = subprocess.Popen( [ - '/usr/bin/ffmpeg', '-f', 's16le', '-ar', str(self.output_rate), '-ac', '2', '-i', '-', - '-f', 'rtsp', f"rtsp://localhost{self._stream_path()}" + '/usr/bin/ffmpeg', + '-f', 's16le', + '-ar', str(self.output_rate), + '-ac', '2', + '-i', '-', + '-f', 'rtsp', self.stream_address() ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, @@ -145,7 +127,12 @@ class Radio: ) self.demod = subprocess.Popen( - ['/usr/bin/python3', 'fm_demod.py', '-f', 'CS16', '-s', str(self.sample_rate), '-d', str(self.output_rate)], + [ + '/usr/bin/python3', 'fm_demod.py', + '-f', 'CS16', + '-s', str(self.sample_rate), + '-d', str(self.output_rate) + ], stdin=subprocess.PIPE, stdout=self.playback.stdin, stderr=subprocess.DEVNULL @@ -176,8 +163,6 @@ class Radio: self.playback.wait() self.playback = None - def _stream_path(self): - return f":{Radio.PORT}/radio/{self.name}" """ @@ -201,4 +186,4 @@ if __name__ == '__main__': print('Ending stream...') sdr.end_stream() - print('Stream ended.') \ No newline at end of file + print('Stream ended.') diff --git a/streamer.py b/streamer.py index b815ba4..865542a 100644 --- a/streamer.py +++ b/streamer.py @@ -1,12 +1,29 @@ from threading import Thread +import yaml + + +with open('mediamtx.yml', 'r') as config_file: + MEDIASERVER_CONFIG = yaml.safe_load(config_file) + def is_alive(subprocess): return True if (subprocess and subprocess.poll() is None) else False + class Streamer: + PROTOCOL = 'rtsp' + REST_PATH = 'stream' + def __init__(self): self.run = False self.thread = None + self.name = None + + def stream_path(self): + return f"{MEDIASERVER_CONFIG['rtspAddress']}/{type(self).REST_PATH}/{self.name}" + + def stream_address(self, host): + return f"{Streamer.PROTOCOL}://{host}{self.stream_path()}" def is_streaming(self): return True if (self.thread and self.thread.is_alive()) else False @@ -26,3 +43,9 @@ class Streamer: self.run = False self.thread.join() self.thread = None + + + +if __name__ == '__main__': + from pprint import pprint + pprint(MEDIASERVER_CONFIG) \ No newline at end of file diff --git a/tuuube.py b/tuuube.py index b25bb7d..1fd94d0 100755 --- a/tuuube.py +++ b/tuuube.py @@ -1,63 +1,61 @@ #! /usr/bin/env python3 """ -# Install issues? -https://stackoverflow.com/a/27481870 - -https://stackoverflow.com/a/66024755 -sudo apt purge youtube-dl -sudo pip3 install youtube-dl - -https://stackoverflow.com/a/75504772 -python3 -m pip install --force-reinstall https://github.com/yt-dlp/yt-dlp/archive/master.tar.gz +We aren't using either the apt or the pip repositories for the youtube_dl +as there is a known bug affecting those two versions. The youtube API has changed +since their release, causing downloads to fail. +Make sure you use the ./setup.sh script to obtain the latest github release of +yt_dlp, as this version carries the latest fixes. """ - #import youtube_dl import yt_dlp as youtube_dl from streamer import Streamer, is_alive import subprocess import time import sys +import os class Tuuube(Streamer): - PORT = 8554 + REST_PATH = 'tuuube' - def __init__(self, id): - self.id = id - self.playback = None + def __init__(self, name): super().__init__() + self.name = name + self.playback = None - def is_playing(self): - return is_alive(self.playback) - - def _stream_path(self): - return f":{Tuuube.PORT}/tuuube/{self.id}" + def source_path(self): + return f"/tmp/{self.name}.mp3" def _stream_thread(self): - ydl_opts = { - 'format': 'bestaudio/best', - 'outtmpl': f'/tmp/{self.id}.%(ext)s', - 'postprocessors': [{ - 'key': 'FFmpegExtractAudio', - 'preferredcodec': 'mp3', - 'preferredquality': '192', - }], - } + if not os.path.exists(self.source_path()) or not os.path.isfile(self.source_path()): + ydl_opts = { + 'format': 'bestaudio/best', + 'outtmpl': f'/tmp/{self.name}.%(ext)s', # yt_dlp will append %(ext) if not specified, + 'postprocessors': [{ # resulting in `/tmp/file.mp3.mp3` :/ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '192', + }], + } - try: - with youtube_dl.YoutubeDL(ydl_opts) as ydl: - ydl.download([f'https://www.youtube.com/watch?v={self.id}']) - except Exception as e: - print(f'File sourcing failed, aborting stream. {e}', file=sys.stderr) - self.run = False - return + try: + with youtube_dl.YoutubeDL(ydl_opts) as ydl: + ydl.download([f'https://www.youtube.com/watch?v={self.name}']) + except Exception as e: + print(f'File sourcing failed, aborting stream. {e}', file=sys.stderr) + self.run = False + return self.playback = subprocess.Popen( [ - '/usr/bin/ffmpeg', '-re', '-stream_loop', '-1', '-i', - f"/tmp/{self.id}.mp3", '-c', 'copy', - '-f', 'rtsp', f"rtsp://localhost{self._stream_path()}" + '/usr/bin/ffmpeg', + '-re', + '-stream_loop', '-1', + '-i', self.source_path(), + '-c', 'copy', + '-f', 'rtsp', + self.stream_address('localhost') ], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, @@ -65,7 +63,7 @@ class Tuuube(Streamer): ) while self.run: - if not self.is_playing(): + if not is_alive(self.playback): print('Playback failed, aborting stream.', file=sys.stderr) break time.sleep(0.1) @@ -79,9 +77,7 @@ class Tuuube(Streamer): if __name__ == '__main__': - #tube = Tuuube('dQw4w9WgXcQ') tube = Tuuube('BaW_jenozKc') - tube.start_stream() while True: