mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-01-11 10:21:02 +01:00
Update DOCS.md
This commit is contained in:
@@ -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()
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user