• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Android: погружаемся в недра ОС, чтобы найти и устранить баг

stihl

Moderator
Регистрация
09.02.2012
Сообщения
1,178
Розыгрыши
0
Реакции
510
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Сегодня нас с тобой ждет настоящая экспедиция вглубь Android. Нашей сиюминутной целью будет устранить баг воспроизведения звука на телеприставке X96Q PRO. На пути к этой цели мы подробно изучим все части звуковой системы и научимся вносить изменения в низкоуровневые настройки, недоступные из графического интерфейса.
Android в нашей подопытной приставке точно такой же, как в телефонах и автомагнитолах. Все принципиальные сведения, кроме конкретных схем и конкретного кода библиотеки HAL, актуальны для всех устройств на Android 10, и, с большой долей вероятности, от Android 7 до Android 13 включительно. Начиная с версии 14 в Android стали использовать язык описания интерфейса AIDL вместо HIDL, но это касается в большей степени низкоуровневых разработчиков.


В каком ухе у меня жужжит?​

Эта история началась с замены телевизионной приставки. У меня был старенький девайс с одним гигабайтом памяти — на смену ему я выбрал модель Для просмотра ссылки Войди или Зарегистрируйся в варианте с двумя гигабайтами оперативки. Ее операционная система Android 10 не захламлена, в ней разблокирована возможность получения прав суперпользователя. Кажется, то что надо.


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

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

В художественных фильмах или новостных программах это почти незаметно, но в отдельных музыкальных композициях звук искажался до невозможности. Я не аудиофил и меня не испугать записью концерта в MP3 со скоростью потока 128 Кбит/с. Но щелчки все же стали действовать на нервы. Я понял, что если это не исправить, то скоро начнет дергаться глаз.

Если у тебя есть устройство с Android, ты можешь прямо сейчас проверить, присущ ли ему описываемый дефект. Для этого воспроизведи файл Для просмотра ссылки Войди или Зарегистрируйся с записью гудка европейской телефонной станции (это синусоида с частотой 425 Гц). На качественном устройстве тон должен быть чистым, мягким и ровным. Если же звук плавает, в нем слышны посторонние шумы или потрескивания, то сочувствую: твое устройство подвержено проблеме, которую мы будем решать в этой статье.

Возможно, тебя миновала эта неприятность, но тебе интересно устройство аудиоподсистемы Android 10 и способ изменения частоты дискретизации выводимого приставкой аудиопотока. Об этом я тоже постараюсь рассказать.


Методика и инструменты исследования​

Сначала, как любой нормальный человек, я попытался настроить звук через интерфейс Android, но выбор там невелик: всего‑то и можно, что включить или выключить AUDIO_CODEC, AUDIO_HDMI и режим проброса звука passthrough. Вне зависимости от установленных флажков, дефекты звука продолжали свое деструктивное воздействие на психику.

Я перебрал несколько медиапроигрывателей, но это тоже не дало результата. Вот и все, обычный пользователь потерпел поражение в этой схватке с Android. Поэтому на сцену пришлось выйти инженеру. Инженерный подход к решению проблем подразумевает сбор и изучение доступной информации об объекте, его диагностику для выяснения причин проблемы, выбор решения и исправление ситуации. Что ж, приступим!

План исследования звуковой подсистемы Android следующий:

  1. Анализ системного журнала Android, который можно получить с помощью команды logcat.
  2. Изучение состояний служб Android, сведения о которых можно получить с помощью команды dumpsys.
  3. Изучение состояния подсистемы ALSA по содержимому каталога /proc/asound и с помощью утилиты tinymix.
  4. Анализ динамической библиотеки слоя абстракции Audio HAL в среде Ubuntu с помощью дизассемблера objdump.
  5. Анализ исходных текстов операционной системы Android.

Осторожно, кирпич!​

Внося изменения в конфигурационные файлы Android-устройства, оказалось удивительно легко привести его в неработоспособное состояние, или, как говорят, «окирпичить». Например, если в теге HDMI Out файла audio_policy_configuration.xml слово Out написать с маленькой буквы, загрузка телевизионной приставки будет останавливаться на этапе демонстрации анимированной заставки.
В большинстве случаев восстановление работоспособности возможно, но для этого потребуются специальные знания и софт. С последним сложнее всего. На Для просмотра ссылки Войди или Зарегистрируйся приставки X96Q PRO есть четыре варианта firmware, но только один подходит к моему экземпляру. Попытка «прошить» другие приводит к тому, что устройство перестает опознаваться даже сервисным ПО.
Поэтому трезво оцени свои силы перед тем, как что‑то сделать на реальном оборудовании. Как минимум заранее ознакомься с опытом неудачников на тематических форумах. Но все равно будь готов к тому, что устройство может быть безнадежно испорчено.
Команды можно выполнять непосредственно в консоли устройства из приложения‑терминала, например, Для просмотра ссылки Войди или Зарегистрируйся. Но я использовал возможности удаленной отладки устройств с Android, для чего установил на свой компьютер пакет Для просмотра ссылки Войди или Зарегистрируйся с утилитой командной строки adb.

Для замены конфигов и установки утилиты tinymix я использовал Для просмотра ссылки Войди или Зарегистрируйся, которое дает права суперпользователя и позволяет оформлять модификации в виде подключаемых модулей.

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

info​

Иногда Magisk приводит к интересным побочным эффектам. Я заметил, что при активном модуле с исправлениями после перезагрузки восстанавливается расположение плиток на рабочем столе ATV Launcher. Чтобы переместить плитки «навсегда», надо сначала отключить модуль, потом выполнить перемещения, после чего снова его включить. Такая защита от случайного нарушения привычной раскладки интерфейса мне показалась очень удобной.

Что такое цифровой звук​

Чтобы лучше понимать работу звуковой подсистемы приставки, надо вспомнить, что собой представляют звуковые данные. Как ты знаешь из школьного курса физики, человек слышит звук из‑за изменения давления воздуха на расположенную в ухе барабанную перепонку с частотой 20 — 20000 Гц. Этот процесс, как и все остальные физические процессы в макромире, благодаря явлению инерции является плавным и непрерывным.
Для использования в компьютерной технике аналоговая физическая величина — в данном случае давление воздуха — представляется конечной последовательностью чисел из ограниченного набора с помощью двух подходов: дискретизации по времени и квантования по уровню.
Оцифровка звука происходит путем дискретизации по времени и квантования по уровню
Оцифровка звука происходит путем дискретизации по времени и квантования по уровню

Суть дискретизации состоит в записи значений физической величины не непрерывно, а только в определенные моменты времени, следующие с определенной частотой. Выбор частоты дискретизации определяется теоремой отсчетов (Котельникова — Найквиста — Шеннона), которая гласит, что исходный сигнал, максимальная частота компонентов которого не превышает F, можно полностью восстановить по измеренным отсчетам, следующим с частотой не меньше 2 × F. Таким образом, по отсчетам с частотой дискретизации 44100 Гц можно восстановить сигнал, частота компонентов которого не превышает 22050 Гц, что перекрывает указанный выше диапазон звуков, слышимых человеком.
Впрочем, точное восстановление сигнала возможно лишь в том случае, если измеренные значения записаны без ошибок. Однако аналого‑цифровые преобразователи (АЦП) отображают измеренную датчиком физическую величину на конечную разрядную сетку машинного представления числа, выполняя квантование значения по уровню. Это неизбежно ведет к ошибкам (погрешностям) записи измеренных значений. Например, 16-разрядные АЦП непрерывный интервал изменения физической величины отображают на 216 = 65536 уровней, которые могут быть пронумерованы целыми числами из диапазона -32768...+32767.
Описанный способ записи аналогового сигнала в цифровой форме называется импульсно‑кодовой модуляцией PCM (Pulse-Code Modulation). Для увеличения компактности записей используются разные методы их сжатия: с потерями (AAC, MP3, OGG) или без потерь (FLAC, WMA Losseless). Но перед воспроизведением данные все равно потребуется распаковать в импульсно‑кодовую форму, которая пригодна для непосредственного преобразования в физическую величину — звук.

