Improve TX/RX AGC
This commit is contained in:
@@ -62,6 +62,10 @@ class SessioneSIP:
|
|||||||
self.stream_id = os.urandom(4)
|
self.stream_id = os.urandom(4)
|
||||||
self.ultimo_src_id = None
|
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_buffer = ""
|
||||||
self.dtmf_task = None
|
self.dtmf_task = None
|
||||||
self.ultimo_rtp_timestamp_dtmf = None
|
self.ultimo_rtp_timestamp_dtmf = None
|
||||||
@@ -159,7 +163,7 @@ class OpenBridgeClient(asyncio.DatagramProtocol):
|
|||||||
|
|
||||||
class SessionManager:
|
class SessionManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.vocoder = VocoderEngine()
|
self.vocoder = VocoderEngine(port_start=2470, port_end=2474)
|
||||||
self.openbridge = None
|
self.openbridge = None
|
||||||
self.rtp_transport = None
|
self.rtp_transport = None
|
||||||
self.sip_transport = None
|
self.sip_transport = None
|
||||||
@@ -278,20 +282,40 @@ class SessionManager:
|
|||||||
if pcm_data and len(pcm_data) >= 2:
|
if pcm_data and len(pcm_data) >= 2:
|
||||||
if len(pcm_data) % 2 != 0: pcm_data = pcm_data[:-1]
|
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)
|
livello_rms = audioop.rms(pcm_data, 2)
|
||||||
if livello_rms > 18000:
|
picco_max = audioop.max(pcm_data, 2)
|
||||||
pcm_data = audioop.mul(pcm_data, 2, 0.4)
|
|
||||||
elif livello_rms > 12000:
|
if livello_rms > 50:
|
||||||
pcm_data = audioop.mul(pcm_data, 2, 0.7)
|
target_rms = 4000.0
|
||||||
elif livello_rms < 300:
|
target_mult = target_rms / livello_rms
|
||||||
pcm_data = audioop.mul(pcm_data, 2, 0.0)
|
|
||||||
# --- FINE AGC ---
|
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)
|
pcma_data = audioop.lin2alaw(pcm_data, 2)
|
||||||
frames_pcm.append(pcma_data)
|
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:
|
for sess in ascoltatori:
|
||||||
header = bytearray(12)
|
header = bytearray(12)
|
||||||
header[0] = 0x80; header[1] = 0x08
|
header[0] = 0x80; header[1] = 0x08
|
||||||
@@ -304,33 +328,29 @@ class SessionManager:
|
|||||||
self.rtp_transport.sendto(rtp_packet, sess.rtp_client_addr)
|
self.rtp_transport.sendto(rtp_packet, sess.rtp_client_addr)
|
||||||
|
|
||||||
sess.rtp_seq = (sess.rtp_seq + 1) % 65536
|
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
|
sess.rtp_timestamp = (sess.rtp_timestamp + 160) % 4294967296
|
||||||
|
|
||||||
if idx < len(frames_pcm) - 1:
|
|
||||||
await asyncio.sleep(0.019)
|
|
||||||
|
|
||||||
except Exception: pass
|
except Exception: pass
|
||||||
|
|
||||||
async def processa_audio_rtp(self, sess, rtp_payload):
|
async def processa_audio_rtp(self, sess, rtp_payload):
|
||||||
if not sess.ptt_attivo: return
|
if not sess.ptt_attivo: return
|
||||||
try:
|
try:
|
||||||
# --- INIZIO AGC SIP -> DMR ---
|
# --- AGC DINAMICO MORBIDO SIP -> DMR ---
|
||||||
# Decodifichiamo G.711a in PCM lineare temporaneamente per calcolare il volume
|
|
||||||
pcm_lineare = audioop.alaw2lin(rtp_payload, 2)
|
pcm_lineare = audioop.alaw2lin(rtp_payload, 2)
|
||||||
livello_rms = audioop.rms(pcm_lineare, 2)
|
livello_rms = audioop.rms(pcm_lineare, 2)
|
||||||
|
|
||||||
if livello_rms > 18000:
|
if livello_rms > 50:
|
||||||
pcm_lineare = audioop.mul(pcm_lineare, 2, 0.4)
|
target_rms = 4000.0
|
||||||
elif livello_rms > 12000:
|
target_mult = target_rms / livello_rms
|
||||||
pcm_lineare = audioop.mul(pcm_lineare, 2, 0.7)
|
|
||||||
elif livello_rms > 500 and livello_rms < 3000:
|
if target_mult > 2.5: target_mult = 2.5
|
||||||
# Piccolo booster se il microfono SIP è basso
|
elif target_mult < 0.5: target_mult = 0.5
|
||||||
pcm_lineare = audioop.mul(pcm_lineare, 2, 1.5)
|
|
||||||
elif livello_rms < 150:
|
# Inerzia per evitare scatti
|
||||||
# Noise gate
|
sess.agc_tx = (sess.agc_tx * 0.90) + (target_mult * 0.10)
|
||||||
pcm_lineare = audioop.mul(pcm_lineare, 2, 0.0)
|
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)
|
rtp_payload = audioop.lin2alaw(pcm_lineare, 2)
|
||||||
# --- FINE AGC ---
|
# --- FINE AGC ---
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -15,9 +15,10 @@ class UdpClientProtocol(asyncio.DatagramProtocol):
|
|||||||
self.future.set_result(data)
|
self.future.set_result(data)
|
||||||
|
|
||||||
class VocoderEngine:
|
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.host = host
|
||||||
self.ports = list(range(2470, 2480))
|
self.ports = list(range(port_start, port_end + 1))
|
||||||
self.pool = {} # porta -> (transport, protocol, lock)
|
self.pool = {} # porta -> (transport, protocol, lock)
|
||||||
self.stream_alloc = {} # stream_id -> porta
|
self.stream_alloc = {} # stream_id -> porta
|
||||||
self.last_seen = {} # stream_id -> timestamp ultimo pacchetto
|
self.last_seen = {} # stream_id -> timestamp ultimo pacchetto
|
||||||
|
|||||||
Reference in New Issue
Block a user