В недавнем апдейте прошивки для Яндекс Станции Макс мы добавили поддержку вывода звука по кабелю HDMI при просмотре фильмов. Если у вашего телевизора хорошая акустика (или к нему подключена качественная аудиосистема), теперь можно слушать аудиодорожку через неё. Это обновление мы выпустили только сейчас, потому что оно потребовало нетривиальных технических решений. Например, нужно было сделать эхоподавление для HDMI. Вот об этом и поговорим — сначала обсудим историю технологий и проблемы с подавлением собственного звука устройства, а затем перейдём к нашему решению.

Как мы боролись с эхом на первой Станции


С появлением умных колонок мы стали развивать комплекс технологий для улучшения качества и чёткости речи. В него входят разные решения. Например, Acoustic Noise Reduction (снижение уровня фонового шума вроде звуков пылесоса или кофеварки), Beamforming (усиление или ослабление определенных направлений — по сути, определение множества источников звука), Dereverberation (удаление эффекта эхо от стен, мебели и тому подобного).

Ключевые направление исследований для темы этого поста — эхоподавление, или Acoustic Echo Cancellation (AEC), то есть удаление из сигнала с микрофона звука, проигрываемого динамиками самого устройства. Суть в том, чтобы колонка не оглушала саму себя и могла услышать команды от пользователя.

Эта задача существует давно: даже на старых телефонах Nokia был Acoustic Echo Cancellation. Эту же задачу решают производители оборудования для конференц-связи и ноутбуков, чтобы пользователи могли общаться в Zoom без наушников. Можно сказать, что всегда, когда есть динамик и микрофон, нужно уметь вычитать звук первого из второго. Разумеется, такая задача встала и перед нами, когда мы стали делать свои умные устройства.

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

Почему мы решили делать собственное решение? Во-первых, алгоритмы можно улучшать и тонко настраивать под конкретные устройства. Во-вторых, это позволяет обеспечить более гибкую интеграцию с другими частями Алисы, например с активацией голосом (споттером) или автоматическим распознаванием речи (ASR). В-третьих, собственные решения в перспективе обходятся дешевле, потому что сторонние проприетарные библиотеки, как правило, платные.



Как работает AEC


В упрощённом виде задача выглядит так: умная колонка играет композицию из Яндекс Музыки, звук отражается от стен и возвращается в микрофон, который должен также воспринимать команды пользователя. У нас есть Echo Canceller — своего рода «чёрный ящик», который принимает сигналы из Яндекс Музыки и с микрофона Станции, вычитает одно из другого и получает только звуковую команду человека без фоновой мелодии.

С физической точки зрения задача выглядит следующим образом: есть звуковые волны, в определённый момент выпущенные динамиками колонки. Они исходят в разные стороны, отражаются от стен и возвращаются в микрофон с разными задержками и интенсивностями. Это искажение называется передаточной функцией. Нужно вычислить его на лету и применить при вычитании.

Вот как можно алгоритмически описать Acoustic Echo Cancellation:

Есть сигнал микрофона M — бесконечная последовательность вещественных чисел. Есть сигнал колонки S — тоже последовательность чисел. M — это комбинация музыки и речи, то есть:

M = Music + Speech

Нужно оценить Music, зная S.

Решение:

Запишем формулу:

Music = S[0]*a[0] + S[-1]*a[-1] +… + S[-L]*a[-L], где

L — достаточно большая константа, приблизительно от 200 миллисекунд до 1 секунды, одинаковая для всех помещений (по сути это время «долёта» звуковой волны).

a — вещественные коэффициенты, которые нужно на лету подстраивать. Они зависят от конфигурации помещения — размеров, мебели, ковров и даже перемещений человека.

И теперь будем искать коэффициенты a градиентным спуском по оптимизационной задаче:

(M — Music)^2 → min

Это решение — бейзлайн, все с него начинают. Оно называется Least Mean Squares filter (LMS) и справляется с задачей, но качество можно улучшать.

Проблема такого подхода в том, что для каждого сигнала нужно обновлять коэффициенты фильтра. Звук обычно приходит 16 000 раз в секунду — если каждый раз обновлять несколько тысяч коэффициентов, получатся миллионы операций, что загрузит процессор колонки, которой ещё нужно играть музыку и распознавать речь.

Чтобы решить эту проблему, можно предположить, что каждая частота не зависит от остальных, то есть звук не меняет частоту по мере прохождения по комнате. Поэтому можно обрабатывать частоты независимо.



Сигналы с микрофона и динамика колонки проходят через оконные преобразования Фурье и делятся на отдельные частоты, которые затем пропускаются через LMS-фильтры. В результате получается вектор эха по частотам. Очистка исходного звука заключается в том, чтобы из микрофона вычесть эхо и сделать обратное преобразование Фурье. Это работает быстро и без потерь качества.