Звуковая подсистема приставки​

В Android запросы на работу со звуком обслуживает служба Audio Flinger сервера звука, которая через слой абстракции от аппаратуры HAL (Hardware Abstraction Layer) управляет звуковой подсистемой ALSA (Advanced Linux Sound System) уровня ядра.

Приложения для воспроизведения звука в Android используют объект класса Для просмотра ссылки Войди или Зарегистрируйся, с помощью которого данные в кодировке PCM из программного буфера направляются в устройство воспроизведения. Служба Audio Flinger тесно взаимодействует со службой Audio Policy, которая на основе конфига audio_policy_configuration.xml определяет, какие устройства и в каком режиме должны использоваться для воспроизведения звука.

Устройство звуковой подсистемы Android
Устройство звуковой подсистемы Android

Слой аппаратной абстракции (HAL) сопоставляет стандартные виртуальные устройства Android — такие как AUDIO_DEVICE_OUT_SPEAKER (встроенный динамик), AUDIO_DEVICE_OUT_WIRED_HEADPHONE (проводные наушники) и AUDIO_DEVICE_OUT_AUX_DIGITAL (цифровой аудиовыход) — с конкретным физическим оборудованием. Это оборудование определяется и настраивается на уровне ядра Linux через описание в дереве устройств (Device Tree).

Разработчики HAL перечисляют устройства, которые могут использоваться аудиосервером, в секции <devicePorts> файла audio_policy_configuration.xml, например:

Код:
<devicePorts>
    <devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="sink"></devicePort>
    <devicePort tagName="HDMI Out" type="AUDIO_DEVICE_OUT_AUX_DIGITAL" role="sink"></devicePort>
</devicePorts>

В приведенном примере виртуальному устройству AUDIO_DEVICE_OUT_SPEAKER, которое играет роль приемника звука sink (в противоположность источникам звука — source), назначается имя Speaker. По этому имени на устройство ссылаются в других местах конфига. Например, при перечислении аудиоустройств, которые встроены в приставку (в противоположность устройствам, которые могут подключаться периодически):

Код:
<attachedDevices>
    <item>Speaker</item>
</attachedDevices>

Уровень аудиосервера​


Сбор информации о службах​

Анализировать работу звуковой подсистемы Android на уровне аудиосервера помогает информация о состоянии служб, которую выводит команда dumpsys. В нашем случае полезны два варианта этой команды:

Код:
dumpsys media.audio_policy
dumpsys media.audio_flinger
Первая команда рассказывает о том, как были интерпретированы настройки из audio_policy_configuration.xml. В частности, какой именно конфиг используется:

Config source: /vendor/etc/audio_policy_configuration.xml
Далее dumpsys в понятной форме отображает правила, взятые из конфига. Например, сведения об устройствах и приоритетном канале воспроизведения звука описываются так:

Код:
<modules>
    <module name="primary" halVersion="2.0">
        <attachedDevices>
            <item>Speaker</item>
            ...
        </attachedDevices>
        <defaultOutputDevice>Speaker</defaultOutputDevice>
        <mixPorts>
            <mixPort name="primary output" role="source" flags="AUDIO_OUTPUT_FLAG_PRIMARY">
                <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                    samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                    </mixPort>
                    ...
                </mixPorts>
                <devicePorts>
            <devicePort tagName="Speaker" type="AUDIO_DEVICE_OUT_SPEAKER" role="sink"></devicePort>
            <devicePort tagName="HDMI Out" type="AUDIO_DEVICE_OUT_AUX_DIGITAL" role="sink"></devicePort>
            ...
                </devicePorts>
                <routes>
            <route type="mix" sink="Speaker" sources="primary output"/>
            <route type="mix" sink="HDMI Out" sources="primary output"/>
            ...
                </routes>
    </module>
</modules>

А вот расшифровка:

Код:
- Available output devices:
  Device 1:
  - id:  2
  - tag name: Speaker
  - type: AUDIO_DEVICE_OUT_SPEAKER
  - Profiles:
      Profile 0:[dynamic format][dynamic channels][dynamic rates]
...
HW Modules dump:
- HW Module 1:
  - name: primary
  - handle: 10
  - version: 2.0
  - outputs:
    output 0:
    - name: primary output
    - Profiles:
        Profile 0:
            - format: AUDIO_FORMAT_PCM_16_BIT
            - sampling rates:48000
            - channel masks:0x0003
    - flags: 0x0002 (AUDIO_OUTPUT_FLAG_PRIMARY)
    - Supported devices:
      Device 1:
      - id:  2
      - tag name: Speaker
      - type: AUDIO_DEVICE_OUT_SPEAKER
      ...
      Device 7:
      - tag name: HDMI Out
      - type: AUDIO_DEVICE_OUT_AUX_DIGITAL|AUDIO_DEVICE_OUT_HDMI

Порты микшера mixPort описывают разные технологии обработки звуковых данных, которые определяются комбинацией значений в атрибуте flags. Например, в настройках обязательно должен присутствовать единственный приоритетный порт микшера с флагом AUDIO_OUTPUT_FLAG_PRIMARY. Другие варианты портов: AUDIO_OUTPUT_FLAG_DEEP_BUFFER — использование «глубокого» буфера для аудиопотоков, допускающих задержку воспроизведения, AUDIO_OUTPUT_FLAG_DIRECT — направление данных непосредственно на устройство воспроизведения, AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD — обработка упакованных данных (MP3, AAC и других) с помощью аппаратного декодера.

Связи портов микшера с устройствами воспроизведения звука описываются в секции маршрутов <routes>. Для каждого устройства‑приемника sink может быть указано через запятую несколько портов‑источников звука sources.

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

Для устройств воспроизведения звука devicePort и портов микшера mixPort могут быть указаны профили profile, информирующие аудиосервер о способности устройств воспринимать аудиоданные. Профили характеризуются:

  • форматом format, например AUDIO_FORMAT_PCM_16_BIT, AUDIO_FORMAT_PCM_FLOAT, AUDIO_FORMAT_DTS_HD;
  • частотой дискретизации samplingRates, например 32000, 44100, 48000 Гц;
  • количеством каналов channelMasks, например, AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO, AUDIO_CHANNEL_OUT_5POINT1.
Характеристики профиля можно перечислить через запятую или, если допускаются любые возможные значения, вместо них можно указать dynamic.

www​

В исходниках Android 10 можно найти определения перечисленных выше и других возможных констант — в файле Для просмотра ссылки Войди или Зарегистрируйся. Там же есть образец конфига, где показано использование расширенного набора этих констант, — Для просмотра ссылки Войди или Зарегистрируйся.
Если команда dumpsys media.audio_policy выполнена в то время, когда устройство проигрывало звук, то в блоке Outputs dump можно увидеть некоторые сведения об активных аудиопотоках. Однако гораздо больше информации о воспроизводящемся звуке предоставляет команда dumpsys media.audio_flinger:

