- PVSM.RU - https://www.pvsm.ru -

AdBlock для радио

Автор статьи — польский программист Томек Рекавек, разрабатывает проект Jackrabbit Oak [1] в рамках Apache Software Foundation для Adobe. Статья опубликована в личном блоге автора 24 февраля 2016 года.

Польское «Радио-3» (так называемая «Тройка») знаменито хорошей музыкой и интеллигентными ведущими. С другой стороны, оно страдает наличием громких и раздражающих рекламных блоков в трансляции, где обычно рекламируется какая-нибудь электроника или лекарство. Я слушаю «Тройку» почти постоянно на работе и дома, поэтому задался вопросом: как удалить рекламу? Кажется, мне удалось найти решение.

Цифровая обработка сигналов

Моя цель — создать приложение, которое приглушает рекламу. Коммерческий блок начинается [2] и заканчивается [3] джинглами, поэтому программа должна распознать эти конкретные звуки и выключить звук между ними.

Знаю, что данная область математики/информатики называется цифровой обработкой сигналов, но мне DSP всегда казалась магией. Что ж, отличная возможность узнать что-то новое. Я провёл день или два, пытаясь выяснить, какой механизм использовать для анализа аудиопотока. И в конце концов нашёл то что надо: это взаимная корреляция или кросс-корреляция (cross-correlation).

Octave

Обычно все ссылаются на реализацию 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);

Получится такой график:

AdBlock для радио - 1

Хорошо заметен пик, описывающий положение jingle.wav в audio.wav. Что меня удивило, так это простота метода: всю работу делает xcorr(), остальной код только для чтения файлов и отображения результата.

Я хотел реализовать тот же алгоритм на Java, и тогда у меня будет инструмент, который:

  1. считывает аудиопоток со стандартного входа (например, от ffmpeg),
  2. анализирует его в поиске джинглов,
  3. выводит тот же поток на stdout и/или отключает его.

Использование 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

Чтобы реализовать функцию 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], но блин, это работает!

Запуск xcorr на потоке

Алгоритм выше предполагает, что 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].

AdBlock для радио - 2

Похоже, всё работает как надо [10] :)

Дальнейшая работа

Конечная цель — отключить рекламу на уровне аппаратного усилителя, получая «реальный» FM-сигнал, а не некий интернет-поток. Об этом рассказано в следующей статье [11].

Обновление (июнь 2018)

Обсуждение на 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