ffmpeg + php: Конвертируем в mp3 и нормализуем

Итак, наша задача преобразовать практически любой медиа-файл со звуком в mp3 и нормализовать звук.

Наша рабочая лошадка, которая здорово поможет нам в этом конечно же ffmpeg

ffmpeg

Основная команда кодирования медиа-файла в mp3 такая:

ffmpeg -i input.wma -vn -ac 1 -ab 40K output.mp3

Умный движок ffmpeg пытается угадать кодек по расширению файла для конечного файла, а расширение mp3 однозначно его трактует. Так же для входных файлов он сам распознаёт кодеки.

Параметры:
-vn – отключить кодирование видео, полезно, если мы будем пытаться брать звук из видео-файлов
-ac 1 – число каналов 1 (моно), для нашего проекта моно достаточно, можно поставить 2 для стерео
-ab 40K – аудио-битрейт в 40Кбай так же довольно низок, но для нашего проекта для голосовых записей 40К довольно. Можно указать хоть 128K, хоть 196К – смотрите сколько Вам необохидмо по качеству.

input.wma – входной медиа-файл. В принципе может быть абсолютно любым медиа, хоть аудио, хоть видео, ffmpeg распознаёт наверное все современные медиа-форматы, надо только его время от времени обновлять, так как новые кодеки и даже новые версии старых кодеков появляются регулярно.

output.mp3 – выходной MP3-файл. MP3 стандарт де-факто, не будем спорить с этим, хотя OGG тоже не плох, только по нему меньше шума и маркетинга было.

Вот в принципе и всё для кодирования.

Ниже я напишу как можно управлять ffmpeg из php.

Нормализация

А как же нормализация?

Один из простейших видов нормализации – приведении максимальной (пиковой) громкости к 0 Дб.
Как это сделать – показано ниже.

ffmpeg не имеет встроенного фильтра нормализации, но у него есть фильтр volume, который позволяет увеличивать громкость аудио-дорожки.

Мы можем написать например так:

ffmpeg -i input.wma -vn -ac 1 -ab 40K -af "volume=5dB" -f mp3 output.mp3

и эта команда при конвертировании увеличит громкость аудио на 5 децибел.
Но если мы будем тупо увеличивать громкость всех аудио на 5 децибел, то слишком тихие аудио не особо прибавят в громкости, а громкие аудио станут просто громче.

Если мы попробуем прибавлять громкость например на 15 децибел, то тогда громкие аудио могут начать просто хрипеть, что не есть хорошо для нас. Где же выход?

Изучить громкость аудио, и прибавить столько децибел, сколько потребуется (или вообще не прибавлять, если звук достаточно громкий).

Для изучения громкости аудио в ffmpeg есть аудио-фильтр volumedetect. Вот как им пользоваться:

ffmpeg -i input.wma -af "volumedetect" -f null /dev/null

Сразу отмечу, что в Windows окончание /dev/null надо поменять на NUL.

Эта команда разрешает ffmpeg изучить громкость аудио дорожки и выдать полезную информацию по вопросу, которая может выглядеть например так:

[Parsed_volumedetect_0 @ 0000000002b03b20] n_samples: 13738752
[Parsed_volumedetect_0 @ 0000000002b03b20] mean_volume: -31.8 dB
[Parsed_volumedetect_0 @ 0000000002b03b20] max_volume: -15.2 dB
[Parsed_volumedetect_0 @ 0000000002b03b20] histogram_15db: 696
[Parsed_volumedetect_0 @ 0000000002b03b20] histogram_16db: 78935

В этом захватывающем воображении выводе от ffmpeg нас будет интересовать только параметр max_volume, который в идеале должен стремиться к 0.

В нашем случае он равен -15.2 dB, то есть для данного файла к аудио-дорожке нам нужно прибавить 15 децибел, чтобы нормализовать её.

Итак, процесс нормализации будет выглядеть так:

1. Изучаем вывод ffmpeg с фильтром volumedetect.
2. Парсим значение с max_volume и сохраняем его в переменной
3. Конвертим медиа-файл в mp3 с установленным фильтром volume=наша_переменнаяdB

На PHP данная задача решается в несколько строк кода и в следующем разделе мы увидим, как это действительно просто.

Кодим конвертер и нормализатор на PHP

В PHP мы будем использвовать вызов системной функции exec, которая будет вызывать ffmpeg с нужными параметрами.

Подразумевается, что ffmpeg установлен в нашей системе (на нашем сервере).

Итак, вот готовый код на PHP:

// пробуем конвертить файл в mp3 с помощью ffmpeg
// ffmpeg -i $rec -vn -ac 1 -ab 40K -f mp3 dest.mp3
 
$dest = uniqid('rec_').'.mp3'; // файл назначения
 
// Далее - наш медиа файл для конвертации и нормализации
// в принципе может быть любым медиа-файлом, который распознаёт ffmpeg,
// а он распознаёт много, если не всё
$src = 'somemediafile.amr';
 
$out = array(); // массив для получения результатов вывода ffmpeg
// вызываем ffmpeg в первый раз для получения инфо о громкости
// обратите внимание на окончание команды 2>&1,
// оно необходимо так как ffmpeg выпоняет вывод в stderr а не в stdout
// а exec перехватывает только вывод от stdout
exec("ffmpeg -i $src -af \"volumedetect\" -f null /dev/null 2>&1", $out);
 
// теперь пытаемся нормализовать звук тупо по max_volume стремящимся к 0dB
// доп.комментарии смотрите после этого листинга
$db = '';
for($i=0; $i < count($out); $i++)
	if(preg_match('/max_volume:\s+-?(\d+)/',$out[$i],$m)) {
		if(intval($m[1]) > 0)
                  $db = "-af \"volume={$m[1]}dB\"";
		break;
	}
 
$cmd = "ffmpeg -i $src -vn -ac 1 -ab 40K $db -f mp3 $dest";
exec($cmd);
 
// проверим наличие сконверженного файла
if(filesize($dest) == 0) {
	@unlink($dest);
	echo 'Ошибка конвертирования записи';
}
else
// теперь в $dest у нас должен остаться конечный нормализованный звук в mp3 формате
  echo 'Ok. Получили файл '.$dest;

В коде есть комментарии, но здесь можно прояснить ещё пару моментов.

Мы используем preg_match(‘/max_volume:\s+-?(\d+)/’,$out[$i],$m) для поиска и вычленения положительного значения max_volume из строки вида [Parsed_volumedetect_0 @ 0000000002b03b20] max_volume: -15.2 dB.

Тут мы отбрасываем любое кол-во пробелов и возможный знак `-` после подстроки max_volume:,
и затем считываем целое значенире в переменную массива $m[1].

Если оно больше нуля, то мы добавляем в вызов второй команды ffmpeg фильтр “-af \”volume={$m[1]}dB\””,
чтобы усилить звук, иначе мы опускаем этот параметр вовсе.

Второе. Чтобы понять, что мы получили конечный файл, мы проверяем его размер. Если попытка конвертации не удалась по какой-то причине (неверный или неизвестный ffmpeg медиа-формат файла), то размер этого файла будет равен 0.

Это ещё не всё

Мы добились многого, но не всего. В идеале нам нужно ещё вырезать лишние куски тишины (иногда в записях голосов бывает слишком много раздражающих пауз, которые к тому же увеличивают итоговый размер файла), и в идеале применить бы ещё фильтр выравниватель, так как некоторые записи могут быть разного уровня громкости в различных местах.

Хочется чтобы всё было ОК.

Я присматривался к утилите sox, но она для mp3 требует MAD, а я не люблю этого.

Я люблю когда всё работает out of the box, к чему и сам стремлюсь.

Удачи вам, друзья, до новых встреч!