Normalizing Audio

audio_wave

Once you’ve harvested your audio or video off the internet using the freeware Screen Capturer and Recorder, you’re going to want to do something with it.  Another great freeware solution for this task is ffmpeg.  Ffmpeg is a command-line utility that does just about everything with video and audio files that you’d ever want to do, but it is a bit cryptic at times.

What I wanted to do most recently was to normalize the audio in my files so that I wasn’t constantly messing with the volume during playback.  I wasn’t always super careful when setting the audio level during capture, so being able to do it with ffmpeg was great.  The problem was I only knew how to do one file at a time.

The first step to normalizing is to run the ffmpeg command with the audio filter “volumedetect”.  The command looks like this:

ffmpeg -i “test_output.mp4” -af “volumedetect” -f null NUL

If you’re not familiar with ffmpeg, the “-i” parameter defines the input file, “-af” says “this is an audio filter,” “volumedetect” is the name of the filter, and “-f null NUL” tell the program to use no output format and output to NUL.  If you were using UNIX this would be “/dev/null.”

You’ll get a block of output, but this is the important section:

[Parsed_volumedetect_0 @ 0000000005377280] n_samples: 1196032
[Parsed_volumedetect_0 @ 0000000005377280] mean_volume: -16.6 dB
[Parsed_volumedetect_0 @ 0000000005377280] max_volume: -7.3 dB
[Parsed_volumedetect_0 @ 0000000005377280] histogram_0db: 16
[Parsed_volumedetect_0 @ 0000000005377280] histogram_1db: 78
[Parsed_volumedetect_0 @ 0000000005377280] histogram_2db: 734
[Parsed_volumedetect_0 @ 0000000005377280] histogram_3db: 2326

For our purposes, you just look at the “max_volume” line.  In this case, the max_volume value is -7.3 dB, which means the max audio level is a bit low.  Correcting this is a very simple affair.  You use this command line:

ffmpeg -i “test_output.mp4” -af “volume=7.3dB” -vcodec copy -acodec -absf aac_adtstoasc -strict experimental “test_output_2.mp4”

You don’t need to wrap the input and output file names in double quotes if they don’t have spaces.  The “-af “volume=7.3dB”” parameter tells ffmpeg to boost the overall volume by +7.3 dB.  The “-vcodec copy” tells it to use the same video codec, and since you’re modifying the audio channel you have to specify the codec.  ” -acodec -absf aac_adtstoasc -strict experimental” tells ffmpeg to use the aac audio codec for output.

And that’s it.  Enter the command, wait for a bit, and you’ll have a nicely normalized file at the end of it.

Now what if you want to do a whole directory full of files?  You can’t use a simple For loop, because each file will need to be adjusted differently.

Enter Perl.

NORMALIZE.PL
use Proc::Background;
use Time::HiRes qw (sleep);

# stop output buffering
$|++;

#set up spinner characters
my @chars = qw(| \ - / | \ - /);

#open the local directory and read the filenames
opendir(my $dh, ".") || die "can't opendir localdir: $!";
while (readdir $dh) {

# set up your matching filename characters
if (m/.*mp4$/) {

# step through the files, creating a new background process to normalize the file
# a spinner lets you know the background process is still running
my $i = 0;
$video_file = $_;
print "\nprocessing $video_file $chars[$i]";
my $proc1 = Proc::Background->new('perl.exe normalize_in_background.pl "' . $video_file . '"');
while ( $proc1->alive) {
sleep (0.1);
print "\b", $chars[++$i % @chars];
}
print "\b ";
}
}

# when all the files are processed the script closes the directory and deletes all of the text files created during the run
closedir $dh;
system('del *.txt');
system('del normalized\\*.txt');
print "\nDone.";

This main Perl script calls another Perl script, normalize_in_background.pl, to do the real work.

NORMALIZE_IN_BACKGROUND.PL
$video_file = $ARGV[0];
$output = system ('ffmpeg -i "' . $video_file . '" -af "volumedetect" -f null NUL 2>"' . $video_file . '.txt"');
open ($fh, "<", "$video_file.txt") or die "Can't open $video_file.txt\n";
while (<$fh>) {
if (m/max_volume/) {
@line = split(" ");
$adjustment = -1 * $line[4];
$normalize = system ('ffmpeg -i "' . $video_file . '" -af "volume=' . $adjustment .
'dB" -vcodec copy -absf aac_adtstoasc -strict experimental "normalized\\' .
$video_file . '" 2> "normalized\\' . $video_file . '.txt"');
}
}

This code is very simple.  It uses as system call to run ffmpeg with the “volumedetect” filter, and output the result to a file.  Ffmpeg outputs everything to STDERR, so you must redirect accordingly.  The output text file is parsed for the line containing “max_volume”, the dB level is found and then applied in another system to ffmpeg to perform the actual normalization and place the output audio or video file into a directly named normalized. 

And that’s it.  A very simple Perl script — so simple I didn’t even put the #! command at the top of the script, but just ran it from the perl command.  You’ll probably want to fancy it up for your own use.  I’m a minimalist, myself.

Advertisements
Standard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s