Можно дополнительно улучшить качество AEC, использовав фильтр Калмана, — именно такой вариант работает у нас в продакшене. Это линейный адаптивный фильтр, который позволяет вычитать сигнал независимо в каждой частоте и учитывает существование голоса и посторонних звуков.

Стерео-AEC для Станции Макс


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

В терминах нашего алгоритма задача выглядит так:

Есть микрофон M. Есть два динамика S1 и S2. Чтобы оценить значение Music, нужно сложить сигналы с обоих динамиков:

Music = S1[0]*a[0] + S1[-1]*a[-1] +… + S1[-L]*a[-L] +
S2[0]*b[0] + S2[-1]*b[-1] +… + S2[-L]*b[-L]

Здесь возникает следующая проблема: если S1 очень похожа на S2, то алгоритм может не сойтись, поскольку из-за линейной зависимости каналов, задача становится недостаточно обусловленной и может иметь бесконечное количество решений. В результате итеративная сходимость алгоритма ломается. Поэтому стерео-AEC сложнее, чем моно-AEC. В алгоритме нужно учитывать возможную корреляцию между каналами, как, например, это сделано в стереофильтре Калмана.

HDMI


Многие пользователи ждали, что мы реализуем вывод звука по HDMI. Но без AEC это означало бы, что колонка потеряет возможность слышать человека при проигрывании музыки или других звуков. Она бы просто не умела вычитать звук, воспроизводимый через HDMI в телевизоре. Дело в том, что в классическом исполнении AEC мы используем фидбеки в виде напряжения, которое подается на динамики и считывается с помощью аналого-цифрового преобразователя. При переходе на HDMI-воспоизведение напряжение на динамики не подается, а звук проигрывается с динамиков телевизора или саундбара. Таким образом, для HDMI-AEC нужны альтернативные референсные каналы.

Аудиодорожка HDMI-канала, как и Станция Макс, — двухканальная, поэтому придется использоавть стерео-АЕС, но это не единственное усложнение.

Основная проблема с AEC для HDMI — синхронизация микрофона Станции и динамиков телевизора. Звуковые дорожки на устройстве обычно синхронизированны отдельными часами, что гарантирует минимальную и стабильную задержку между каналами микрофона и динамика. В случае с HDMI это не так — каналы рассинхронизированы на сотни милисекунд. При этом такая задержка меняется от запуска к запуску звука через HDMI.

Для качественного вычитания музыки и другого аудиоконтента нужно их синхронизировать: если этого не делать, то качество деградирует прямо на глазах.

Если звук микрофона отстаёт от динамика…



…то всё просто — нужно всего лишь подождать микрофон. Но бывает и наоборот — сигнал динамика может отставать от микрофона:



Это контринтуитивно, но в реальности случается довольно часто из-за особенностей системной реализации. Дело в том, что микрофон и динамик — это разные устройства, чтение из которых никак не синхронизировано. Это неизбежно приводит к сдвигу одного сигнала относительно другого — так и возникает отставание.

Подробности
Микрофоны и аналого-цифровой преобразователь — это физически разные устройства, подключённые к разным шинам. В системе они представлены отдельными устройствами.

При вызове функций snd_pcm_start/snd_pcm_prepare на разных девайсах может возникать сдвиг, обусловленный многозадачностью. Для решения проблемы можно связать два устройства вызовом snd_pcm_link. Тогда вызов snd_pcm_start/snd_pcm_prepare на одном из девайсов приведёт к срабатыванию соответствующих триггеров в драйверах всех связанных устройств.

Но это не решает всех проблем. Триггеры всё равно вызываются последовательно, и между вызовом триггера в драйвере ALSA девайса и реальным захватом звука в разных устройствах может пройти разное время. Девайсы обслуживаются разными драйверами и могут иметь разные источники тактирования, тактироваться с разной частотой и не обязаны быть синхронизированными. Также могут возникать задержки, связанные с реализацией аппаратуры и драйверов. Кроме того, этот способ накладывает дополнительные ограничения: нужно аккуратно работать с параметрами устройств (buffer size, period size, start threshold и так далее).

В этом случае придётся «подождать» динамик, а значит — задержать ответ пользователю. Это решение сделает менее удобным использование колонки, поэтому мы пошли искать другие способы.

В Станции Макс, в отличие от первой Станции, есть аппаратный синхронизатор: он принимает на вход любые два источника, один из которых — микрофоны, а другой — фидбек от динамика или HDMI. Ограничение в том, что синхронизатор может работать только с одним источником — либо с колонкой, либо с телевизором (HDMI). К счастью, одновременно колонка с телевизором не работают, поэтому нам нужно было просто научиться понимать, какое устройство играет в конкретный момент, и синхронизировать соответствующий источник с микрофонами.

После того как мы научились синхронизировать каналы, осталось соединить все детали между собой: настроить алгоритм под разные режимы, динамически переключать синхронизатор и режим работы алгоритма, а также следить, чтобы система оставалась стабильной.

Послесловие


Как показала практика, мы правильно сделали ставку на собственные решения VQE. За короткое время нам удалось создать эффективный алгоритм, который мы тщательно подогнали под устройства. Оказалось, что кастомизация под железо, быстрая коммуникация со смежниками и возможность быстро вносить изменения обеспечивает заметный выигрыш в качестве над проприетарными решениями.

Мы продолжаем развивать как AEC, так и другие наши решения в области VQE. Будем рассказывать о них в будущих постах.

Honorable mentions:

  • Борис Василевский borisvasilevskiy, который сделал бóльшую часть работы со стороны команды VQE.
  • Михаил Ванчугов vanchugov, который обеспечивал синхронизацию на стороне системной разработки.
  • Михаил Максимов, который сшивал это в прикладном коде.

Комментарии (12)


  1. Kembreg
    00.00.0000 00:00
    +3

    Удивительное рядом. Звук по HDMI заучились воспроизводить, а с изображением до сих пор проблема. "Макс" является единственным устройством в моём зоопарке, который произвольно инвертирует цвета на экране при смене сцены и продолжает это делать с завидным постоянством (да, в ТП обращался, нет, там не помогли).

    Насколько я понимаю речь про настройку "Цветового профиля" и невозможности установить RGB Данная настройка есть на Яндекс.Модуле, но до сих пор, спустя годы, отсутствует на Максе


  1. ivanglushnev
    00.00.0000 00:00
    +1

    а на Станции 1 поколения эта фича доступна? А то я очень расстроился, когда понял, что кино она показывать может через HDMI, но звук???? моно через колонку


    1. evgs89
      00.00.0000 00:00
      +1

      В Станции Макс, в отличие от первой Станции, есть аппаратный синхронизатор: он принимает на вход любые два источника, один из которых — микрофоны, а другой — фидбек от динамика или HDMI. Ограничение в том, что синхронизатор может работать только с одним источником — либо с колонкой, либо с телевизором (HDMI). К счастью, одновременно колонка с телевизором не работают, поэтому нам нужно было просто научиться понимать, какое устройство играет в конкретный момент, и синхронизировать соответствующий источник с микрофонами.

      прямо в тексте написано...


      1. ssj100
        00.00.0000 00:00
        +3

        В Станции Макс, в отличие от первой Станции, есть аппаратный синхронизатор: он принимает на вход любые два источника, один из которых — микрофоны, а другой — фидбек от динамика или HDMI. 

        - готов “пожертвовать” микрофоном ради звука в HDMI . для первой Станции там вообще есть кнопка отключение микрофона - могли бы переиспользовать на переключение на HDMI если в данный момент включен телек, и да был огорчен не увидев возможность использования звука


    1. trunya
      00.00.0000 00:00

      ну у меня через раз работает то выдает сигнал на саундбар транзитом то нет, от чего зависит непонятно ....


    1. Akr0n
      00.00.0000 00:00
      +4

      Справедливости ради стоит признать, что звук с колонки всё-таки мощнее и чище чем у встроенных динамиков большинства подключенных к ней ТВ.


      1. turbodriver
        00.00.0000 00:00

        это проблема для тех, у кого есть нормальная акустика с 5.1, но как я понял тут все равно стерео, поэтому нормальные фильмы всё равно не посмотреть.


  1. evgs89
    00.00.0000 00:00
    +1

    Было бы неплохо иметь устройство с микрофоном и микшером - чтобы можно было домашнюю акустику подключить через него. Потому что не всегда именно станция будет включена в качестве источника, и не обязательно источник совместим с Яндексом в принципе, а умные фишки от Яндекса иметь всё же хочется. Умный микшер, который мог бы притушить звук внешнего источника и через внешние колонки ответить на заданный голосом вопрос или выполнить команду намного нужнее в гостиной, чем просто яндекс.Станция...


  1. mavrikk
    00.00.0000 00:00
    +2

    а нормализацию звука в яндекс.музыку так и не завезли?


  1. the-swamp-screamer
    00.00.0000 00:00

    а когда появится возможность 2 станции по bluetooth подключить?


  1. turbodriver
    00.00.0000 00:00

    а как вы решили эту проблему со станцией-мини и выходом через джек?


  1. JustMoose
    00.00.0000 00:00

    Ничего не понял, но очень интересно.

    Ребята, вы крутые!