From 7057bc0af7cdc204b66ccbe27567c95b54558330 Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:46:07 +0200 Subject: [PATCH] Update DOCS.md --- birdnet-pi/DOCS.md | 232 +++++++++++++-------------------------------- 1 file changed, 68 insertions(+), 164 deletions(-) diff --git a/birdnet-pi/DOCS.md b/birdnet-pi/DOCS.md index d6f4f6f79..6647c1638 100644 --- a/birdnet-pi/DOCS.md +++ b/birdnet-pi/DOCS.md @@ -84,7 +84,7 @@ sudo ethtool -s eth0 speed 100 duplex full autoneg on # Run GStreamer RTSP server if installed if command -v gst-launch-1.0 &>/dev/null; then - ./rtsp_audio_server.py --device plughw:0,0 --format S16LE --rate 96000 --channels 2 --mount-point /birdmic --port 8554 >/tmp/log_rtsp 2>/tmp/log_rtsp_error & + ./rtsp_audio_server.py & sleep 2 >/tmp/log_rtsp 2>/tmp/log_rtsp_error & gst_pid=$! else echo "GStreamer not found, skipping to ffmpeg fallback" @@ -155,11 +155,11 @@ Create a script named rtsp_audio_server.py ``` #!/usr/bin/env python3 -import sys import gi -import argparse -import socket +import sys import logging +import os +import signal gi.require_version('Gst', '1.0') gi.require_version('GstRtspServer', '1.0') @@ -169,189 +169,93 @@ from gi.repository import Gst, GstRtspServer, GLib # Initialize GStreamer Gst.init(None) -def get_lan_ip(): - """ - Retrieves the LAN IP address by creating a dummy connection. - """ - try: - with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: - # This doesn't send any data; it's just used to get the local IP address - s.connect(("8.8.8.8", 80)) - return s.getsockname()[0] - except Exception as e: - logging.error(f"Failed to get LAN IP address: {e}") - return "127.0.0.1" +# Configure Logging +LOG_FILE = "gst_rtsp_server.log" +logging.basicConfig( + filename=LOG_FILE, + filemode='a', + format='%(asctime)s %(levelname)s: %(message)s', + level=logging.DEBUG # Set to DEBUG for comprehensive logging +) +logger = logging.getLogger(__name__) -class PCMStream(GstRtspServer.RTSPMediaFactory): - def __init__(self, device, format, rate, channels): - super(PCMStream, self).__init__() - self.device = device - self.format = format - self.rate = rate - self.channels = channels - self.set_shared(True) +class AudioFactory(GstRtspServer.RTSPMediaFactory): + def __init__(self): + super(AudioFactory, self).__init__() + self.set_shared(True) # Allow multiple clients to access the stream + self.set_latency(500) # Increase latency to 500ms to improve stream stability + self.set_suspend_mode(GstRtspServer.RTSPSuspendMode.NONE) # Prevent suspension of the stream when no clients are connected + logger.debug("AudioFactory initialized: shared=True, latency=500ms, suspend_mode=NONE.") def do_create_element(self, url): """ - Overridden method to create the GStreamer pipeline. + Create and return the GStreamer pipeline for streaming audio. """ - # Attempt to retrieve and log the RTSP URL's URI - try: - # Some versions might have 'get_uri()', others might not - uri = url.get_uri() - logging.info(f"Creating pipeline for URL: {uri}") - except AttributeError: - # Fallback if 'get_uri()' doesn't exist - logging.info("Creating pipeline for RTSP stream.") - - # Define the GStreamer pipeline string for PCM streaming pipeline_str = ( - f"alsasrc device={self.device} ! " - f"audio/x-raw, format={self.format}, rate={self.rate}, channels={self.channels} ! " - "audioconvert ! audioresample ! " - "rtpL16pay name=pay0 pt=96" + "alsasrc device=plughw:0,0 do-timestamp=true buffer-time=2000000 latency-time=1000000 ! " # Increased buffer size + "queue max-size-buffers=0 max-size-bytes=0 max-size-time=0 ! " # Add queue to handle buffer management + "audioconvert ! " # Convert audio to a suitable format + "audioresample ! " # Resample audio if necessary + "audio/x-raw,format=S16BE,channels=2,rate=48000 ! " # Set audio properties (rate = 48kHz) + "rtpL16pay name=pay0 pt=96" # Payload for RTP ) - - logging.info(f"Pipeline: {pipeline_str}") - - # Parse and launch the pipeline - pipeline = Gst.parse_launch(pipeline_str) - - if not pipeline: - logging.error("Failed to create GStreamer pipeline.") + logger.debug(f"Creating GStreamer pipeline: {pipeline_str}") + try: + pipeline = Gst.parse_launch(pipeline_str) + if not pipeline: + logger.error("Failed to parse GStreamer pipeline.") + return None + return pipeline + except Exception as e: + logger.error(f"Exception while creating pipeline: {e}") return None - - # Get the bus from the pipeline and connect to the message handler - bus = pipeline.get_bus() - bus.add_signal_watch() - bus.connect("message", self.on_message) - - return pipeline - - def on_message(self, bus, message): - t = message.type - if t == Gst.MessageType.ERROR: - err, debug = message.parse_error() - logging.error(f"GStreamer Error: {err}, {debug}") - elif t == Gst.MessageType.WARNING: - err, debug = message.parse_warning() - logging.warning(f"GStreamer Warning: {err}, {debug}") - elif t == Gst.MessageType.EOS: - logging.info("End-Of-Stream reached.") - return True class GstServer: - def __init__(self, mount_point, device, format, rate, channels, port, ip=None): - self.mount_point = mount_point - self.device = device - self.format = format - self.rate = rate - self.channels = channels - self.port = port - self.ip = ip - + def __init__(self): self.server = GstRtspServer.RTSPServer() - self.server.set_service(str(self.port)) + self.server.set_service("8554") # Set the RTSP server port + self.server.set_address("0.0.0.0") # Listen on all network interfaces + logger.debug("RTSP server configured: address=0.0.0.0, port=8554.") - if self.ip: - self.server.set_address(self.ip) - else: - self.server.set_address("0.0.0.0") + factory = AudioFactory() + mount_points = self.server.get_mount_points() + mount_points.add_factory("/birdmic", factory) # Mount point + logger.debug("Factory mounted at /birdmic.") - self.factory = PCMStream(self.device, self.format, self.rate, self.channels) - self.mount_points = self.server.get_mount_points() - self.mount_points.add_factory(self.mount_point, self.factory) - - try: - self.server.attach(None) - except Exception as e: - logging.error(f"Failed to attach RTSP server: {e}") - sys.exit(1) - - server_ip = self.ip if self.ip else get_lan_ip() - - # Verify that the server is listening on the desired port - if not self.verify_server_binding(): - logging.error(f"RTSP server failed to bind to port {self.port}. It might already be in use.") - sys.exit(1) - - print(f"RTSP server is live at rtsp://{server_ip}:{self.port}{self.mount_point}") - - def verify_server_binding(self): - """ - Verifies if the RTSP server is successfully listening on the specified port. - """ - try: - with socket.create_connection(("127.0.0.1", self.port), timeout=2): - return True - except Exception as e: - logging.error(f"Verification failed: {e}") - return False - -def parse_args(): - parser = argparse.ArgumentParser(description="GStreamer RTSP Server for 16-bit PCM Audio") - parser.add_argument( - '--device', type=str, default='plughw:0,0', - help='ALSA device to capture audio from (default: plughw:0,0)' - ) - parser.add_argument( - '--format', type=str, default='S16LE', - help='Audio format (default: S16LE)' - ) - parser.add_argument( - '--rate', type=int, default=44100, - help='Sampling rate in Hz (default: 44100)' - ) - parser.add_argument( - '--channels', type=int, default=1, - help='Number of audio channels (default: 1)' - ) - parser.add_argument( - '--mount-point', type=str, default='/birdmic', - help='RTSP mount point (default: /birdmic)' - ) - parser.add_argument( - '--port', type=int, default=8554, - help='RTSP server port (default: 8554)' - ) - parser.add_argument( - '--ip', type=str, default=None, - help='Explicit LAN IP address to bind the RTSP server to (default: auto-detected)' - ) - return parser.parse_args() + self.server.attach(None) # Attach the server to the default main context + logger.info("RTSP server attached and running.") def main(): - # Configure logging to display errors and warnings - logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') - - args = parse_args() - - try: - server = GstServer( - mount_point=args.mount_point, - device=args.device, - format=args.format, - rate=args.rate, - channels=args.channels, - port=args.port, - ip=args.ip - ) - except Exception as e: - logging.error(f"Failed to initialize RTSP server: {e}") - sys.exit(1) + # Create GstServer instance + server = GstServer() + print("RTSP server is running at rtsp://localhost:8554/birdmic") + logger.info("RTSP server is running at rtsp://localhost:8554/birdmic") + # Set up the main loop with proper logging loop = GLib.MainLoop() + # Handle termination signals to ensure graceful shutdown + def shutdown(signum, frame): + logger.info(f"Shutting down RTSP server due to signal {signum}.") + print("\nShutting down RTSP server.") + loop.quit() + + # Register signal handlers for graceful termination + signal.signal(signal.SIGINT, shutdown) + signal.signal(signal.SIGTERM, shutdown) + try: loop.run() - except KeyboardInterrupt: - print("Shutting down RTSP server...") - loop.quit() except Exception as e: - logging.error(f"An unexpected error occurred: {e}") - loop.quit() + logger.error(f"Main loop encountered an exception: {e}") + finally: + logger.info("RTSP server has been shut down.") if __name__ == "__main__": + # Ensure log file exists + if not os.path.exists(LOG_FILE): + open(LOG_FILE, 'w').close() + main() ```