Код:
Output thread 0xea794240, name AudioOut_D, tid 2032, type 0 (MIXER):
  I/O handle: 13
  Standby: no
  Sample rate: 44100 Hz
  HAL frame count: 960
  HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  HAL buffer size: 3840 bytes
  Channel count: 2
  Channel mask: 0x00000003 (front-left, front-right)
  Processing format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  Processing frame size: 4 bytes
  Pending config events: none
  Output device: 0x2 (AUDIO_DEVICE_OUT_SPEAKER)
  Input device: 0 (AUDIO_DEVICE_NONE)
  Audio source: 0 (AUDIO_SOURCE_DEFAULT)
  ...
  AudioStreamOut: 0xea727d48 flags 0x2 (AUDIO_OUTPUT_FLAG_PRIMARY)
  ...
  1 Tracks of which 1 are active
    Type     Id Active Client Session Port Id S  Flags   Format Chn mask  SRate ST Usg CT  G db  L dB  R dB  VS dB   Server FrmCnt  FrmRdy F Underruns  Flushed   Latency
             65    yes  12662      57      17 A  0x000 00000001 00000001  32000  3   1  3     0     0     0     0  000381F5   8000    8000 A

В начале секции Output thread сообщается о формате аудиоданных, направляемых на устройство воспроизведения Output device (AUDIO_DEVICE_OUT_SPEAKER): PCM16 (AUDIO_FORMAT_PCM_16BIT), стерео (channel count: 2), с частотой дискретизации 44100 Гц (Sample rate: 44100 Hz). А воспроизводимый поток закодирован в PCM16 (Format 01), моно (Chn mask 01), с частотой дискретизации 32000 Гц (SRate 32000).

В выводе команды dumpsys media.audio_flinger много сведений о разных параметрах аудиосистемы. Не все они интуитивно понятны, и мне не попадалась документация с их подробным описанием. Поскольку за формирование всей этой информации отвечают методы AudioFlinger::ThreadBase::dump..., которые определены в файле исходных текстов Android 10 Для просмотра ссылки Войди или Зарегистрируйся, именно к нему следует обращаться за необходимыми разъяснениями. В коде и сопроводительных комментариях помимо ответов на возникающие вопросы можно найти массу информации для размышления. Чего стоят одни лишь блоки FIXME!


Переключение с колонок на цифровой выход​

Первое, что я сделал, — это убрал из конфига упоминания о встроенном микрофоне. Его в приставке нет, но, возможно, попытка его как‑то активировать или использовать приводит к шумам.

<devicePort tagName="Built-In Mic" type="AUDIO_DEVICE_IN_BUILTIN_MIC" role="source" />
Это не помогло. Далее, анализируя информацию о службе Audio Flinger, я заметил такую закономерность: что бы ни воспроизводилось — файлы из аудиоколлекции, потоки интернет‑радио или онлайн телевидения, — на устройство воспроизведения Output device данные всегда передаются в одном и том же формате: PCM16, стерео, 44100 Гц. Само по себе это не плохо, но, может быть, причиной артефактов в звуке становится перекодировка? Вызывает недоумение и тот факт, что в файле audio_policy_configuration.xml присутствуют профили только с частотой дискретизации 48000 Гц. Я попробовал везде заменить 48000 на 44100, но это тоже не оказало никакого эффекта.

Следующий вопрос, который у меня возник: а почему в качестве устройства воспроизведения используется AUDIO_DEVICE_OUT_SPEAKER? Ведь никаких колонок непосредственно к приставке не подключено. В то же время, в перечне устройств <devicePorts> есть HDMI Out, которому соответствует AUDIO_DEVICE_OUT_AUX_DIGITAL, или, что то же самое, AUDIO_DEVICE_OUT_HDMI, — гораздо лучше отражающее действительность. Поэтому я добавил запись <item>HDMI Outitem>в секцию <attachedDevices> и перезагрузил приставку. Звук пропал совсем. Лишь приложив ухо к динамику телевизора можно было услышать тихий шепот.

Команда dumpsys media.audio_flinger показала, что звук теперь направляется на устройство AUDIO_DEVICE_OUT_AUX_DIGITAL. Прекрасная новость!
Код:
Output thread 0xeba93640, name AudioOut_D, tid 2068, type 0 (MIXER):
  ...
  Output device: 0x400 (AUDIO_DEVICE_OUT_AUX_DIGITAL|AUDIO_DEVICE_OUT_HDMI)
  ...
  1 Tracks of which 1 are active
    Type     Id Active Client Session Port Id S  Flags   Format Chn mask  SRate ST Usg CT  G db  L dB  R dB  VS dB   Server FrmCnt  FrmRdy F Underruns  Flushed   Latency
             55    yes   5309      17       7 A  0x000 00000005 00000003  44100  3   1  3   -40     0     0     0  00389A00   3848    2888 A         0        0   unavail
Но внимательный анализ строки о воспроизводимом потоке позволил установить, что уровень звука G (от англ. Gain) составляет -40 дБ, а не 0, как было раньше. И на кнопки регулировки громкости пульта дистанционного управления приставка перестала реагировать. Что ж, попробуем с этим разобраться.

info​

Децибел (дБ) — это числовая величина, равная десяти (деци) десятичным логарифмам отношения двух сравниваемых величин (бел): G = 10 * Lg (P2/P1). В аудиотехнике P1 и P2 — это мощности входного и выходного сигнала соответственно.
Уровень цифровых сигналов принято указывать в децибелах относительно полной шкалы dBFS (deciBels relative to Full Scale), когда измеряется отношение A/Amax, где A — амплитуда оцениваемого сигнала, а Amax — максимально возможная амплитуда. Мощность пропорциональна квадрату амплитуды, поэтому G = 20 * Lg (A/Amax). В таком случае 0 дБ соответствует A = Amax, а все остальные возможные уровни сигнала A описываются отрицательными значениями, поскольку отношение A/Amax < 1. Так, -40 дБ соответствует сигналу, амплитуда которого в 102 = 100 раз меньше максимальной.

Настройка уровня громкости​

В конце конфига audio_policy_configuration.xml есть группа строк, обозначенная как «Секция громкости»:

Код:
<!-- Volume section -->
<xi:include href="audio_policy_volumes_drc.xml"/>
<xi:include href="default_volume_tables.xml"/>

Она призывает обратить внимание на два подключаемых файла: audio_policy_volumes_drc.xml и default_volume_tables.xml.

В файле audio_policy_volumes_drc.xml лежат описания траекторий регулировки громкости (volume) для разных категорий устройств (deviceCategory) при воспроизведении разных аудиопотоков (stream). Например, кривая изменения звука для динамиков DEVICE_CATEGORY_SPEAKER при воспроизведении музыки AUDIO_STREAM_MUSIC описывается четырьмя лежащими на ней точками (<point>):

Код:
<volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_SPEAKER">
    <point>1,-2400</point>
    <point>33,-1200</point>
    <point>66,-600</point>
    <point>100,0</point>
</volume>

Каждая точка представлена двумя значениями: уровнем громкости по шкале процентов (от 0 до 100) и соответствующей ему степенью ослабления сигнала в миллибелах — сотых долях децибелов.

С устройством SPEAKER все более‑менее понятно. Но никаких зацепок для AUX_DIGITAL или HDMI в файле не видно. Обратимся к исходным текстам Android 10. В файле Для просмотра ссылки Войди или Зарегистрируйся определен класс Volume, в котором есть метод, сопоставляющий идентификаторы устройств с их категориями:

Код:
static device_category getDeviceCategory(audio_devices_t device)
{
    switch(getDeviceForVolume(device)) {
    ...
        case AUDIO_DEVICE_OUT_LINE:
        case AUDIO_DEVICE_OUT_AUX_DIGITAL:
        case AUDIO_DEVICE_OUT_USB_DEVICE:
            return DEVICE_CATEGORY_EXT_MEDIA;
        ...
        default:
            return DEVICE_CATEGORY_SPEAKER;
        }
}

Оказывается, устройство AUDIO_DEVICE_OUT_AUX_DIGITAL (оно же HDMI) относится к категории DEVICE_CATEGORY_EXT_MEDIA.

Возвращаемся к файлу audio_policy_volumes_drc.xml и видим, что все теги volume для интересующей нас категории вместо таблиц с точками кривой громкости содержат ссылки на них в атрибуте ref:

Код:
<volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_EXT_MEDIA"
    ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
Сами же таблицы находятся в файле default_volume_tables.xml:

Код:
<reference name="DEFAULT_MEDIA_VOLUME_CURVE">
<!-- Default Media reference Volume Curve -->
    <point>1,-5800</point>
    <point>20,-4000</point>
    <point>60,-1700</point>
    <point>100,0</point>
</reference>

А почему уровень звука для воспроизводимого потока составляет именно -40 дБ? Узнаем, какой уровень громкости установлен на приставке:

Код:
media volume --stream 3 --get
[v] will control stream=3 (STREAM_MUSIC)
[v] will get volume
[v] Connecting to AudioService
[v] volume is 3 in range [0..15]

Если отобразить значение 3 из шкалы уровней громкости 0...15 на шкалу процентов, то получим 3 / 15 × 100 = 20, что попадает прямо на вторую строчку таблицы точек кривой громкости и соответствует значению -4000 мБ = -40 дБ.

Кривая громкости DEFAULT_MEDIA_VOLUME_CURVE
Кривая громкости DEFAULT_MEDIA_VOLUME_CURVE

Изменить установленный уровень громкости ни через интерфейс Android, ни из командной строки мне не удалось. Поэтому я просто в файле audio_policy_volumes_drc.xml для всех тегов с категорией устройства DEVICE_CATEGORY_EXT_MEDIA прописал ссылку на таблицу FULL_SCALE_VOLUME_CURVE, которая без вариантов задает максимальный уровень громкости:

Код:
<reference name="FULL_SCALE_VOLUME_CURVE">
<!-- Full Scale reference Volume Curve -->
    <point>0,0</point>
    <point>100,0</point>
</reference>

После перезагрузки звук в режиме AUX_DIGITAL/HDMI появился, но его качество не стало лучше. Зато теперь регулировать громкость можно только на телевизоре, не трогая приставку, и это здорово.


Уровень ALSA​


Изучение документации​

Несмотря на определенный прогресс в настройке приставки, на уровне аудиосервера решить проблему с потрескиванием мне не удалось. Тогда я решил погрузиться на самое дно и посмотреть, как выглядит ситуация с точки зрения ALSA. К сожалению, в открытом доступе спецификации чипа Allwinner H313 не нашлось. Производитель ограничился выпуском брошюры, из которой можно узнать, что в звуковой подсистеме этого чипа имеются:

  • Audio Codec c двумя каналами DAC (ЦАП) и одним аудиовыходом;
  • DMIC — цифровой микрофон;
  • OWA OUT — однопроводная шина для передачи аудиоданных;
  • Audio HUB — звуковой концентратор, поддерживающий три канала I2S/PCM.
Не густо. Но аналогичная брошюра об изделии Allwinner H616 выглядит как брат‑близнец, и отличия обнаруживаются только в характеристиках видеоподсистемы. А спецификацию на этот чип найти оказалось проще. Ее можно, например, загрузить из раздела Download по ссылке Datasheet официального сайта Orange Pi Zero2.

www​

В разделе «Chapter 8 Audio» этого руководства подробнейшим образом расписаны все элементы звуковой подсистемы, которая реализована в чипе. На основе приведенной в руководстве иллюстрации я подготовил немного адаптированный рисунок одного из возможных вариантов ее использования.

Вариант использования звуковой подсистемы в режиме караоке из руководства по Allwinner H616
Вариант использования звуковой подсистемы в режиме караоке из руководства по Allwinner H616

Здесь изображен интерфейс внутренней шины APBIF (Advanced Peripheral Bus InterFace), через три канала DMA TX FIFO которого сигнал от цифрового микрофона, музыкальный трек и системные оповещения поступают в звуковой концентратор AHUB (Audio HUB). Концентратор направляет аудиопотоки в цифровой аудиомикшер DAM0 (Digital Audio Mixer), где они смешиваются и передаются через первый из трех каналов DMA RX FIFO на шину OWA (One Wire Audio), а сигнал от цифрового микрофона отдельно выводится через второй канал. Кроме того, объединенный аудиопоток из микшера направляется на шины I2S0 и I2S1 телевизионных выходов CVBS и HDMI.


Исследование /proc/asound​

Но это все теория, а как используется чип H313 в приставке X96Q PRO? Знакомство с реализацией подсистемы ALSA я начал с изучения структуры каталогов /proc/asound. Вот как она выглядит:

Код:
/proc/asound/
    card0 <- audiocodec
        pcm0c,pcm0p — SUNXI-CODEC sun50iw9-codec-0
    card1 <- sndspdif
        pcm0c,pcm0p — SUNXI-SPDIF spdif-hifi-0
    card2 <- sndahub
        pcm0c,pcm0p — Media Stream sunxi-ahub-aif1-0
        pcm1c,pcm1p — System Stream sunxi-ahub-aif2-1
        pcm2c,pcm2p — Accompany Stream sunxi-ahub-aif3-2
    card3 <- sndhdmi
        pcm0p — SUNXI-HDMIAUDIO audiohdmi-dai-0
    card4 <- sndaudio0
        pcm0c,pcm0p — SUNXI-AUDIO snd-soc-dummy-dai-0

Интерес для нас представляют «карты» (в терминологии ALSA) 0, 2 и 3, которые изображают устройства Audio Codec, Audio HUB и выход HDMI соответственно. Каталоги pcmNc и pcmNp соответствуют «устройствам» (в терминологии ALSA) захвата (capture) и воспроизведения (playback) звука соответственно.

Когда приставка воспроизводит звук, информация о состоянии активных устройств появляется в файлах status, sw_params и hw_params их подкаталогов. Вот какие устройства были задействованы при разных настройках звука в Android:

Настройка/Устройства ALSAcard0/pcm0pcard2/pcm0pcard3/pcm0p
AUDIO_CODEC + AUDIO_HDMI+++
AUDIO_HDMI-++
AUDIO_HDMI + Passthrough-++
В таблице символ «плюс» означает, что устройство используется, а «минус» — что нет. Конфигурацию используемого устройства можно подсмотреть в файле hw_params. В моем случае для всех активных устройств его содержимое было одинаковым, какие бы варианты медиаданных я ни воспроизводил:

Код:
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 44100 (44100/1)
period_size: 960
buffer_size: 1920

Терминология ALSA​

