20 07 2026
Auro-3D: как в обычном PCM прячут высотные каналы
Пока все спорят «Atmos — маркетинг или нет», рядом тихо живёт другой 3D-звук — Auro-3D. Без «объектов вокруг головы», зато с честными высотными каналами звука и очень хитрой упаковкой в обычный многоканальный PCM.
Я давно интересуюсь звуком, писал обзоры по железу, писал про Dolby Atmos. В том числе интересовался Auro 3D, который был представлен бельгийской компанией Galaxy Studios в 2006 году и назывался Octopus. В 2011 стал доступен в потребительских устройствах, а в 2019 был представлен новый формат Auro-Cx, который кроме канального кодирования может использовать и объектное, как и Dolby Atmos и DTS:X

Скачав несколько демо роликов и образов дисков, я обнаружил лишь звуковые дорожки в формате DTS HD MA (или многоканальный Flac) классической раскладки 5.1\7.1, а популярная утилита MediaInfo ничего не показывала про высотные каналы, тогда как железный ресивер (с нужным декодером) эти дорожки показывал как Auro 9.1 или 13.1 и, если приложить ухо, высотные каналы звучали только на правильных верхних колонках и не звучали внизу. Ресиверы без декодера Auro озвучивал все каналы внизу.
Тогда я начал искать официальную документацию, но большинство ссылок были устаревшими, а новые вели лишь на маркетинговое описание формата. Но webarchive и другие файлпомойки мне помогли и я все таки нашел старое описание декореда. Ссылка на доки в конце статьи.
Оттуда я узнал что Auro 3D встраивает информацию в нижние биты 24 битного PCM сигнала. Нейронки в 2025 мало что знали о формате или только информацию из wiki. Тогда я декодировал с помощью ffmpeg звук до многоканального PCM 24bit, быстренько накидал на python скрипт, который из 24 битного PCM извлекал фиксированные верхние 4 бита и тогда я воочию увидел метаданные Auro, вот пример тишины, но содержащие магические биты информации:

Эти блоки повторялись (внутри были и постоянные данным и меняющиеся), а потом и смешивались с реальными звуковыми сигналами (их паттерн все еще можно было разглядеть в потоке двоичного кода). Центральный канал и LFE могли не содержать эту мету. Угадать, что за что какой бит отвечает, было нереально.
Потом начал искать декодер, приложения и официальный декодер платный, а все что удалось скачать — зашифровано\обфусцировано. Те прошивки ресиверов которые удавалось расковырять, декодер auro был в формате для специализированных DSP. И наконец мне улыбнулась удача и я нашел декодер в ELF, да и еще с сохранёнными debug символами. И началась работа над реверс инжинирингом, сначала над детектором layout, а потом и до полноценного декодера.
Я долго ковырял этот формат изнутри (примерно год) и таки раскопал + помогли LLM:
- где и как лежит мета;
- какой битрейт у меты;
- как высоты вытаскивают обратно из нижнего слоя и теряется ли качество после dematrix.
Сначала немного теории
Auro-3D — канальный иммерсивный формат: нижний bed (как 5.1/7.1) плюс слой высоты (HL, HR, HLS, HRS, Top/HC). Типичные потребительские раскладки:


- 9.1 = 5.1 + 4 высоты
- 11.1 = 7.1 + 4 высоты
- 13.1 = 7.1 + 5 высот + Top

Есть специфический Auro 2D, в котором в стерео (в контейнере он 2.1) смикширован звук в 5.1 (и по тестам, декодер очень качественно их восстанавливает). Так же есть редкие 4.0.4H или 5.1.2H.
Профессиональное оборудование поддерживает до 27 каналов — AuroMax® 26.1 и выглядит устрашающе:

Но по HDMI можно отдать только 8 дискретных каналов без сжатия, а для, например, 9.1 уже нужно 10. Ответ от Auro:
закодировать высоту внутрь обычного PCM 5.1/7.1 так, чтобы без декодера файл звучал как нормальный surround, а с декодером — разворачивался в полный Auro с высотой.
Это Auro-3D Octopus Codec (классический Auro-Codec или codec-v3).
Где прячется мета
Отдельного «мета-трека» нет. Всё живёт в младших битах 24-bit PCM.
Идея из white paper (ссылки на доку в конце статьи) простая:

- для доставки звука 24 бита избыточны: реальный динамический диапазон системы/слуха ближе к 16–20 битам;
- нижние 2–4 бита почти «шум»;
- их и используют под служебные данные и residuals (ошибки предсказания).
Что именно лежит в LSB
Это не «пара флагов», а можно сказать полноценный side-channel (конечно после восстановления):
Глобальная ADOL-мета (layout/boot):
- целевой layout (например
5.1+4H); - carrier layout (что физически в файле);
- нужен ли
mix3(три выхода из одного carrier); metadata_block(часто 1000 или 1024 сэмпла);sync_sample— где впервые ловится синхрослово.
Per-frame данные по каналам:
- какой выходной канал восстанавливаем;
- от какого carrier-source предсказываем;
- режим Extrapolate (
1/2/3); - quant/scale;
- параметры Golomb-Rice;
- seeds предиктора;
- упакованные residuals (не PCM высоты целиком!).
Синхронизация общая: декодер OR’ит LSB по живым carrier-каналам и ловит sync (типично 16 единиц подряд в младшем бите). Полезный битстрим с важным residuals при этом у каналов свой.
Внутри целая стейтмашина:
# ============================================================
# ADOL state machine (classic Auro-Codec, LSB side-channel)
# ============================================================
enum State:
SEEK_SYNC
PARSE_SYNC_HEADER
CHECK_CRC
PARSE_A3D_HEADER
PARSE_ADOL_BLOCKS
PARSE_ADOL_INSTR
APPLY_SEMANTICS
DONE
FAIL
procedure ScanAdol(pcm_channels):
meta = empty
for ch in pcm_channels:
samples = channel_pcm24(ch)
state = SEEK_SYNC
run_ones = 0
s = 0
while s < len(samples) and state != DONE:
switch state:
# --- 1) охота за sync: 16 единиц подряд в bit0 ---
case SEEK_SYNC:
if (samples[s] & 1) == 1:
run_ones += 1
else:
run_ones = 0
if run_ones >= 16:
sync_start = s - 15
state = PARSE_SYNC_HEADER
else:
s += 1
# --- 2) разбор sync: m (число LSB), block_size ---
case PARSE_SYNC_HEADER:
sync = ParseSyncAt(samples, sync_start)
if not sync.ok:
run_ones = 0
state = SEEK_SYNC
s += 1
continue
if sync_start + sync.block_size > len(samples):
state = FAIL
else:
state = CHECK_CRC
# --- 3) CRC16-CCITT по блоку ---
case CHECK_CRC:
if CRC16(samples[sync_start .. +block_size]) != ExpectedCRC(...):
run_ones = 0
state = SEEK_SYNC
s += 1
else:
state = PARSE_A3D_HEADER
# --- 4) заголовок A3D-блока до ADOL ---
case PARSE_A3D_HEADER:
bits = MuxBitstream(samples, sync_start, m=sync.m)
# первые 32 бита уже съедены sync'ом
skip: u8, 4×bool, u4, 2×u8, 2×bool, u2, u4, u8
adol_blocks = read_u8(bits)
skip: u8
# optional vector size зависит от count≠255 в 4 байтах
count = count_non_255(read 4×u8)
skip 4×u8
vec_sz = {2→2, 3→5, else FAIL}
skip vec_sz × u32
state = PARSE_ADOL_BLOCKS
b = 0
instructions = []
# --- 5) ADOL-блоки: каждый начинается с tag==1 ---
case PARSE_ADOL_BLOCKS:
if b >= adol_blocks:
state = APPLY_SEMANTICS
continue
tag = read_u8(bits)
if tag != 1:
state = FAIL
continue
state = PARSE_ADOL_INSTR
# --- 6) инструкции до opcode==0 (END) ---
case PARSE_ADOL_INSTR:
opcode = read_u8(bits)
ins = {block:b, tag, opcode}
switch opcode:
case 0x00: # END
# payload нет
case 0x01, 0x03, 0x5A..0x62:
ins.value = read_bits(16)
case 0x02, 0x04, 0x1E, 0x1F, 0x41, 0x47, 0x50..0x58:
ins.value = read_bits(8)
case 0x0E, 0x46, 0x6E..0x76, 0x80..0x85, 0x8C..0x90:
ins.value = read_bits(32)
case 0x40: # primary downmix gain
ins.value = read_bits(8) # channel
ins.value2 = read_bits(8) # scaler
case 0x64..0x6C: # encoder version и рядом
ins.value = read_bits(24)
default:
state = FAIL
continue
instructions.append(ins)
if opcode == 0x00:
b += 1
state = PARSE_ADOL_BLOCKS
# иначе остаёмся в PARSE_ADOL_INSTR
# --- 7) семантика ключевых opcode ---
case APPLY_SEMANTICS:
for ins in instructions:
switch ins.opcode:
case 0x1E: # ChannelInputConfig
meta.layout_id = OriginalLayout(ins.value)
meta.carrier_id = CarrierLayout(ins.value)
case 0x40:
meta.primary_downmix = (ch=ins.value, scaler=ins.value2)
case 0x41:
meta.limit_simple = ins.value
case 0x46:
meta.secondary_downmix = ins.value
case 0x47:
meta.auromatic = (profile=value>>4, mode=value&0xF)
case 0x64:
meta.encoder_version = ins.value & 0xFFFFFF
case 0x80..0x85:
meta.loudness = ins.value
meta.adol_instructions.append(ins)
if meta.layout_id == 0:
state = FAIL
else:
meta.found = true
meta.sync_sample = sync_start
meta.block_size = sync.block_size
meta.carrier_ch = ch
state = DONE
case FAIL:
run_ones = 0
state = SEEK_SYNC
s += 1
case DONE:
return meta
# после успешного блока прыгаем за его конец
if state == DONE:
break
if just_finished_good_block:
s = sync_start + sync.block_size
run_ones = 0
# можно продолжить сканировать следующие sync,
# но для boot достаточно первого валидного layout
return meta
Число младших бит не константа на весь трек. Кодек смотрит уровень блоками (~1 мс @ 48 kHz) и, если сигнал тихий, забирает меньше LSB, чтобы не портить тишину. Если громко — можно забрать больше из полезного сигнала.
Примерная схема
SEEK_SYNC
│ 16× LSB=1
▼
PARSE_SYNC → CHECK_CRC → PARSE_A3D_HEADER
│ │
│ fail ▼
└──────────────────── PARSE_ADOL_BLOCKS
│ tag=1
▼
PARSE_ADOL_INSTR ⟲ (пока opcode≠0)
│ opcode=0, все блоки
▼
APPLY_SEMANTICS → DONE
Любая нормализация/ресемпл/громкость «поверх» такого PCM легко убивает мету.
Какой итоговый битрейт
Тут важно не смешать три разных цифры.
1) Битрейт файла на диске / по HDMI
Carrier (физ каналы) остаётся обычным PCM или сжатым без потерь, например, в DTS HD MA. Для 5.1 24-bit @ 48 kHz:
6 × 24 × 48000 ≈ 6.9 Мбит/с
Для 7.1:
8 × 24 × 48000 ≈ 9.2 Мбит/с
После Auro-encode внешний битрейт тот же: это всё ещё 5.1/7.1 PCM. Высотные биты «бесплатны» с точки зрения числа каналов контейнера. Именно поэтому даже PCM Auro 13.1 влезает в HDMI/BD.
2) Сколько «места» у меты внутри
Мета — side-channel из нескольких LSB × сэмплы:
ёмкость блока ≈ block_size × m бит
где m — сколько младших бит забирают (часто от 2–4, в реальных стримах бывает и больше).
Пример порядка величин: блок 1024, m=3 на один carrier-канал:
1024 × 3 ≈ 3072 бит ≈ 3 кбит на кадр
кадр ≈ 21 мс @ 48 kHz
→ порядка ~140+ кбит/с side-info на один carrier-канал
Каналов несколько, плюс общий header/sync/CRC.
Напомню в LSB лежит не «весь height PCM», а в основном:
- параметры разделения;
- сжатые residuals ошибки предсказания/разделения.
Энергия высоты уже запечена в carrier на этапе encode. Поэтому информации для восстановления хватает даже когда bed и height звучат «по-разному». А когда данные высотных каналов сильно коррелированы с bed, например, как на концертных записях, данных еще меньше и точность выше.
3) Сколько «сэкономили» относительно дискретных высот
Если бы 9.1 хранили дискретным PCM 24/48:
10 × 24 × 48000 ≈ 11.5 Мбит/с
Auro-9.1 в carrier 5.1:
≈ 6.9 Мбит/с PCM + высоты внутри LSB
Получается сжатие числа каналов примерно в 1.5–2+ раза (в white paper для Octopus пишут reduction от ~50% до сильно больше, в зависимости от режима), при этом совместимость с железом без Auro сохраняется.
Как восстанавливаются высотные каналы
Это не «угадай высоту из корреляции bed», как в старых matrix-кодеках. Это controlled mix → unmix.
На encode:
оригинал bed + height
↓ художественный mix (с gain’ами)
carrier 5.1/7.1 (уже содержит энергию высоты)
+
LSB: residuals + параметры разделения
На decode:
DelayLine(carrier)
→ Sync/Format detector
→ Parser (frame meta)
→ Golomb-Rice (распаковка residuals)
→ Extrapolate (unmix)
→ discrete bed + height
Режимы Extrapolate
Mode 2 (mix2) — из одного carrier + одного residual-потока собираются два канала. Классика для пар:
- FL ↔ HL
- FR ↔ HR
- LS ↔ HLS
- RS ↔ HRS
Mode 3 (mix3) — из одного carrier + двух residuals — уже три выхода. Нужен для Auro-2D и тяжёлых раскладок вроде 7.1 + 5H + T. Предиктор трёхфазный: первые сэмплы получаются из меты, дальше что-то в духе 4*last − 3*previous, residuals раскидываются по фазе.
Как высоты «вычитаются» из нижних

