Хабрахабр

[Перевод] AdBlock для радио

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

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

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

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

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

Octave

Обычно все ссылаются на реализацию MATLAB. Но MATLAB — дорогостоящее приложение, которое упрощает выполнение сложных математических операций, в том числе DSP. К счастью, есть бесплатная альтернатива под названием Octave. Кажется, в Octave несложно запустить взаимную корреляцию на двух аудиофайлах. Нужно лишь выполнить следующие команды:

pkg load signal
jingle = wavread('jingle.wav')(:,1);
audio = wavread ('audio.wav')(:,1);
[R, lag] = xcorr(jingle, audio);
plot(R);

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

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

Я хотел реализовать тот же алгоритм на 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, я изучил исходный код 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 и получил те же результаты, что в Octave. Я думаю, здесь отчасти карго-культ, но блин, это работает!

Запуск 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. Его можно скачать здесь. Если не любите запускать на своей машине чужие JAR (что совершенно правильно), то все исходники лежат на GitHub.

Похоже, всё работает как надо 🙂

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

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

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

Обсуждение на Hacker News
Обсуждение на Wykop
Обсуждение на Reddit

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть