Riconoscere il silenzio da un input audio su Linux

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)
MiroAdmin