Без Auro обычная сумма необратима: S = A + B. С Auro в LSB есть информация, чтобы максимально приближённо восстановить A' и B' и при этом развести то, что было смешано с gain’ами.
Упрощённо для пары bed/height:
carrier ≈ g1·bed + g2·height (+ квантование/LSB-замещение)
decode:
height’ = f(carrier, residuals, predictor, scales)
bed’ = unmix(carrier, height’, gains…)
На практике OutputGenerator не «отнимает height из WAV линейно одной формулой на всё», а:
- берёт carrier-сэмплы из DelayLine;
- разворачивает residuals через Golomb-Rice;
- прогоняет Extrapolate — получает до 2–3 выходов;
- bed-слот, который участвовал в dematrix, помечается как восстановленный bed (иногда почти тихий, если в carrier почти вся энергия была «высотной» для identification-стримов);
- height уходит в native-выход.
Важный практический момент: после mix3 нельзя тупо скопировать сырые LS/RS из carrier обратно в bed. В channel-id тестах там как раз остаются «объявления» высотных каналов. Bed надо брать из dematrix/unmix, height — из native-реконструкции.
В декодере есть отдельный пайплайн, когда может запрашиваться большее количество каналов чем то что получилось после декодирования, тогда в дело вступает Auromatic — специальный апмиксер, он достраивает алгоритмически недсотающие каналы.
Без декодера вы слышите совместимый surround: высота уже «вшита» в bed-микс. С декодером этот микс разделяется обратно на слои.
Теряется ли качество после dematrix?
На слух нет, НО битовой точности — нет ¯\_(ツ)_/¯
Звук в Auro может быть сохранен внутри любого lossless контейнера поддерживающий многоканальный звук в 24bit это WAV, FLAC, DTS HD MA и даже Dolby TrueHD. Но железные плееры обычно работают только с DTS HD MA.
Сама компания Auro называет это virtually lossless:
- это не perceptual coding как mp3/AAC/AC-3 (частоты не выкидывают «потому что мозг не услышит»);
- residuals и quant дают маленькую ошибку;
- в white paper артефакты часто ниже −85 dBFS, иногда около −90 dBFS;
- спектр ошибки больше похож на низкоуровневый шум, чем на pre-echo/filter-bank кашу lossy-кодеков;
- заявляется 100% channel separation после decode (слои реально разъезжаются, а не остаются «матричной кашей»).
Где качество всё же страдает:
- LSB substitution — младшие биты carrier больше не содержат «чистого» звука, а смешаны с метой. Напомню при прослушивании без Auro декодера обычно неслышно, но это уже не исходный 24-bit master as-is.
- Quant/scale — если side-budget жмут, энкодер сильнее квантует residuals (rate–distortion).
- Округления предиктора — Extrapolate работает в фиксированной арифметике, потом clamp в PCM24.
- Повторные бессмысленные encode/decode в целом заявлены устойчивыми, но любой «кривой» remux с громкостью/дизером поверх Auro-PCM ломает все.
Итого по качеству:
| Что сравниваем | Результат |
|---|---|
| Исходные высотные каналы vs Auro | Виртуальный lossless, не bit-perfect |
| Многоканал без декодера vs художественный downmix | Совместимый surround, высоты уже внутри микса |
| Нижние каналы после dematrix vs исходные | Восстанавливается unmix’ом; ошибка на уровне шума кодека |
| «Матричный» Dolby Pro Logic-style | Другая природа: там корреляция, тут residuals + controlled mix |
В моих тестовых файлах оказалось лишь два файлы в исходном многоканальном формате и закодированный в Auro и вот результаты сравнения:
orig5.1.flac vs декод auro2d.flac 96000 Hz, 6 ch
| ch | corr | ref_rms | cand_rms | gain_db |
|---|---|---|---|---|
| FL | 0.999527 | 0.009154 | 0.009147 | -0.006 |
| FR | 0.999676 | 0.008253 | 0.008248 | -0.005 |
| C | 0.999692 | 0.008783 | 0.008778 | -0.005 |
| LFE | в дорожку auro2d не включили lfe канал | 0.003858 | 0.000000 | nan |
| LS | 0.999410 | 0.008814 | 0.008806 | -0.008 |
| RS | 0.999783 | 0.007584 | 0.007581 | -0.004 |
Сравнение других муз треков с WAV 7.1.4 → Auro 5.1+5H+T (без BL/BR):
| ch | corr | ref_rms | cand_rms | gain_db |
|---|---|---|---|---|
| FL | 0.980811 | 0.063336 | 0.067622 | 0.569 |
| FR | 0.974268 | 0.062315 | 0.066608 | 0.579 |
| C | 1.000000 | 0.092218 | 0.086228 | -0.583 |
| LFE | 1.000000 | 0.046164 | 0.043083 | -0.600 |
| SL | 0.861498 | 0.023975 | 0.020202 | -1.487 |
| SR | 0.819390 | 0.023686 | 0.018894 | -1.963 |
| TFL | 0.999849 | 0.010921 | 0.011993 | 0.813 |
| TFR | 0.999815 | 0.012149 | 0.013190 | 0.714 |
| TBL | 0.886241 | 0.010921 | 0.016209 | 3.430 |
| TBR | 0.922870 | 0.012149 | 0.017331 | 3.086 |
- corr — корреляция Пирсона между каналом reference и candidate (после выравнивания по lag).
1.0= формы волны совпадают,0= не связаны, отрицательное = в противофазе.nan= один из каналов тишина (нулевая энергия). - ref_rms — RMS reference-канала, нормированный на full-scale PCM24 (
÷ 2²³). По сути относительная громкость оригинала (0…1). - cand_rms — то же для candidate (декод).
- gain_db — разница уровней:
20·log10(cand_rms / ref_rms).0≈ одинаковая громкость,+= декод громче,−= тише.nan— если RMS одного из каналов ноль.
Декодер
По результатам года работы был написан декодер на основе реверс инжиниринга оригинального от Auro. Это бета версия! Точность восстановления не гарантирована, хотя по тестам выше декодирование вроде как работает правильно 🕺
Auromatic (при использовании параметра dsp-output-channels) нечем протестировать.
orua3d-decode — консольный декодер Auro-3D. Берёт файл с Auro-Codec (PCM/FLAC/MKV/DTS…) или AuroCX (MP4 a3ds), находит мету в LSB, разворачивает высоты/bed и пишет обычный многоканальный WAV/FLAC.
На входе нужен ffmpeg/ffprobe в PATH (для demux). Рядом с WAV пишется XML с картой каналов.
Минимум:
orua3d-decode -i input.flac
Параметры
Вход / выход
| Параметр | Описание |
|---|---|
-i, --input | Входной файл: WAV, FLAC, MKV, MP4, M2TS, DTS, raw .s24le |
-o, --output | Куда писать. По умолчанию <input>_decoded.wav (или _decoded_binaural.wav с --binaural) |
--output-format | wav (без лимита каналов) или flac (макс. 8 каналов) |
--output-bits | Глубина PCM на выходе: 16 или 24 (по умолчанию 24) |
--raw | Вход — сырой interleaved s24le; нужны --rate и --channels |
--rate / --channels | Частота и число каналов для --raw |
Диагностика (без декода в файл)
| Параметр | Описание |
|---|---|
--probe | Показать layout, carrier, sync, ADOL, маски — без записи PCM |
--channel-diagram | Схема «что из carrier → что на выходе» (dematrix / native / passthrough) |
-v, --verbose | Подробный лог |
Ядро декода (лучше не трогать) 😅
| Параметр | Описание |
|---|---|
--block N | Размер внутреннего host-блока. По умолчанию подбирается так, чтобы кадры кодека не рвались; fallback 832 (как у native) |
--dsp-output-channels N | Сколько каналов на выходе. 0 / не указано = авто из меты. Потолок native — 27. Для legacy PCM без меты: 6=5.1, 10=5.1.4, 12=7.1.4 |
--dsp-strength N | «Сила» DSP/рендера, 0..15, по умолчанию 12 |
--dsp-headroom-db X | Запас по уровню на всём выходе, 0..24 дБ, по умолчанию 0 |
Порядок каналов и экспорт
| Параметр | Описание |
|---|---|
--wav-standard | Переставить каналы в канонический WAVEFORMATEXTENSIBLE порядок и прописать dwChannelMask (удобно для плееров/DAW) |
--clear-output-lsb [N] | Обнулить младшие N бит на экспорте (1..8, по умолчанию 4). Срезает остатки sync/ADOL с passthrough-каналов — для «чистого» PCM |
--mono-tracks | Дополнительно выгрузить каждый канал отдельным моно-файлом: … (FL).wav и т.п. |
Наушники / виртуализация
| Параметр | Описание |
|---|---|
--binaural | Свести декод в стерео через HRTF (принудительно 48 kHz) |
--room-preset N | Помещение: 0 HOME, 1 CONCERT, 2 LOUNGE, 3 CINEMA |
--hrtf-preset N | HRTF: 0 HPV2, 1..3 GENERIC_1..3 |
--virtualizer-mode N | 0 ENABLED, 1 DISABLED |
Экспериментальное
| Параметр | Описание |
|---|---|
--restore-lfe | Если LFE тихий/пустой — синтезировать из bed (mono sum + LPF 120 Hz, −10 dB). Не native-путь |
Что декодер делает по шагам
- Demux через ffmpeg → PCM24
- Скан LSB → sync / ADOL → layout + carrier
- Codec-v3: DelayLine → Sync → Parser → Golomb-Rice → Extrapolate (unmix bed/height)
- (Опционально) binaural / restore-lfe / wav-standard / clear LSB
- WAV/FLAC + XML mapping
Ссылки
Вся официальная дока, которую только смог найти.
Мой 💾получившийся декодер, можете использовать бесплатно, не для коммерческого использования, с указанием автора @almirus.
Поблагодарить автора:

Спасибо с российских карт

Мой публичный адрес для получения ETH 0x5AA8B619B4a1C598b48F370F48602E8E327c9E3D
Произведите оплату через Trust Wallet: https://link.trustwallet.com/send?coin=60&address=0x5AA8B619B4a1C598b48F370F48602E8E327c9E3D

Мой публичный адрес для получения USDT 0x5AA8B619B4a1C598b48F370F48602E8E327c9E3D
Произведите оплату через Trust Wallet: https://link.trustwallet.com/send?coin=10042221&address=0x5AA8B619B4a1C598b48F370F48602E8E327c9E3D&token_id=0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9
Мира!
Формат объемного звука Auro-3D и все сопутствующие технологии принадлежат бельгийской компании Goer Dynamics B.V. (подразделению группы Goerdyna Group).
Новый датчик присутствия от Яндекс и подключение к HomeAssistant
