gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Definitions

⎇ 0818392847a3b93a34e20ab2868e69f7c95bdcc8 recipes/sbc.toml TOML · 169 lines · 11.4 KB History raw

sbc: el códec que A2DP exige — un auricular bluetooth ya puede sonar 081839284 · Sergio ·

1# sbc 2.2 — el códec de audio OBLIGATORIO de A2DP. Sin esto, un auricular bluetooth no suena.
2#
3# ══ POR QUÉ ENTRA: EL BLUETOOTH EMPAREJABA Y NO SONABA ═════════════════════════════════════════
4# Medido el 2026-10-21. El estado era este, y es la figura que este catálogo persigue repartida
5# entre dos paquetes SANOS:
6#
7#   · emparejar: FUNCIONA. `bluez` trae `bluetoothctl` 5.87 (corre, estático) y su daemon
8#     `bluetoothd`, y los CUATRO escritorios que lo instalan también lo HABILITAN en `servicios`.
9#   · oír algo:  IMPOSIBLE. `pipewire` iba con `-Dbluez5=disabled`, así que `/usr/lib/spa-0.2/`
10#     traía alsa, audioconvert, aec… y CERO bluez ⇒ PipeWire no ve el auricular como salida.
11#
12# Ninguno de los dos paquetes estaba roto, y la capacidad no existía.
13#
14# ══ POR QUÉ SÓLO SBC, Y NO LA «CADENA ENORME» QUE LA RECETA DE PIPEWIRE TEMÍA ══════════════════
15# La nota de `pipewire` justificaba apagar bluez5 por «cadena de códecs aptx/ldac/aac/opus enorme».
16# Es cierto que esa cadena existe — y es OPCIONAL. Leyendo `spa/meson.build:49-53`, las deps DURAS
17# de `bluez5=enabled` son exactamente cinco:
18#
19#     bluez >= 4.101 · gio-2.0 · gio-unix-2.0 · glib-2.0 · sbc
20#
21# Las cuatro primeras ya estaban (verificado: `bluez.pc` dice 5.87, y el artefacto de `glib`
22# publica los nueve `.pc` incluidos `gio-2.0` y `gio-unix-2.0`). **Faltaba una sola receta.**
23#
24# Y es la correcta de elegir: **SBC es el único códec que A2DP exige**. Todo auricular del mercado
25# lo habla; aptX, LDAC y AAC son mejoras de calidad que el perfil puede añadir después sin tocar
26# esto. Así que el salto de «no suena» a «suena» cuesta un tarball de 277 KB, no una campaña.
27#
28# ══ ⚠ PIC NO ES OPCIONAL ACÁ ══════════════════════════════════════════════════════════════════
29# El consumidor es `libspa-bluez5.so`, un plugin que PipeWire carga con `dlopen`. Una `.a` sin PIC
30# dentro de una `.so` falla al enlazar («relocation R_X86_64_32S against ... can not be used when
31# making a shared object») o, peor, enlaza y revienta. El corpus ya pagó esto en el trío de fuentes
32# que pasó a `--with-pic` el 2026-10-19. ⇒ `--with-pic` explícito, y el guardián lo COMPRUEBA.
33name    = "sbc"
34version = "2.2"
35license = "LGPL-2.1-or-later AND GPL-2.0-or-later"
36
37[source]
38tarball = "https://www.kernel.org/pub/linux/bluetooth/sbc-2.2.tar.xz"
39sha256  = "a1ada76ef35e5af9c2fbd063754dc9e37a8d989417c6eb1ecebb089b1383ae9e"
40
41[build]
42compiler = "zig-cc"
43target   = "x86_64-linux-musl"
44link     = "static"
45flags    = []
46
47[build.phases]
48# `--disable-tools` apaga `sbcenc`/`sbcdec`/`sbcinfo`, utilidades de línea de comandos para
49# convertir ficheros: PipeWire no las usa (enlaza la librería) y arrastran `libsndfile`.
50#
51# ⚠ **Y HACE FALTA TAMBIÉN `--disable-tester`, que es quien exige sndfile de verdad.** Con sólo
52# `--disable-tools` el configure muere así, y el mensaje no nombra la opción que lo arregla:
53#     checking for sndfile... no
54#     configure: error: sndfile library is required
55# Leyendo `configure.ac:60-72`: `--disable-tools` gobierna el condicional TOOLS, pero el
56# `PKG_CHECK_MODULES(SNDFILE, …, AC_MSG_ERROR(...))` está dentro del bloque de **`enable_tester`**,
57# que es otra opción. Son dos interruptores distintos para lo que parece una sola cosa, y ninguno
58# de los dos sale en `./configure --help`. Comprobado en seco antes de volver a construir: con las
59# dos el configure sale 0 y genera `sbc/sbc.pc`.
60#
61# ⚠ **Y `--with-pic` NO BASTA: hace falta `CFLAGS=-fPIC` explícito.** Medido acá — el guardián de
62# PIC de abajo rechazó el primer build con `sbc.o trae 600 reubicaciones absolutas` teniendo
63# `--with-pic` puesto. La razón es libtool: con `--disable-shared` sólo construye la variante
64# estática y **para esa variante ignora `--with-pic`** (ese flag gobierna los objetos de la `.so`).
65# `--with-pic` se deja igual porque documenta la intención y no estorba, pero quien lo consigue es
66# el `CFLAGS`. Mismo patrón que `argp-standalone`, que lo dice en su cabecera: «Fix = --with-pic +
67# CFLAGS=-fPIC». Se APPENDEA a `$CFLAGS` en vez de reemplazarlo, para no perder lo que pone el lab
68# (ésa es la familia de error de `cgo` perdiendo `-mcpu=baseline` al redefinir `CC`).
69configure = '''
70# ⚠ **LOS PRIMITIVOS MMX/SSE NO COMPILAN CON CLANG + PIC, y hay que apagarlos.** Con `-fPIC` el
71# build muere así:
72#     sbc/sbc_primitives_mmx.c:300:5: error: invalid operand for inline asm constraint 'i'
73#     sbc/sbc_primitives_sse.c:268:5: error: invalid operand for inline asm constraint 'i'
74# Su asm en línea pasa la dirección de una tabla con la restricción `"i"` (inmediato). En código
75# PIC esa dirección deja de ser un inmediato, y clang lo rechaza donde GCC lo tolera. O sea que el
76# conflicto es real: **PIC y ese asm no pueden coexistir con este compilador**, y PIC no es
77# negociable porque el consumidor es una `.so` cargada con `dlopen`.
78#
79# La guarda que los activa es `defined(__GNUC__) && (__i386__ || __amd64__) && …`, y **zig/clang
80# define `__GNUC__` por compatibilidad**: de ahí que se enciendan solos. El parche dice la verdad
81# en vez de esquivarla — ese asm es de GCC, así que se exige que NO sea clang:
82sed -i 's/^#if defined(__GNUC__) \&\& (defined(__i386__) || defined(__amd64__)) \&\& \\/#if defined(__GNUC__) \&\& !defined(__clang__) \&\& (defined(__i386__) || defined(__amd64__)) \&\& \\/' \
83  sbc/sbc_primitives_mmx.h sbc/sbc_primitives_sse.h
84# Comprobación de que el parche MORDIÓ: un `sed` que no casa no falla, y sin esto el error de arriba
85# volvería disfrazado. Es la familia de error que este repo ya documentó con los parches de fcitx5.
86for h in mmx sse; do
87  grep -q '!defined(__clang__)' "sbc/sbc_primitives_$h.h" \
88    || { echo "sbc: el parche de $h NO se aplicó — upstream cambió la guarda" >&2; sed -n '14,20p' "sbc/sbc_primitives_$h.h" >&2; exit 1; }
89done
90# ⚠ **SEGUNDO PARCHE, y hace falta porque el primero no alcanza.** Apagadas las dos rutas SIMD, el
91# enlace seguía muriendo:
92#     ld.lld: error: undefined symbol: __cpu_indicator_init
93#     >>> referenced by sbc_primitives.c:581 (sbc_init_primitives_x86)
94# `__cpu_indicator_init` es el soporte de `__builtin_cpu_supports()` que vive en **libgcc**, y el
95# compiler-rt de zig no lo trae. La llamada culpable es `__builtin_cpu_init()`, que
96# `sbc_primitives.c:581` hace **FUERA** de los `#ifdef` de MMX/SSE — o sea que queda aunque no haya
97# ni una ruta SIMD que seleccionar. Se guarda con los mismos macros, que es lo semánticamente
98# correcto: sin implementaciones SIMD compiladas, detectar la CPU no sirve para nada.
99#
100# (La alternativa era declarar `gcc-libs` y enlazar `-lgcc` para conseguir el símbolo. Se descarta:
101# traer libgcc entera para una detección que no se va a usar es pagar una dep por nada.)
102sed -i 's/^\t__builtin_cpu_init();$/#if defined(SBC_BUILD_WITH_MMX_SUPPORT) || defined(SBC_BUILD_WITH_SSE_SUPPORT)\n\t__builtin_cpu_init();\n#endif/' sbc/sbc_primitives.c
103grep -q 'defined(SBC_BUILD_WITH_MMX_SUPPORT) || defined(SBC_BUILD_WITH_SSE_SUPPORT)' sbc/sbc_primitives.c \
104  || { echo "sbc: el parche de __builtin_cpu_init NO se aplicó — mirá sbc_primitives.c cerca de sbc_init_primitives_x86" >&2; grep -n '__builtin_cpu_init' sbc/sbc_primitives.c >&2; exit 1; }
105# ⇒ Se pierde la ruta SIMD del codificador y queda la de C (`sbc_primitives.c`), que es la de
106# referencia y siempre se construye; la selección era por CPUID en tiempo de ejecución. El coste es
107# despreciable para lo que hace falta: SBC sobre un flujo de audio de unos cientos de kbps.
108./configure --build=$CBUILD --host=$CHOST --prefix=/usr --libdir=/usr/lib --disable-shared --enable-static --with-pic --disable-tools --disable-tester CFLAGS="$CFLAGS -fPIC"
109'''
110compile   = 'make -j"$(nproc)"'
111install = '''
112set -e
113make DESTDIR=/out install
114
115[ -f /out/usr/lib/libsbc.a ] || { echo "sbc: no quedó libsbc.a — NO sello" >&2; exit 1; }
116# El `.pc` es lo que meson busca con `dependency('sbc')`: sin él, `bluez5=enabled` de pipewire falla
117# con «Dependency sbc not found» aunque la librería esté.
118[ -f /out/usr/lib/pkgconfig/sbc.pc ] || { echo "sbc: falta lib/pkgconfig/sbc.pc — es lo que busca el dependency('sbc') de pipewire" >&2; find /out -name '*.pc' >&2; exit 1; }
119[ -f /out/usr/include/sbc/sbc.h ] || { echo "sbc: falta include/sbc/sbc.h" >&2; exit 1; }
120
121# ⚠ GUARDIÁN DE PIC — **LA PRIMERA VERSIÓN ESTABA MAL Y RECHAZÓ UN BUILD CORRECTO.**
122#
123# Contaba reubicaciones `R_X86_64_32/32S` en el `.o` y abortaba con «321 reubicaciones absolutas
124# ⇒ la .a NO es PIC». Falso: al mirar en QUÉ SECCIÓN están, todas eran de `.debug_abbrev` y
125# `.debug_str` —información DWARF—, que siempre lleva reubicaciones absolutas y **no se carga en
126# ejecución**. O sea que el control medía algo real y concluía algo falso, y de paso me hizo
127# perseguir un `-fPIC` que ya estaba surtiendo efecto.
128#
129# ⇒ Se reemplaza por la prueba AUTORITATIVA: **enlazar la `.a` dentro de una `.so`**, que es
130# literalmente lo que hace el consumidor (`libspa-bluez5.so`). Si el objeto no fuera PIC, el
131# enlazador lo diría con «relocation R_X86_64_32S against ... can not be used when making a shared
132# object». No hay forma de que esto pase y el consumidor falle.
133printf 'extern int sbc_init(void*,unsigned long);\nint envoltura(void*p){return sbc_init(p,0);}\n' > /tmp/so.c
134/opt/zig/zig cc -mcpu=baseline -shared -fPIC -o /tmp/libprueba.so /tmp/so.c /out/usr/lib/libsbc.a 2>/tmp/so.err \
135  || { echo "sbc: libsbc.a NO entra en una .so ⇒ no es PIC y libspa-bluez5.so no podría enlazarla" >&2; tail -8 /tmp/so.err >&2; exit 1; }
136[ -f /tmp/libprueba.so ] || { echo "sbc: el enlace de la .so dijo OK y no dejó fichero" >&2; exit 1; }
137
138# ⚠ GUARDIÁN 2: que la librería SIRVA, compilando y EJECUTANDO contra ella.
139# «Existe libsbc.a» no prueba nada: una .a se crea igual con objetos inútiles. Acá se inicializa un
140# codificador A2DP de verdad y se piden sus tamaños de trama, que es lo primero que hace el plugin
141# de PipeWire. Si `sbc_init` o `sbc_get_frame_length` no funcionan, no se sella.
142cat > /tmp/t.c <<'EOF'
143#include <sbc/sbc.h>
144#include <stdio.h>
145int main(void) {
146    sbc_t s;
147    if (sbc_init(&s, 0) < 0) { printf("sbc_init FALLO\n"); return 1; }
148    s.frequency = SBC_FREQ_44100; s.blocks = SBC_BLK_16;
149    s.subbands = SBC_SB_8; s.mode = SBC_MODE_STEREO;
150    s.allocation = SBC_AM_LOUDNESS; s.bitpool = 53;
151    size_t fl = sbc_get_frame_length(&s), cs = sbc_get_codesize(&s);
152    sbc_finish(&s);
153    if (fl == 0 || cs == 0) { printf("tamanos en cero: fl=%zu cs=%zu\n", fl, cs); return 1; }
154    printf("OK frame=%zu codesize=%zu\n", fl, cs);
155    return 0;
156}
157EOF
158/opt/zig/zig cc -mcpu=baseline -I/out/usr/include -o /tmp/t /tmp/t.c /out/usr/lib/libsbc.a 2>/tmp/cc.err \
159  || { echo "sbc: el programa de prueba NO ENLAZA contra libsbc.a" >&2; tail -12 /tmp/cc.err >&2; exit 1; }
160sal=$(/tmp/t) || { echo "sbc: el programa de prueba enlaza y FALLA al correr: $sal" >&2; exit 1; }
161case "$sal" in OK*) : ;; *) echo "sbc: salida inesperada del programa de prueba: $sal" >&2; exit 1 ;; esac
162
163echo "sbc: libsbc.a PIC + sbc.pc + cabecera · codificador A2DP probado ($sal)"
164'''
165
166[deps]
167# `binutils` por `readelf`/`ar` del guardián de PIC: sin ellos el control se caería por falta de
168# herramienta en vez de por el defecto que busca.
169build = ["make", "pkgconf", "binutils"]