Для понимания содержимого файлов hw_params надо усвоить некоторые Для просмотра ссылки Войди или Зарегистрируйся.
  • Семпл (sample) — это данные об отдельном измерении звука, например, отклонении мембраны микрофона или динамика от равновесного состояния. В режиме PCM16 они представлены 16 битами или двумя байтами.
  • Фрейм (frame) — это набор семплов, достаточный для описания звуковой системы в определенный момент времени. Состояние стереосистемы определяется семплами левого и правого каналов, в режиме PCM16 это уже четыре байта. При частоте дискретизации 44100 Гц скорость потока звуковых данных для стереосистемы составляет 44100 Гц × 4 байта = 176 400 байт/с.
  • Период (period) — это порция данных, которую ALSA загружает в аппаратный буфер звукового устройства. В нашем примере используется период размером period_size 960 фреймов или 3840 байт, которые будут воспроизведены примерно за 21,8 мс. Размер буфера buffer_size позволяет хранить два периода: один воспроизводится, другой готовится. Поэтому за 43,6 мс будут сгенерированы два аппаратных прерывания для подгрузки данных.
Аудиопоток может поступать в разных форматах. INTERLEAVED — каждый фрейм содержит семплы всех каналов (двух для стереосистемы), а период — это последовательность фреймов. NONINTERLEAVED нарушает фреймовую структуру: период представлен сначала семплами первого канала, потом — второго и так далее.

Применение утилиты tinymix​

Информацию другого рода об уровне ALSA можно получить с помощью утилиты tinymix. Например, следующие команды выводят сведения об «элементах управления» компонентов ALSA и их возможных значениях:

Код:
tinymix -D 0 -a > tinymix_0.txt
tinymix -D 1 -a > tinymix_1.txt
...
tinymix -D 4 -a > tinymix_4.txt

Вот что получилось в файле tinymix_0.txt:

Код:
Mixer name: 'audiocodec'
Number of controls: 16
ctl type    num name                                     value
    range/values
0   ENUM    1   codec hub mode                           null >hub_disable hub_enable
1   INT 1   digital volume                           0 (dsrange 0->63)
2   INT 1   LINEIN to output mixer gain control      3 (dsrange 0->7)
3   INT 1   FMIN to output mixer gain control        3 (dsrange 0->7)
4   INT 1   LINEOUT volume                           31 (dsrange 0->31)
5   BOOL    1   LINEOUT Switch                           On
6   BOOL    1   Left Output Mixer DACL Switch            On
7   BOOL    1   Left Output Mixer DACR Switch            Off
8   BOOL    1   Left Output Mixer FMINL Switch           Off
9   BOOL    1   Left Output Mixer LINEINL Switch         Off
10  BOOL    1   Right Output Mixer DACL Switch           Off
11  BOOL    1   Right Output Mixer DACR Switch           On
12  BOOL    1   Right Output Mixer FMINR Switch          Off
13  BOOL    1   Right Output Mixer LINEINR Switch        Off
14  ENUM    1   Left LINEOUT Mux                         >LOMixer LROMixer
15  ENUM    1   Right LINEOUT Mux                        >ROMixer LROMixer

Здесь перечислены элементы управления карты Audio Codec, представленные шестнадцатью переменными. Их значения можно изменять так:

Код:
tinymix -D 0 'digital volume' 63
tinymix -D 0 'LINEOUT Switch' 0
tinymix -D 0 'Left LINEOUT Mux' LROMixer

Для установки переменных типа BOOL надо использовать цифры 1 (Вкл/On) или 0 (Откл/Off). Переменные типа INT задаются числом из допустимого диапазона dsrange, а переменные типа ENUM — указанием конкретного значения из набора возможных.

www​

Я извлек двоичный файл tinymix из архива Для просмотра ссылки Войди или Зарегистрируйся. Сам модуль устанавливать не стал, потому что его оформление соответствует устаревшим версиям Magisk. Кстати, команду tinymix надо выполнять с правами суперпользователя.
Чтобы исключить возможные помехи для воспроизводимого звука, я попробовал уменьшить до нуля уровень сигналов от линейного входа «LINEIN to output mixer gain control» и FM-радио «FMIN to output mixer gain control». Это не повлияло на качество звука, так же, как и увеличение громкости цифрового звука (digital volume) не подействовало на звук в режиме HDMI. А отключение линейного выхода LINEOUT Switch приводило к остановке воспроизведения медиапотока. Видимо, драйвер информировал приложение медиапроигрывателя о возникновении проблемы.

Еще большее поле для настроек имеет карта 2 — Audio HUB:

Код:
Mixer name: 'sndahub'
Number of controls: 26
ctl type    num name                                     value
    range/values
0   ENUM    1   ahub audio format Function               null >pcm AC3 MPEG1 MP3 MPEG2 AAC DTS ATRAC ONE_BIT_AUDIO DOLBY_DIGITAL_PLUS DTS_HD MAT WMAPRO
1   BOOL    1   I2S0IN Switch                            Off
2   BOOL    1   I2S0OUT Switch                           Off
3   BOOL    1   I2S1IN Switch                            Off
4   BOOL    1   I2S1OUT Switch                           On
5   BOOL    1   I2S2IN Switch                            Off
6   BOOL    1   I2S2OUT Switch                           On
7   BOOL    1   I2S3IN Switch                            Off
8   BOOL    1   I2S3OUT Switch                           Off
9   BOOL    1   DAM0IN Switch                            Off
10  BOOL    1   DAM1IN Switch                            Off
11  BOOL    1   DAM0OUT Switch                           Off
12  BOOL    1   DAM1OUT Switch                           Off
13  ENUM    1   APBIF0 Src Select                        >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
14  ENUM    1   APBIF1 Src Select                        >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
15  ENUM    1   APBIF2 Src Select                        >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
16  ENUM    1   I2S0 Src Select                          >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
17  ENUM    1   I2S1 Src Select                          NONE >APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
18  ENUM    1   I2S2 Src Select                          NONE APBIF_TXDIF0 APBIF_TXDIF1 >APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
19  ENUM    1   I2S3 Src Select                          >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
20  ENUM    1   DAM0Chan0 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
21  ENUM    1   DAM0Chan1 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
22  ENUM    1   DAM0Chan2 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
23  ENUM    1   DAM1Chan0 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
24  ENUM    1   DAM1Chan1 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF
25  ENUM    1   DAM1Chan2 Src Select                     >NONE APBIF_TXDIF0 APBIF_TXDIF1 APBIF_TXDIF2 I2S0_TXDIF I2S1_TXDIF I2S2_TXDIF I2S3_TXDIF DAM0_TXDIF DAM1_TXDIF

Управление аудиоконцентратором​

Обнаружив в названиях переменных знакомые по даташиту аббревиатуры аудиоконцентратора, я решил построить реальную схему работы ТВ‑приставки. По состояниям элементов управления видно, что:

  • активными являются выходы I2S1OUT и I2S2OUT;
  • источники данных для I2S1 и I2S2 — это каналы APBIF_TXFDIF0 и APBIF_TXDIF2 соответственно.
Чтобы определить назначения выходов, я попробовал их отключать.

tinymix -D 2 'I2S2OUT Switch' 0
Звук на телевизоре по‑прежнему есть, как и те артефакты, на борьбу с которыми направлены наши усилия.

tinymix -D 2 'I2S1OUT Switch' 0
Звук пропал, и воспроизведение в приложении медиаплеера остановилось. Через несколько секунд звук появился снова, а состояние выхода из Off автоматически переключилось на On. Вероятно, драйвер обнаружил проблему и самостоятельно ее исправил. Таким образом, можно с большой уверенностью заявить, что выход I2S1 соответствует HDMI.

