Configuração Global (TX)
Liste GUIDs com gst-device-monitor-1.0 Audio/Source. Compatível com WASAPI, DirectSound e PulseAudio; ignorado em autoaudiosrc.
Afiliadas (RX) — 1
Vazio = dispositivo padrão. Liste GUIDs com gst-device-monitor-1.0 Audio/Sink.
Scripts Gerados (GStreamer)
import os
import sys
import time
import traceback
def aguardar_saida():
print("")
if os.name == "nt":
os.system("pause")
return
try:
input("Pressione ENTER para sair...")
except EOFError:
print("Sem terminal interativo; mantendo a janela por 60 segundos para leitura do erro.")
time.sleep(60)
def erro_global(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
print("[ERRO NAO TRATADO]", exc_value)
traceback.print_exception(exc_type, exc_value, exc_traceback)
aguardar_saida()
sys.excepthook = erro_global
try:
import gi
gi.require_version('Gst', '1.0')
from gi.repository import Gst, GLib
except Exception as e:
print("[ERRO] Nao foi possivel carregar o GStreamer/PyGObject:", e)
traceback.print_exc()
aguardar_saida()
raise SystemExit(1)
Gst.init(None)
def validar_elemento(nome):
if Gst.ElementFactory.find(nome) is None:
raise RuntimeError(f"Elemento GStreamer nao encontrado: {nome}. Verifique plugins e PATH do GStreamer.")
def normalizar_afiliadas(lista):
validas = []
for index, rx in enumerate(lista, start=1):
nome = (rx.get("nome") or f"Afiliada #{index}").strip()
ip = (rx.get("ip") or "").strip()
try:
porta = int(rx.get("porta") or 5004)
except (TypeError, ValueError):
porta = 5004
if not ip:
print(f"[AVISO] {nome} ignorada: IP Tailscale vazio.")
continue
if porta < 1 or porta > 65535:
print(f"[AVISO] {nome} ignorada: porta UDP invalida ({porta}).")
continue
validas.append({"nome": nome, "ip": ip, "porta": porta})
if not validas:
raise RuntimeError("Nenhuma afiliada RX valida. Preencha pelo menos um IP Tailscale antes de executar o TX.")
return validas
# --- CONFIGURAÇÕES GERADAS PELO STL HUB ---
afiliadas = [
{"nome": "", "ip": "", "porta": 5004},
]
SRC = "wasapisrc"
for elemento in [SRC, "audioconvert", "audioresample", "opusenc", "rtpopuspay", "tee", "queue", "udpsink"]:
validar_elemento(elemento)
afiliadas_validas = normalizar_afiliadas(afiliadas)
# Pipeline Base (Captura e Encode)
pipeline_str = "wasapisrc ! audioconvert ! audioresample ! opusenc bitrate=128000 ! rtpopuspay pt=96 ! tee name=t "
# Pipeline Multi-Destino (Fan-out)
for index, rx in enumerate(afiliadas_validas):
pipeline_str += f"t. ! queue max-size-buffers=0 max-size-time=0 max-size-bytes=0 ! udpsink host={rx['ip']} port={rx['porta']} sync=false async=false "
print("Pipeline:", pipeline_str)
print("Iniciando transmissao multiponto para", len(afiliadas_validas), "emissoras...")
loop = GLib.MainLoop()
def on_bus(bus, msg):
t = msg.type
if t == Gst.MessageType.ERROR:
err, dbg = msg.parse_error()
print("[GST ERROR]", err.message)
if dbg: print(" debug:", dbg)
loop.quit()
elif t == Gst.MessageType.EOS:
print("[GST] EOS recebido")
loop.quit()
elif t == Gst.MessageType.WARNING:
w, dbg = msg.parse_warning()
print("[GST WARN]", w.message)
return True
pipeline = None
try:
pipeline = Gst.parse_launch(pipeline_str)
bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message", on_bus)
ret = pipeline.set_state(Gst.State.PLAYING)
if ret == Gst.StateChangeReturn.FAILURE:
raise RuntimeError("Falha ao iniciar pipeline. Verifique device ID, plugin de audio, porta e permissao do dispositivo.")
print("Pipeline iniciado. Pressione Ctrl+C para parar.")
loop.run()
except KeyboardInterrupt:
print("Interrompido pelo usuario.")
except GLib.Error as e:
print("[GLIB ERROR]", e.message if hasattr(e, "message") else e)
traceback.print_exc()
except Exception as e:
print("[EXCECAO]", e)
traceback.print_exc()
finally:
if pipeline is not None:
pipeline.set_state(Gst.State.NULL)
aguardar_saida()