- PVSM.RU - https://www.pvsm.ru -
Автор статьи — польский программист Томек Рекавек, разрабатывает проект Jackrabbit Oak [1] в рамках Apache Software Foundation для Adobe. Статья опубликована в личном блоге автора 24 февраля 2016 года.
Польское «Радио-3» (так называемая «Тройка») знаменито хорошей музыкой и интеллигентными ведущими. С другой стороны, оно страдает наличием громких и раздражающих рекламных блоков в трансляции, где обычно рекламируется какая-нибудь электроника или лекарство. Я слушаю «Тройку» почти постоянно на работе и дома, поэтому задался вопросом: как удалить рекламу? Кажется, мне удалось найти решение.
Моя цель — создать приложение, которое приглушает рекламу. Коммерческий блок начинается [2] и заканчивается [3] джинглами, поэтому программа должна распознать эти конкретные звуки и выключить звук между ними.
Знаю, что данная область математики/информатики называется цифровой обработкой сигналов, но мне DSP всегда казалась магией. Что ж, отличная возможность узнать что-то новое. Я провёл день или два, пытаясь выяснить, какой механизм использовать для анализа аудиопотока. И в конце концов нашёл то что надо: это взаимная корреляция или кросс-корреляция (cross-correlation).
Обычно все ссылаются на реализацию MATLAB. Но MATLAB — дорогостоящее приложение, которое упрощает выполнение сложных математических операций, в том числе DSP. К счастью, есть бесплатная альтернатива под названием Octave [4]. Кажется, в Octave несложно запустить взаимную корреляцию на двух аудиофайлах. Нужно лишь выполнить следующие команды:
pkg load signal
jingle = wavread('jingle.wav')(:,1);
audio = wavread ('audio.wav')(:,1);
[R, lag] = xcorr(jingle, audio);
plot(R);
Получится такой график:
Хорошо заметен пик, описывающий положение jingle.wav
в audio.wav
. Что меня удивило, так это простота метода: всю работу делает xcorr()
, остальной код только для чтения файлов и отображения результата.
Я хотел реализовать тот же алгоритм на Java, и тогда у меня будет инструмент, который:
Использование stdin и stdout позволит подключить новый анализатор к другим приложениям, отвечающим за аудиотрансляцию и воспроизведение результата.
Первым делом Java-программа должна прочитать джингл (сохранённый в виде файла .wav
) в массив. В файле есть некоторая дополнительная информацию вроде заголовков, метаданных и прочего, но нам нужен только звук. Подходящий формат называется PCM, это просто список чисел, представляющих звуки. Преобразовать WAV в PCM может ffmpeg:
ffmpeg -i input.wav -f s16le -acodec pcm_s16le output.raw
Здесь каждый сэмпл сохраняется в виде 16-битного числа с обратным порядком байтов (little endian). В Java такое число называется short
, а для автоматического преобразования входного потока в список значений short
можно использовать класс ByteBuffer
:
ByteBuffer buf = ByteBuffer.allocate(4);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put(bytes);
short leftChannel = buf.readShort(); // stereo stream
short rightChannel = buf.readShort();
Чтобы реализовать функцию xcorr()
на Java, я изучил исходный код [5] Octave. Не изменяя конечный результат, я смог заменить вызов xcorr() следующими строчками — их нужно переписать на Java:
N = length(audio);
M = 2 ^ nextpow2(2 * N - 1);
pre = fft(postpad(prepad(jingle(:), length(jingle) + N - 1), M));
post = fft(postpad(audio(:), M));
cor = ifft(pre .* conj(post));
R = real(cor(1:2 * N));
Выглядит страшновато, но большинство функций — тривиальные операции с массивами. В основе кросс-корреляции лежит применение быстрого преобразования Фурье на звуковом образце.
Как человек, который не имел опыта работы с DSP, я просто рассматриваю FFT как функцию, которая берёт массив с описанием звукового образца — и возвращает массив с комплексными числами, представляющими частоты. Такой минималистичный подход хорошо сработал: я запустил реализацию FFT из пакета JTransforms [6] и получил те же результаты, что в Octave. Я думаю, здесь отчасти карго-культ [7], но блин, это работает!
Алгоритм выше предполагает, что audio
представляет собой массив, в котором мы ищем jingle
. Это не совсем подходит для радиотрансляции, где у нас непрерывный поток звука. Чтобы запустить анализ, я создал циклический буфер чуть больше, чем продолжительность джингла, который нужно распознать. Входящий поток заполняет буфер, и как только он заполнен, запускается тест кросс-корреляции. Если ничего не найдено, то самая старая часть буфера отбрасывается — и снова ожидаем его заполнения.
Я немного поэкспериментировал с длиной буфера и получил наилучшие результаты с размером буфера в 1,5 раза больше размера джингла.
Получить поток в формате PCM несложно. Это можно сделать с помощью вышеупомянутого ffmpeg
. Команда ниже перенаправляет поток на стандартный вход java
, а затем выводит Got jingle 0
или Got jingle 1
, когда в потоке найден соответствующий образец.
ffmpeg -loglevel -8
-i http://stream3.polskieradio.pl:8904/;stream
-f s16le -acodec pcm_s16le -
| java -jar target/analyzer-1.0.0-SNAPSHOT-jar-with-dependencies.jar
2
src/test/resources/commercial-start-44.1k.raw 500
src/test/resources/commercial-end-44.1k.raw 700
Я также подготовил простую автономную версию анализатора, которая сама подключается к потоку «Тройки» (без внешнего ffmpeg
) и воспроизводит результат с помощью javax.sound
. Всё вмещается в один файл JAR и содержит базовый пользовательский интерфейс с кнопками Star и Stop. Его можно скачать здесь [8]. Если не любите запускать на своей машине чужие JAR (что совершенно правильно), то все исходники лежат на GitHub [9].
Похоже, всё работает как надо [10] :)
Конечная цель — отключить рекламу на уровне аппаратного усилителя, получая «реальный» FM-сигнал, а не некий интернет-поток. Об этом рассказано в следующей статье [11].
Обсуждение на Hacker News [12]
Обсуждение на Wykop [13]
Обсуждение на Reddit [14]
Автор: m1rko
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/octave/284286
Ссылки в тексте:
[1] Jackrabbit Oak: https://jackrabbit.apache.org/oak/
[2] начинается: https://soundcloud.com/tomek-r-kawek/jingle-start
[3] заканчивается: https://soundcloud.com/tomek-r-kawek/jingle-end
[4] Octave: https://www.gnu.org/software/octave/
[5] исходный код: https://sourceforge.net/p/octave/signal/ci/default/tree/inst/xcorr.m
[6] JTransforms: https://github.com/wendykierp/JTransforms
[7] карго-культ: https://en.wikipedia.org/wiki/Cargo_cult
[8] здесь: http://blog.rekawek.eu/files/radioblock-1.3.1.jar
[9] GitHub: https://github.com/trekawek/radioblock
[10] всё работает как надо: https://soundcloud.com/tomek-r-kawek/commercial-block-muted
[11] следующей статье: http://blog.rekawek.eu/2016/02/27/radio-adblock-2/
[12] Обсуждение на Hacker News: https://news.ycombinator.com/item?id=17385563
[13] Обсуждение на Wykop: https://www.wykop.pl/link/4385801/radio-adblock/
[14] Обсуждение на Reddit: https://www.reddit.com/r/programming/comments/8to4wm/radio_adblock/
[15] Источник: https://habr.com/post/415469/?utm_campaign=415469
Нажмите здесь для печати.