А к чему привязан I2S2? Может быть, к CVBS? Я попробовал направить источник APBIF_TXDIF2 на I2S0:

tinymix -D 2 'I2S1 Src Select' APBIF_TXDIF2
Звук снова пропал, как будто выход отключили. Получается, что из APBIF_TXDIF2 не поступает никаких аудиоданных, поэтому как его назначение, так и назначение I2S2 остаются под вопросом. Когда воспроизведение автоматически восстанавливается, в журнал logcat записываются такие строки:

Код:
D audio_hw_primary: enable_ahub_hdmi_out
D audio_hw_primary: enable_ahub_bt_out
Может быть, неработающие элементы управления относятся к ahub_bt_out? Как бы то ни было, с учетом полученной информации я нарисовал такую схему работы ТВ‑приставки.

Реальная схема работы звуковой подсистемы ТВ-приставки X96Q PRO
Реальная схема работы звуковой подсистемы ТВ‑приставки X96Q PRO

Как еще можно попробовать устранить проблемы со звуком? Изменение «ahub audio format Function» с pcm на DTS, DOLBY_DIGITAL_PLUS и даже null не возымело никакого эффекта. Тогда я решил напоследок переконфигурировать Audio HUB так, чтобы звуковой поток проходил через микшер DAM0. Это приблизило бы работу звуковой подсистемы к эталонному примеру из даташита.

Экспериментальная схема работы звуковой подсистемы ТВ-приставки X96Q PRO
Экспериментальная схема работы звуковой подсистемы ТВ‑приставки X96Q PRO

Для реализации своего замысла я выполнил следующие команды:

Код:
tinymix -D 2 'I2S2OUT Switch' 0
tinymix -D 2 'I2S2 Src Select' NONE
tinymix -D 2 'DAM0Chan0 Src Select' APBIF_TXDIF0
tinymix -D 2 'DAM0IN Switch' 1
tinymix -D 2 'DAM0OUT Switch' 1
tinymix -D 2 'I2S1 Src Select' DAM0_TXDIF

Звук по новой схеме заработал как ни в чем ни бывало, только потрескивания и пощелкивания из него не исчезли. Нужны какие‑то новые идеи.


Поиск свежих идей​

Я временно перенес поиски решения в интернет и наткнулся на Для просмотра ссылки Войди или Зарегистрируйся работы USB Creative SoundBlaster MP3+ на Raspberry Pi.

Участник, инициировавший дискуссию, отмечает зависимость качества воспроизводимого звука от комбинации параметров period_size/buffer_size, которыми инициализируется устройство ALSA. При некоторых значениях наблюдаются дефекты, которые автор сообщения назвал «заиканием» (shuttering).

В ответах другие пользователи сообщают, что проблема может заключаться в выполнении принудительного внутреннего преобразования частоты дискретизации из 48000 Гц к 44100 Гц при прохождении аудиопотока по элементам звуковой подсистемы.

Это обсуждение побудило меня продолжить поиски в направлении параметров цифрового звука. Я вспомнил о том, что в audio_policy_configuration.xml везде прописаны значения 48000, а все аудиопотоки воспроизводятся с частотой дискретизации 44100. Но, оглядываясь назад, я не смог припомнить никаких возможностей каким‑то образом повлиять на эту ситуацию. Кроме того, я тестировал воспроизведение медиаданных, которые были закодированы с разными частотами дискретизации: 16000, 22050, 24000, 32000, 44100 и 48000, — и все они имели дефекты звучания.

В каталоге /vendor/etc/ нашелся файл audio_platform_info.xml, содержащий параметры для инициализации звуковых устройств:

Код:
...
<!-- main output profile -->
<platform_devices_profile>
    <platform_devices devices="OUT_SPK|OUT_EAR|OUT_HP|OUT_SPK_AND_HP|OUT_DULSPK|OUT_DULSPK_HP"/>
    <snd_card_config
        type="frontend" card_name="audiocodec" device="0"
        channels="2" rate="48000" period_size="1024" period_count="2"
    />
</platform_devices_profile>
...
<!-- hdmi -->
<platform_devices_profile>
    <platform_devices devices="OUT_HDMI"/>
    <snd_card_config
        type="frontend" card_name="sndhdmi" device="0"
        channels="2" rate="48000" period_size="512" period_count="2"
    />
</platform_devices_profile>
...

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

Наверное, пришло время вступить в самую темную область звуковой подсистемы Android и заняться изучением библиотеки промежуточного слоя Audio HAL. Почему я так мрачно высказываюсь об этой части звуковой подсистемы? Потому что остальные ее компоненты неплохо изучены и документированы, а вот HAL — это прерогатива производителя конкретного устройства, которая обычно сопровождается всякими словами типа сonfidential, и мне бы очень не хотелось во все это погружаться. Но по всем законам сказочного жанра, чему быть, того не миновать.


Уровень слоя абстракции Audio HAL​


Исследование файла audio.primary.cupid.so​

Что нам известно о промежуточном слое Audio HAL и вообще, где его искать? Для просмотра ссылки Войди или Зарегистрируйся сообщает, что библиотека HAL должна иметь имя audio.primary.<device>.so. На приставке нашлись два файла, отвечающих этому требованию: /vendor/lib/hw/audio.primary.default.so и /vendor/lib/hw/audio.primary.cupid.so. Наибольший интерес представляет второй файл, потому что именно он реализует функции, специфичные для нашего устройства. Изучим его подробнее.

Посмотрим, какие аудиоустройства обслуживает библиотека HAL audio.primary.cupid.so:

Код:
strings audio.primary.cupid.so | grep AUDIO_DEVICE_
start_output_stream, AUDIO_DEVICE_OUT_SPEAKER
start_output_stream AUDIO_DEVICE_OUT_DGTL_DOCK_HEADSET
start_output_stream AUDIO_DEVICE_OUT_AUX_DIGITAL

Аналогичным образом мы можем посмотреть, какие системные настройки ей известны:

Код:
strings audio.primary.cupid.so | grep vendor
vendor.audio.input.active
vendor/etc/ac100_paths.xml
vendor.audio.output.active
vendor.mediasw.sft.rawdata

Присваивание свойству vendor.mediasw.sft.rawdata значения с помощью команды setprop на воспроизведение звука не оказывает влияния, потому что значение как этого свойства, так и свойства vendor.audio.output.active переустанавливаются в соответствии с настройками звука в Android.


Настройки в ac100_paths.xml​

Интересен случайно попавший в выборку файл /vendor/etc/ac100_paths.xml. Как оказалось, он используется библиотекой вместо audio_mixer_paths.xml. В файле ac100_paths.xml содержится описание параметров для устройств ALSA. Эти параметры выставляются для разных режимов работы аудиоподсистемы.

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

Код:
D audio_route: Apply path: media-speaker-headphones
D audio_route: Apply path: media-headset-mic

Режиму media-speaker-headphones соответствует вот эта часть конфига:

Код:
<mixer>
  ...
  <path name="media-speaker-headphones">
    <path name="dac-mixer" />
    <ctl name="Left Output Mixer DACL Switch" value="1" />
    <ctl name="Left Output Mixer DACR Switch" value="0" />
    <ctl name="Right Output Mixer DACL Switch" value="0" />
    <ctl name="Right Output Mixer DACR Switch" value="1" />
    <ctl name="LINEOUT volume" value="31" />
    <ctl name="LINEOUT Switch" value="1" />
  </path>
  ...
