Riconoscere il silenzio da un input audio su Linux
Come Digital Streaming Specialist per una web radio (Radio Città Aperta) ho sempre nuove sfide e problemi da risolvere.
In una grande società c’è sempre la scelta tra “fare o comprare?” ma quando il tuo mondo è piccolo, senza finanziamenti, e con tanto entusiasmo ma poco tempo, devi trovare soluzioni adatte a tutte le tue esigenze. Non solo una soluzione deve essere economica: deve anche essere facile da capire, facile da mantenere, dovrebbe funzionare senza intervento manuale e cose del genere.
Il problema attuale è “come capire che la web radio è in streaming, quando lo streaming è muto?”
come capire che la web radio è in streaming, quando lo streaming è muto?
Perchè succede? Lo streaming della web radio è una catena di più parti, sia analogiche che digitali, e può capitare che alcune di esse siano mute, mentre altre funzionino come previsto. Ad esempio, supponiamo di avere un computer che funziona come nostro streamer; sta inviando l’input audio acquisito da una scheda audio direttamente a un server streamer. Anche se internet funziona, c’è la connessione, la password è nota e l’host è ok, non c’è modo di capire se l’audio catturato sia muto o meno. Ciò si traduce in uno streaming audio attivo e funzionante, ma che non produce audio (o forse si può sentire solo del rumore bianco).
Se il cavo di ingresso è staccato, se la sorgente audio è spenta o se il fader del mixer è completamente giù invece di essere al massimo volume, tutte queste situazioni possono portare a un audio silenzioso che passa attraverso la catena di streaming. La soluzione ottimale per questo è qualcosa che monitora in tempo reale l’audio in ingresso, controlla se ci sono almeno X secondi di silenzio e, se ciò accade, avvia alcune procedure che possono essere:
- avviare una playlist in modo che l’audio non sia più silenzioso
- inviare una e-mail e/o un avviso a qualcuno
Inoltre, quando l’ingresso audio non è più silenzioso (ad esempio, uno speaker della radio super intelligente ha spostato il fader del mixer nella posizione corretta…), questo software deve interrompere la riproduzione della playlist. Deve essere eseguito per sempre, deve essere riavviato se viene arrestato e deve essere eseguito all’avvio. Quindi iniziamo con qualcosa che ho trovato: Python hasounddevice
che fa quello che ci serve.
Sounddevice con Python
Non un campione di Python, ma sembra molto facile da capire, facile da usare, facile da modificare e super potente. Ho iniziato da questo thread con questo frammento di codice
# Print out realtime audio volume as ascii bars
import sounddevice as sd
import numpy as np
def print_sound(indata, outdata, frames, time, status):
volume_norm = np.linalg.norm(indata)*10
print ("|" * int(volume_norm))
with sd.Stream(callback=print_sound):
sd.sleep(10000)
che mostra alcune barre del livello di ingresso audio. Con qualche modifica, una catena di “if” ho modificato lo script in modo che scriva su un file quando vengono trovati X campioni silenziosi. Silent è definito come “un livello sotto la soglia, th.
#!/usr/bin/env python3
import numpy as np import sounddevice as sd import datetime duration = 10 #in seconds th = 10 sec = 0 maxNumberOfSilent = 4000 isSilent = True logfile = open("soundlevel.log", "a") def audio_callback(indata, frames, time, status): global sec global isSilent global logfile dateLog = datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S] ") volume_norm = np.linalg.norm(indata) * 10 #print("|" * int(volume_norm)) #print(volume_norm) if volume_norm < th: sec += 1 else: sec = 0 if (sec > maxNumberOfSilent and not isSilent): isSilent = True logfile.write(dateLog+"Silent for "+str(maxNumberOfSilent)+" samples\n") logfile.flush() #print("Silent for "+str(maxNumberOfSilent)+" samples") elif (sec == 0 and isSilent): isSilent = False logfile.write(dateLog+"Music\n") logfile.flush() #print("Music") stream = sd.InputStream(callback=audio_callback) with stream: while (True): sd.sleep(duration * 1000)
Dopo qualche altra ricerca, ho trovato una classe per inviare e-mail utilizzando un account Gmail:
import smtplib, ssl
class Mail:
def __init__(self):
self.port = 465
self.smtp_server_domain_name = "smtp.gmail.com"
self.sender_mail = "........"
self.password = "........"
def send(self, emails, subject, content):
ssl_context = ssl.create_default_context()
service = smtplib.SMTP_SSL(self.smtp_server_domain_name, self.port, context=ssl_context)
service.login(self.sender_mail, self.password)
for email in emails:
result = service.sendmail(self.sender_mail, email, f"Subject: {subject}\n{content}")
service.quit()
if __name__ == '__main__':
mails = input("Enter emails: ").split()
subject = input("Enter subject: ")
content = input("Enter content: ")
mail = Mail()
mail.send(mails, subject, content)
Per mettere tutto insieme, ho creato un sistema che invia una e-mail quando il suono è silenzioso:
#!/usr/bin/env python3
import numpy as np
import sounddevice as sd
import datetime
import smtplib, ssl
th = 10
sec = 0
maxNumberOfSilent = 10000
isSilent = True
logfile = open("soundlevel.log", "a")
to_addresses = ("myemail@mail.com",)
class Mail:
def __init__(self):
self.port = 465
self.smtp_server_domain_name = "smtp.gmail.com"
self.sender_mail = "....."
self.password = "...."
def send(self, emails, subject, content):
ssl_context = ssl.create_default_context()
service = smtplib.SMTP_SSL(self.smtp_server_domain_name, self.port, context=ssl_context)
service.login(self.sender_mail, self.password)
for email in emails:
result = service.sendmail(self.sender_mail, email, f"Subject: {subject}\n{content}")
service.quit()
mail_client = Mail()
def audio_callback(indata, frames, time, status):
global sec
global isSilent
global logfile
dateLog = datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S] ")
volume_norm = np.linalg.norm(indata) * 10
#print("|" * int(volume_norm))
#print(volume_norm)
if volume_norm < th: sec += 1 else: sec = 0 if (sec > maxNumberOfSilent and not isSilent):
isSilent = True
logfile.write(dateLog+"Silent for "+str(maxNumberOfSilent)+" samples\n")
logfile.flush()
mail_client.send(to_addresses,"Audio is silent",dateLog+" audio is silent")
#print("Silent for "+str(maxNumberOfSilent)+" samples")
elif (sec == 0 and isSilent):
isSilent = False
logfile.write(dateLog+"Music\n")
logfile.flush()
mail_client.send(to_addresses,"Audio back to normal",dateLog+" audio back to normal")
#print("Music")
stream = sd.InputStream(callback=audio_callback)
with stream:
while (True):
sd.sleep(10 * 1000)