Real time detect silence from audio input in Linux
As a
The current issue at hand is “how to understand that the web radio is streaming, when the stream is silent?”
understand that the web radio is streaming, but the stream is silent
How can this happen? Web radio streaming is a chain of multiple parts, both analog and digital, and it can happen that some of them are silent, while others are working as expected. For example, suppose we have a computer working as our streamer; it’s sending the audio input grabbed from a sound card directly to a streamer server. Even if the internet works, there’s connection, password is known and host is ok, there is no way to understand if the audio grabbed is silent or not. This results in audio streaming that is up-and-running, but not producing audio (or perhaps some white noise can be heard).
If the input cable is detached, if the audio source is turned off, or if the mixer fader is completely faded out instead of being at top volume, all these situations may lead to silent audio that goes through the streaming chain. The optimum solution for this is something that real-time monitors the input audio, checks if there are at least X seconds of silence and, if this happens, starts some procedures that can be:
- start some playlist so that the audio is no more silent
- send an e-mail and/or an alert to someone
Also, when the audio input is no more silent (a super smart radio speaker has moved the mixer fader to the correct position, for example…), this software needs to stop playing the playlist. It must run forever, it must restart if it’s stopped, and it must run at boot time. So let’s start with something that I’ve found: Python hassounddevice
that do what we need to do.
Sounddevice with Python
I’m not so used to Python, but it seems very easy to understand, easy to use, easy to modify and super powerful. I’ve started from this thread with this code snippet
# 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)
that shows some bars from the audio input level, with some modifications, a chain of “if”; I’ve modified the script so that it writes to a file when X silent samples are found. Silent is defined as “a level under the threshold, th, value.
#!/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)
After some more research, I’ve found a class to send e-mails using a Gmail account:
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)
To put everything together, I’ve created a system that sends an e-mail when the sound is silent:
#!/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)