</mixer>

Сопоставив этот фрагмент со схемой звуковой подсистемы Allwinner H616 и режимами работы компонента Audio Codec, который мы исследовали на уровне ALSA, ты без особого труда увидишь, как применяются настройки из этого файла. Но мы по‑прежнему не знаем, где устанавливаются параметры цифрового звука.

Чип X-Powers​

У тебя не возник вопрос, что означает «AC100» в имени файла? Я тоже этим заинтересовался и нашел в сети Для просмотра ссылки Войди или Зарегистрируйся о микросхеме с таким наименованием.
Оказалось, она представляет собой высокоинтегрированную аудиоподсистему производства X-Powers и предназначена для использования в Wi-Fi колонках, планшетах и смартфонах. Ее описание перекликается с тем, что мы видели в руководстве по Allwinner H616, но принцип работы и управляющие параметры здесь рассмотрены более подробно.
Из Для просмотра ссылки Войди или Зарегистрируйся на микросхему можно понять (и простить), почему для основного канала вывода звука в модуле HAL был выбран странный для телевизионной приставки идентификатор media-speaker-headphones.
Что еще мы можем предпринять? Давай посмотрим, что записывается в журнал logcat при старте воспроизведения медиапотока с частотой дискретизации 32000 Гц приложением Для просмотра ссылки Войди или Зарегистрируйся.

Код:
V mpv     : [af:v] [out] 32000Hz stereo 2ch s16
V mpv     : [cplayer:v] audio ready
V mpv     : [cplayer:v] starting audio playback
V mpv     : [cplayer:v] playback restart complete @ 0.000000, audio=playing, video=eof
D audio_hw_primary: start_output_stream   out->format : 0x00000000
V audio_hw_primary: start_output_stream, line: 1654
D audio_route: Apply path: media-speaker-headphones
D audio_route: Apply path: media-headset-mic
V audio_hw_primary: use AUDIO_CODEC to playback audio
D audio_hw_primary: open card=0, port=0
D audio_hw_primary: config:2 44100 960 2 0 5760 1 0
D audio_hw_primary: set_raw_flag(card=0, raw_flag=1)
D audio_hw_primary: set_raw_flag(card=2, raw_flag=1)
D audio_hw_primary: set_raw_flag control_name = ahub audio format Function)
V audio_hw_primary: do not use out resampler
V audio_hw_primary: use AUDIO_HDMI to playback audio
D audio_hw_primary: open card=3, port=0
D audio_hw_primary: config:2 44100 960 2 0 5760 1 0
D audio_hw_primary: set_raw_flag(card=3, raw_flag=1)
D audio_hw_primary: set_raw_flag control_name = hdmi audio format Function)
D audio_hw_primary: set_raw_flag(card=2, raw_flag=1)
D audio_hw_primary: set_raw_flag control_name = ahub audio format Function)
V audio_hw_primary: do not use out resampler
D audio_hw_primary: enable_ahub_hdmi_out
D audio_hw_primary: enable_ahub_bt_out

Строки с пометкой audio_hw_primary записаны библиотекой Audio HAL. Из этого отрывка видно, что, несмотря на частоту воспроизводимых аудиоданных 32000 Гц и значения 48000 Гц во всех настройках, библиотека HAL инициализирует устройства AUDIO_CODEC и AUDIO_HDMI значением 44100 Гц. Я решительно не понимаю, почему так происходит, поэтому решил исследовать код библиотеки HAL. Исходников мне найти не удалось, пришлось воспользоваться дизассемблером.


Дизассемблирование библиотеки HAL​

Для дизассемблирования понадобится утилита objdump, понимающая машинный код процессоров ARM. Поэтому в Ubuntu установим пакет binutils-arm-linux-gnueabihf:

sudo apt install binutils-arm-linux-gnueabihf
Дизассемблируем библиотеку audio.primary.cupid.so:

arm-linux-gnueabihf-objdump -D audio.primary.cupid.so > audio.primary.cupid.so.dasm
Теперь выполним поиск по значению 44100 в полученном листинге:

grep '44100' audio.primary.cupid.so.dasm
Ответом стали 18 строк с командами загрузки числа 44100 в разные регистры:


Код:
51cc:   f64a 4044   movw    r0, #44100  ; 0xac44
    52cc:   f64a 4344   movweq  r3, #44100  ; 0xac44
    6356:   f64a 4144   movw    r1, #44100  ; 0xac44
    63b4:   f64a 4044   movweq  r0, #44100  ; 0xac44
    6d06:   f64a 4244   movw    r2, #44100  ; 0xac44
    7196:   f64a 4044   movw    r0, #44100  ; 0xac44
    72d0:   f64a 4044   movw    r0, #44100  ; 0xac44
    74b8:   f64a 4144   movweq  r1, #44100  ; 0xac44
    74de:   f64a 4044   movw    r0, #44100  ; 0xac44
    7508:   f64a 4344   movw    r3, #44100  ; 0xac44
    7522:   f64a 4344   movw    r3, #44100  ; 0xac44
    762e:   f64a 4044   movw    r0, #44100  ; 0xac44
    7724:   f64a 4144   movw    r1, #44100  ; 0xac44
    772c:   f64a 4244   movwne  r2, #44100  ; 0xac44
    798a:   f64a 4044   movw    r0, #44100  ; 0xac44
    79a6:   f64a 4044   movw    r0, #44100  ; 0xac44
    79c0:   f64a 4344   movw    r3, #44100  ; 0xac44
    7a9e:   f64a 4344   movw    r3, #44100  ; 0xac44

Наиболее примечательный участок кода выглядит так:


