Volume Detection for a Audio Stream

Recently I was tasked with developing a solution for detecting the volume level of a audio stream coming from Icecast. Our SAM Broadcaster streams would sometimes keep their mount on the Icecast server but would not play any audio. This is still being investigated at the time of writing this post. ¯\_(ツ)_/¯

So as a check I decided to write a small NodeJS script to periodically check the stream and send an alarm if the audio was below a certain level.

Using FFmpeg

How do you programmatically check the volume of a stream? FFmpeg of course! “The swiss army knife of audio/video”™.

Here is the command we want:

$ ffmpeg -t 10 -i http://icecast.radio24.ch/radio24-rc-96-aac -af "volumedetect" -f null /dev/null
# Note: the icecast stream was randomly selected from the Icecast directory: dir.xiph.com

The above command above samples the stream for 1/10th of a second and uses the audio filter “volumedetect” and outputs the result.

Here is the output:

$ ffmpeg -t 10 -i http://icecast.radio24.ch/radio24-rc-96-aac -af “volumedetect” -f null /dev/null
ffmpeg version 3.3 Copyright © 2000–2017 the FFmpeg developers
built with Apple LLVM version 8.0.0 (clang-800.0.42.1)
configuration: — prefix=/usr/local/Cellar/ffmpeg/3.3 — enable-shared — enable-pthreads — enable-gpl — enable-version3 — enable-hardcoded-tables — enable-avresample — cc=clang — host-cflags= — host-ldflags= — enable-libmp3lame — enable-libx264 — enable-libxvid — enable-opencl — disable-lzma — enable-vda
libavutil 55. 58.100 / 55. 58.100
libavcodec 57. 89.100 / 57. 89.100
libavformat 57. 71.100 / 57. 71.100
libavdevice 57. 6.100 / 57. 6.100
libavfilter 6. 82.100 / 6. 82.100
libavresample 3. 5. 0 / 3. 5. 0
libswscale 4. 6.100 / 4. 6.100
libswresample 2. 7.100 / 2. 7.100
libpostproc 54. 5.100 / 54. 5.100
Input #0, mp3, from ‘http://icecast.radio24.ch/radio24-rc-96-aac’:
Metadata:
icy-br : 128
icy-description : EHR
icy-genre : Various
icy-name : Radio 24
icy-pub : 0
icy-url : http://www.radio24.ch
StreamTitle : WINCENT WEISS+-+MUSIK SEIN
StreamUrl :
Duration: N/A, start: 0.000000, bitrate: 128 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, s16p, 128 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (mp3 (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, null, to ‘/dev/null’:
Metadata:
icy-br : 128
icy-description : EHR
icy-genre : Various
icy-name : Radio 24
icy-pub : 0
icy-url : http://www.radio24.ch
StreamTitle : WINCENT WEISS+-+MUSIK SEIN
StreamUrl :
encoder : Lavf57.71.100
Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
Metadata:
encoder : Lavc57.89.100 pcm_s16le
size=N/A time=00:00:10.00 bitrate=N/A speed=9.77x
video:0kB audio:1723kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
[Parsed_volumedetect_0 @ 0x7f803aa00000] n_samples: 882000
[Parsed_volumedetect_0 @ 0x7f803aa00000] mean_volume: -15.3 dB
[Parsed_volumedetect_0 @ 0x7f803aa00000] max_volume: -2.0 dB
[Parsed_volumedetect_0 @ 0x7f803aa00000] histogram_2db: 22
[Parsed_volumedetect_0 @ 0x7f803aa00000] histogram_3db: 44
[Parsed_volumedetect_0 @ 0x7f803aa00000] histogram_4db: 705
[Parsed_volumedetect_0 @ 0x7f803aa00000] histogram_5db: 2628

The line that we are looking for is the `mean_volume: -15.3 dB`. Dead silence would output -91 dB. We want to set the threshold for our alarm to be-50 dB, which would be a very low volume.

The NodeJS Script

Now that we have the command who do we write this in to a NodeJS script? There is a nice NodeJS library out there called node-fluent-ffmpeg which provides a nice wrapper for FFmpeg.

Here is our code:

So far the above script has worked pretty well. Every once in a while a stream will be transitioning between tracks or a song will have low or no audio for a moment, so you will receive false positives. One thing to fix this is to set a timeout for 1–2 seconds and then run the test again.

If you have any thoughts on what I could do to improve this script, I would love to hear them. Cheers!