diff --git a/core.py b/core.py index 414008e..dcf6dc1 100644 --- a/core.py +++ b/core.py @@ -62,6 +62,10 @@ class SessioneSIP: self.stream_id = os.urandom(4) self.ultimo_src_id = None + # Variabili Inerzia per l'AGC + self.agc_rx = 1.0 + self.agc_tx = 1.0 + self.dtmf_buffer = "" self.dtmf_task = None self.ultimo_rtp_timestamp_dtmf = None @@ -159,7 +163,7 @@ class OpenBridgeClient(asyncio.DatagramProtocol): class SessionManager: def __init__(self): - self.vocoder = VocoderEngine() + self.vocoder = VocoderEngine(port_start=2470, port_end=2474) self.openbridge = None self.rtp_transport = None self.sip_transport = None @@ -278,20 +282,40 @@ class SessionManager: if pcm_data and len(pcm_data) >= 2: if len(pcm_data) % 2 != 0: pcm_data = pcm_data[:-1] - # --- INIZIO AGC DMR -> SIP --- + # --- AGC DEFINITIVO (Zero Sleep, Soft-Limiting) --- livello_rms = audioop.rms(pcm_data, 2) - if livello_rms > 18000: - pcm_data = audioop.mul(pcm_data, 2, 0.4) - elif livello_rms > 12000: - pcm_data = audioop.mul(pcm_data, 2, 0.7) - elif livello_rms < 300: - pcm_data = audioop.mul(pcm_data, 2, 0.0) - # --- FINE AGC --- + picco_max = audioop.max(pcm_data, 2) + + if livello_rms > 50: + target_rms = 4000.0 + target_mult = target_rms / livello_rms + + if target_mult > 2.0: target_mult = 2.0 + elif target_mult < 0.3: target_mult = 0.3 + + for sess in ascoltatori: + if target_mult < sess.agc_rx: + sess.agc_rx = (sess.agc_rx * 0.90) + (target_mult * 0.10) + else: + sess.agc_rx = (sess.agc_rx * 0.99) + (target_mult * 0.01) + + moltiplicatore = ascoltatori[0].agc_rx + + # Soft-Brickwall: previene scatti improvvisi del moltiplicatore + if picco_max > 0 and (picco_max * moltiplicatore) > 30000: + moltiplicatore_sicuro = 30000 / picco_max + # Lasciamo scivolare l'inerzia verso il basso dolcemente, non di scatto + for sess in ascoltatori: + sess.agc_rx = (sess.agc_rx * 0.70) + (moltiplicatore_sicuro * 0.30) + moltiplicatore = sess.agc_rx + + pcm_data = audioop.mul(pcm_data, 2, moltiplicatore) pcma_data = audioop.lin2alaw(pcm_data, 2) frames_pcm.append(pcma_data) - for idx, pcma_data in enumerate(frames_pcm): + # --- INVIO RTP A RAFFICA (Gestito dal Jitter Buffer del SIP) --- + for pcma_data in frames_pcm: for sess in ascoltatori: header = bytearray(12) header[0] = 0x80; header[1] = 0x08 @@ -304,33 +328,29 @@ class SessionManager: self.rtp_transport.sendto(rtp_packet, sess.rtp_client_addr) sess.rtp_seq = (sess.rtp_seq + 1) % 65536 + # Il timestamp matematico è tutto ciò che serve al telefono per temporizzare l'audio sess.rtp_timestamp = (sess.rtp_timestamp + 160) % 4294967296 - if idx < len(frames_pcm) - 1: - await asyncio.sleep(0.019) - except Exception: pass async def processa_audio_rtp(self, sess, rtp_payload): if not sess.ptt_attivo: return try: - # --- INIZIO AGC SIP -> DMR --- - # Decodifichiamo G.711a in PCM lineare temporaneamente per calcolare il volume + # --- AGC DINAMICO MORBIDO SIP -> DMR --- pcm_lineare = audioop.alaw2lin(rtp_payload, 2) livello_rms = audioop.rms(pcm_lineare, 2) - if livello_rms > 18000: - pcm_lineare = audioop.mul(pcm_lineare, 2, 0.4) - elif livello_rms > 12000: - pcm_lineare = audioop.mul(pcm_lineare, 2, 0.7) - elif livello_rms > 500 and livello_rms < 3000: - # Piccolo booster se il microfono SIP è basso - pcm_lineare = audioop.mul(pcm_lineare, 2, 1.5) - elif livello_rms < 150: - # Noise gate - pcm_lineare = audioop.mul(pcm_lineare, 2, 0.0) + if livello_rms > 50: + target_rms = 4000.0 + target_mult = target_rms / livello_rms + + if target_mult > 2.5: target_mult = 2.5 + elif target_mult < 0.5: target_mult = 0.5 + + # Inerzia per evitare scatti + sess.agc_tx = (sess.agc_tx * 0.90) + (target_mult * 0.10) + pcm_lineare = audioop.mul(pcm_lineare, 2, sess.agc_tx) - # Ricodifichiamo in formato PCMA/A-Law prima di passarlo al vocoder rtp_payload = audioop.lin2alaw(pcm_lineare, 2) # --- FINE AGC --- diff --git a/vocoder_engine.py b/vocoder_engine.py index 73fdf62..ffeceeb 100644 --- a/vocoder_engine.py +++ b/vocoder_engine.py @@ -15,9 +15,10 @@ class UdpClientProtocol(asyncio.DatagramProtocol): self.future.set_result(data) class VocoderEngine: - def __init__(self, mode="emulator", host="127.0.0.1"): + # Aggiungiamo port_start e port_end per decidere dinamicamente il range + def __init__(self, mode="emulator", host="127.0.0.1", port_start=2470, port_end=2479): self.host = host - self.ports = list(range(2470, 2480)) + self.ports = list(range(port_start, port_end + 1)) self.pool = {} # porta -> (transport, protocol, lock) self.stream_alloc = {} # stream_id -> porta self.last_seen = {} # stream_id -> timestamp ultimo pacchetto