Код:
7186:   f44f 7070   mov.w   r0, #960    ; 0x3c0
    718a:   f8c8 00a0   str.w   r0, [r8, #160]  ; 0xa0
    718e:   f44f 60f0   mov.w   r0, #1920   ; 0x780
    7192:   f8c8 0090   str.w   r0, [r8, #144]  ; 0x90
    7196:   f64a 4044   movw    r0, #44100  ; 0xac44
    719a:   f8c8 0080   str.w   r0, [r8, #128]  ; 0x80

Похоже, что он записывает десятичные числа 960, 1920 и 44100 в структуру, на которую указывает содержимое регистра r8. А где нам встречались эти значения? В файле, отражающем настройки звуковой подсистемы драйверов Linux /proc/asound/card3/pcm0p/sub0/hw_params. Видимо, именно здесь происходит подготовка этой подсистемы к воспроизведению.

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


Исправление библиотеки HAL​

Как же исправить двоичный файл библиотеки HAL, чтобы реализованные в нем функции ориентировали компоненты ALSA на работу с частотой дискретизации 48000 Гц? Ассемблера для архитектуры ARM я не знаю, поэтому не могу проанализировать логику работы соответствующих участков библиотеки, чтобы локализовать конкретные команды, требующие изменения.

Кроме того, у меня было настроение Бармалея, который в ответ на замечание главного героя кинофильма «Айболит-66» о том, что нехорошо обманывать маленькую обезьянку, нетерпеливо возразил: «Я не могу ждать, пока она вырастет!». Поэтому я принял авантюрное решение: заменить значение 44100 на 48000 во всех командах mov, где оно встречается.

С одной стороны, вероятно, существуют участки кода, в которых значение 44100 используется не для инициализации звуковой подсистемы, а, например, для сравнения с параметром предоставленного к воспроизведению аудиопотока. С другой стороны, если раньше при инициализации звуковой подсистемы всегда использовалось значение 44100, то сейчас вместо него будет стоять 48000.

Я бы ни за что не стал общим чохом вслепую менять в банковском приложении 100 на 150, если бы вышел закон о новой пороговой сумме каких‑то операций. Потому что 100 может быть как величиной этой суммы, так и, например, коэффициентом перевода рублей в копейки. Да и финансовая сфера — совсем не та область, в которой допустимы подобные легкомысленные решения.

Но 44100 — слишком уж характерная величина, которую сложно с чем‑нибудь перепутать. Кроме того, маловероятно, что частота дискретизации явно влияет на выделение буферов (иначе такое грубое вмешательство могло бы привести к утечкам памяти). Ну и в самом худшем случае ущерб ограничится неисправной телевизионной приставкой — потеря не так велика.

В этот момент последовательный рассудительный инженер уходит со сцены, и в лучах софитов на ней появляется хакер!

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

Код:
grep '#48000' audio.primary.cupid.so.dasm
    634e:   f64b 3180   movw    r1, #48000  ; 0xbb80
    7002:   f64b 3380   movw    r3, #48000  ; 0xbb80
    7028:   f64b 3380   movw    r3, #48000  ; 0xbb80
    7734:   f64b 3180   movw    r1, #48000  ; 0xbb80
    773c:   f64b 3280   movwne  r2, #48000  ; 0xbb80

Сопоставив шестнадцатеричные машинные коды с мнемоническими записями команд, можно сделать вывод, что для замены команд movw r?, #44100 на movw r?, #48000 надо выполнить такие подстановки:

  • 4BF6 8030 вместо 4AF6 4440,
  • 4BF6 8031 вместо 4AF6 4441,
  • 4BF6 8032 вместо 4AF6 4442,
  • 4BF6 8033 вместо 4AF6 4443.
Помимо этого, значение 44100 (0x44AC в шестнадцатеричном представлении Little Endian) трижды встречается в сегменте данных. Его тоже изменим на 48000 (0x80BB в шестнадцатеричном представлении Little Endian).

Полная таблица исправлений для файла audio.primary.cupid.so с контрольной суммой MD5 daa172745b41d0db7e7e03bdaae4796b будет выглядеть следующим образом:

Код:
0x51CC: 4A F6 44 40  -> 4B F6 80 30
0x52CC: 4A F6 44 43  -> 4B F6 80 33
0x6356: 4A F6 44 41  -> 4B F6 80 31
0x63B4: 4A F6 44 40  -> 4B F6 80 30
0x6D06: 4A F6 44 42  -> 4B F6 80 32
0x7196: 4A F6 44 40  -> 4B F6 80 30
0x72D0: 4A F6 44 40  -> 4B F6 80 30
0x74B8: 4A F6 44 41  -> 4B F6 80 31
0x74DE: 4A F6 44 40  -> 4B F6 80 30
0x7508: 4A F6 44 43  -> 4B F6 80 33
0x7522: 4A F6 44 43  -> 4B F6 80 33
0x762E: 4A F6 44 40  -> 4B F6 80 30
0x7724: 4A F6 44 41  -> 4B F6 80 31
0x772C: 4A F6 44 42  -> 4B F6 80 32
0x798A: 4A F6 44 40  -> 4B F6 80 30
0x79A6: 4A F6 44 40  -> 4B F6 80 30
0x79C0: 4A F6 44 43  -> 4B F6 80 33
0x7A9E: 4A F6 44 43  -> 4B F6 80 33
0xC008: 44 AC  -> 80 BB
0xC038: 44 AC  -> 80 BB
0xC068: 44 AC  -> 80 BB

Контрольная сумма MD5 исправленного файла — c58c080cca142114dcd840d0d94f66a7.


Испытание исправленной библиотеки​

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

Звук приставки стал если не идеальным, то вполне приемлемым. Из аудиосопровождения телевизионных программ исчезли «царапающие» щелчки и запинания, постепенно ушло и нервное напряжение, связанное с ожиданием проявления очередного дефекта.

Проверяю параметры аудиопотока с помощью команды dumpsys media.audio_flinger:

Output thread 0xeb193880, name AudioOut_D, tid 2114, type 0 (MIXER):
Код:
I/O handle: 13
  Standby: no
  Sample rate: 48000 Hz
  HAL frame count: 896
  HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  HAL buffer size: 3584 bytes
  Channel count: 2
  Channel mask: 0x00000003 (front-left, front-right)
  Processing format: 0x5 (AUDIO_FORMAT_PCM_FLOAT)
  ...
  1 Tracks of which 1 are active
    Type     Id Active Client Session Port Id S  Flags   Format Chn mask  SRate ST Usg CT  G db  L dB  R dB  VS dB   Server FrmCnt  FrmRdy F Underruns  Flushed   Latency
             56    yes   6665      25       9 A  0x000 00000005 00000001  44100  3   1  2     0     0     0     0  00081F97   6372    3197 A         0        0   unavail

Она показывает, что исходные аудиоданные с частотой дискретизации 44100 Гц преобразуются для передачи на аудиовыход с частотой дискретизации 48000 Гц. Но неожиданно для меня изменились значения HAL frame count: 896 вместо 960 и HAL buffer size: 3584 вместо 3840. А что произошло на уровне ALSA? Содержимое файла /proc/asound/card3/pcm0p/sub0/hw_params подтверждает изменение частоты дискретизации выходного потока, при этом величины period_size и buffer_size остались прежними:

Код:
access: RW_INTERLEAVED
format: S16_LE
subformat: STD
channels: 2
rate: 48000 (48000/1)
period_size: 960
buffer_size: 1920

Может быть, изменившееся на уровне аудиосервера значение HAL frame count привело к тому, что операции заполнения аппаратного и программного буферов аудиоданных разнеслись по времени, в результате чего устранились накладки, которые происходили раньше?

Как бы то ни было, многочасовая эксплуатация телевизионной приставки с измененной библиотекой Audio HAL не выявила никаких побочных эффектов. Разнообразные медиапотоки воспроизводились с хорошим качеством звука и не вызывали сбоев.

В качестве основного устройства воспроизведения можно вернуть AUDIO_DEVICE_OUT_SPEAKER, если отсутствие возможности регулировки уровня громкости для AUDIO_DEVICE_OUT_HDMI вызывает дискомфорт — это не испортит достигнутого результата.


Заключение​

Мы проделали долгий путь, в ходе которого познакомились со всеми уровнями звуковой подсистемы Android 10, изменили устройство вывода звука с AUDIO_DEVICE_OUT_SPEAKER на AUDIO_DEVICE_OUT_HDMI, выставили необходимый уровень громкости и, в конце концов, исправили библиотеку Audio HAL так, чтобы аудиопоток выводился с частотой дискретизации 48000 Гц, а не 44100 Гц.

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

Ради интереса я проверил состав дистрибутивов Android 10 для некоторых других устройств, построенных на платформе Allwinner H313/H616. Оказалось, что файл библиотеки Audio HAL от телевизионной приставки X96Q PRO ничем не отличается от тех, которые поставляются в дистрибутивах для Orange Pi Zero2 и Tanix TX6S.

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

Печально, но факт: причина проблем крылась в софте приставки. Похоже, ему в сфере массового производства уделяют все меньше внимания. В результате даже качественное «железо» не всегда может раскрыть свои лучшие стороны.
 
Activity
So far there's no one here
Сверху Снизу