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 188Rust 55Shell 30Markdown 281211 Definiciones

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

El bluetooth EMPAREJABA y NO SONABA, repartido entre dos paquetes sanos:
`bluez` trae `bluetoothctl` y su daemon y los cuatro escritorios habilitan el
servicio, pero `pipewire` iba con -Dbluez5=disabled ⇒ /usr/lib/spa-0.2/ traía
alsa, audioconvert, aec… y CERO bluez, así que PipeWire no veía el auricular
como salida.

La nota de pipewire justificaba apagarlo por «cadena de códecs aptx/ldac/aac/
opus enorme». Esa cadena existe y es OPCIONAL: leyendo spa/meson.build:49-53,
las deps DURAS son cinco —bluez>=4.101, gio-2.0, gio-unix-2.0, glib-2.0 y
sbc— y las cuatro primeras ya estaban. Faltaba UNA receta, y SBC es la correcta
de elegir porque es el único códec que A2DP exige: todo auricular lo habla.

Tres muros, todos medidos, ninguno en la documentación de upstream:

1. `--disable-tools` NO alcanza: quien exige sndfile es `--disable-tester`,
   otra opción (configure.ac:60-72), y NINGUNA de las dos sale en --help.
2. `--with-pic` NO alcanza: con --disable-shared libtool lo aplica a los
   objetos de la .so que no se construye. Hace falta CFLAGS=-fPIC, apendado
   para no perder lo del lab. Mismo patrón que argp-standalone.
3. Los primitivos MMX/SSE NO compilan con clang + PIC: su asm pasa una
   dirección con la restricción "i" (inmediato), que en código PIC deja de
   serlo. Y se encienden solos porque su guarda pide `defined(__GNUC__)`, que
   zig define por compatibilidad. El parche dice la verdad —ese asm es de
   GCC— exigiendo !defined(__clang__). Queda la ruta en C, que es la de
   referencia; el coste es despreciable para unos cientos de kbps.
   Y un cuarto escondido detrás: `__builtin_cpu_init()` se llama FUERA de los
   #ifdef de SIMD, así que pedía `__cpu_indicator_init` de libgcc igual. Se
   guarda con los mismos macros en vez de traer libgcc entera por una
   detección que ya no se usa.

⚠ El guardián de PIC de la primera versión ESTABA MAL y rechazó un build
correcto: contaba R_X86_64_32/32S y abortaba con «321 reubicaciones
absolutas». Al mirar en qué SECCIÓN estaban, todas eran de .debug_abbrev y
.debug_str — DWARF siempre las lleva y no se carga en ejecución. Se reemplazó
por la prueba AUTORITATIVA: enlazar la .a dentro de una .so, que es
literalmente lo que hace el consumidor.

Y el otro guardián ejecuta: inicializa un codificador A2DP y pide sus tamaños
de trama. Da frame=118 codesize=512, que son los valores correctos para
44,1 kHz / 16 bloques / 8 subbandas / estéreo / bitpool 53.

0818392847a3b93a34e20ab2868e69f7c95bdcc8 · por Sergio · (hace 1 d) · padre ed8e4e285 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

1 archivos · +169 −0

diff --git a/recipes/sbc.toml b/recipes/sbc.toml
new file mode 100644
index 00000000..033f8d4c
--- /dev/null
+++ b/recipes/sbc.toml
@@ -0,0 +1,169 @@
+# sbc 2.2 — el códec de audio OBLIGATORIO de A2DP. Sin esto, un auricular bluetooth no suena.
+#
+# ══ POR QUÉ ENTRA: EL BLUETOOTH EMPAREJABA Y NO SONABA ═════════════════════════════════════════
+# Medido el 2026-10-21. El estado era este, y es la figura que este catálogo persigue repartida
+# entre dos paquetes SANOS:
+#
+#   · emparejar: FUNCIONA. `bluez` trae `bluetoothctl` 5.87 (corre, estático) y su daemon
+#     `bluetoothd`, y los CUATRO escritorios que lo instalan también lo HABILITAN en `servicios`.
+#   · oír algo:  IMPOSIBLE. `pipewire` iba con `-Dbluez5=disabled`, así que `/usr/lib/spa-0.2/`
+#     traía alsa, audioconvert, aec… y CERO bluez ⇒ PipeWire no ve el auricular como salida.
+#
+# Ninguno de los dos paquetes estaba roto, y la capacidad no existía.
+#
+# ══ POR QUÉ SÓLO SBC, Y NO LA «CADENA ENORME» QUE LA RECETA DE PIPEWIRE TEMÍA ══════════════════
+# La nota de `pipewire` justificaba apagar bluez5 por «cadena de códecs aptx/ldac/aac/opus enorme».
+# Es cierto que esa cadena existe — y es OPCIONAL. Leyendo `spa/meson.build:49-53`, las deps DURAS
+# de `bluez5=enabled` son exactamente cinco:
+#
+#     bluez >= 4.101 · gio-2.0 · gio-unix-2.0 · glib-2.0 · sbc
+#
+# Las cuatro primeras ya estaban (verificado: `bluez.pc` dice 5.87, y el artefacto de `glib`
+# publica los nueve `.pc` incluidos `gio-2.0` y `gio-unix-2.0`). **Faltaba una sola receta.**
+#
+# Y es la correcta de elegir: **SBC es el único códec que A2DP exige**. Todo auricular del mercado
+# lo habla; aptX, LDAC y AAC son mejoras de calidad que el perfil puede añadir después sin tocar
+# esto. Así que el salto de «no suena» a «suena» cuesta un tarball de 277 KB, no una campaña.
+#
+# ══ ⚠ PIC NO ES OPCIONAL ACÁ ══════════════════════════════════════════════════════════════════
+# El consumidor es `libspa-bluez5.so`, un plugin que PipeWire carga con `dlopen`. Una `.a` sin PIC
+# dentro de una `.so` falla al enlazar («relocation R_X86_64_32S against ... can not be used when
+# making a shared object») o, peor, enlaza y revienta. El corpus ya pagó esto en el trío de fuentes
+# que pasó a `--with-pic` el 2026-10-19. ⇒ `--with-pic` explícito, y el guardián lo COMPRUEBA.
+name    = "sbc"
+version = "2.2"
+license = "LGPL-2.1-or-later AND GPL-2.0-or-later"
+
+[source]
+tarball = "https://www.kernel.org/pub/linux/bluetooth/sbc-2.2.tar.xz"
+sha256  = "a1ada76ef35e5af9c2fbd063754dc9e37a8d989417c6eb1ecebb089b1383ae9e"
+
+[build]
+compiler = "zig-cc"
+target   = "x86_64-linux-musl"
+link     = "static"
+flags    = []
+
+[build.phases]
+# `--disable-tools` apaga `sbcenc`/`sbcdec`/`sbcinfo`, utilidades de línea de comandos para
+# convertir ficheros: PipeWire no las usa (enlaza la librería) y arrastran `libsndfile`.
+#
+# ⚠ **Y HACE FALTA TAMBIÉN `--disable-tester`, que es quien exige sndfile de verdad.** Con sólo
+# `--disable-tools` el configure muere así, y el mensaje no nombra la opción que lo arregla:
+#     checking for sndfile... no
+#     configure: error: sndfile library is required
+# Leyendo `configure.ac:60-72`: `--disable-tools` gobierna el condicional TOOLS, pero el
+# `PKG_CHECK_MODULES(SNDFILE, …, AC_MSG_ERROR(...))` está dentro del bloque de **`enable_tester`**,
+# que es otra opción. Son dos interruptores distintos para lo que parece una sola cosa, y ninguno
+# de los dos sale en `./configure --help`. Comprobado en seco antes de volver a construir: con las
+# dos el configure sale 0 y genera `sbc/sbc.pc`.
+#
+# ⚠ **Y `--with-pic` NO BASTA: hace falta `CFLAGS=-fPIC` explícito.** Medido acá — el guardián de
+# PIC de abajo rechazó el primer build con `sbc.o trae 600 reubicaciones absolutas` teniendo
+# `--with-pic` puesto. La razón es libtool: con `--disable-shared` sólo construye la variante
+# estática y **para esa variante ignora `--with-pic`** (ese flag gobierna los objetos de la `.so`).
+# `--with-pic` se deja igual porque documenta la intención y no estorba, pero quien lo consigue es
+# el `CFLAGS`. Mismo patrón que `argp-standalone`, que lo dice en su cabecera: «Fix = --with-pic +
+# CFLAGS=-fPIC». Se APPENDEA a `$CFLAGS` en vez de reemplazarlo, para no perder lo que pone el lab
+# (ésa es la familia de error de `cgo` perdiendo `-mcpu=baseline` al redefinir `CC`).
+configure = '''
+# ⚠ **LOS PRIMITIVOS MMX/SSE NO COMPILAN CON CLANG + PIC, y hay que apagarlos.** Con `-fPIC` el
+# build muere así:
+#     sbc/sbc_primitives_mmx.c:300:5: error: invalid operand for inline asm constraint 'i'
+#     sbc/sbc_primitives_sse.c:268:5: error: invalid operand for inline asm constraint 'i'
+# Su asm en línea pasa la dirección de una tabla con la restricción `"i"` (inmediato). En código
+# PIC esa dirección deja de ser un inmediato, y clang lo rechaza donde GCC lo tolera. O sea que el
+# conflicto es real: **PIC y ese asm no pueden coexistir con este compilador**, y PIC no es
+# negociable porque el consumidor es una `.so` cargada con `dlopen`.
+#
+# La guarda que los activa es `defined(__GNUC__) && (__i386__ || __amd64__) && …`, y **zig/clang
+# define `__GNUC__` por compatibilidad**: de ahí que se enciendan solos. El parche dice la verdad
+# en vez de esquivarla — ese asm es de GCC, así que se exige que NO sea clang:
+sed -i 's/^#if defined(__GNUC__) \&\& (defined(__i386__) || defined(__amd64__)) \&\& \\/#if defined(__GNUC__) \&\& !defined(__clang__) \&\& (defined(__i386__) || defined(__amd64__)) \&\& \\/' \
+  sbc/sbc_primitives_mmx.h sbc/sbc_primitives_sse.h
+# Comprobación de que el parche MORDIÓ: un `sed` que no casa no falla, y sin esto el error de arriba
+# volvería disfrazado. Es la familia de error que este repo ya documentó con los parches de fcitx5.
+for h in mmx sse; do
+  grep -q '!defined(__clang__)' "sbc/sbc_primitives_$h.h" \
+    || { 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; }
+done
+# ⚠ **SEGUNDO PARCHE, y hace falta porque el primero no alcanza.** Apagadas las dos rutas SIMD, el
+# enlace seguía muriendo:
+#     ld.lld: error: undefined symbol: __cpu_indicator_init
+#     >>> referenced by sbc_primitives.c:581 (sbc_init_primitives_x86)
+# `__cpu_indicator_init` es el soporte de `__builtin_cpu_supports()` que vive en **libgcc**, y el
+# compiler-rt de zig no lo trae. La llamada culpable es `__builtin_cpu_init()`, que
+# `sbc_primitives.c:581` hace **FUERA** de los `#ifdef` de MMX/SSE — o sea que queda aunque no haya
+# ni una ruta SIMD que seleccionar. Se guarda con los mismos macros, que es lo semánticamente
+# correcto: sin implementaciones SIMD compiladas, detectar la CPU no sirve para nada.
+#
+# (La alternativa era declarar `gcc-libs` y enlazar `-lgcc` para conseguir el símbolo. Se descarta:
+# traer libgcc entera para una detección que no se va a usar es pagar una dep por nada.)
+sed -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
+grep -q 'defined(SBC_BUILD_WITH_MMX_SUPPORT) || defined(SBC_BUILD_WITH_SSE_SUPPORT)' sbc/sbc_primitives.c \
+  || { 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; }
+# ⇒ Se pierde la ruta SIMD del codificador y queda la de C (`sbc_primitives.c`), que es la de
+# referencia y siempre se construye; la selección era por CPUID en tiempo de ejecución. El coste es
+# despreciable para lo que hace falta: SBC sobre un flujo de audio de unos cientos de kbps.
+./configure --build=$CBUILD --host=$CHOST --prefix=/usr --libdir=/usr/lib --disable-shared --enable-static --with-pic --disable-tools --disable-tester CFLAGS="$CFLAGS -fPIC"
+'''
+compile   = 'make -j"$(nproc)"'
+install = '''
+set -e
+make DESTDIR=/out install
+
+[ -f /out/usr/lib/libsbc.a ] || { echo "sbc: no quedó libsbc.a — NO sello" >&2; exit 1; }
+# El `.pc` es lo que meson busca con `dependency('sbc')`: sin él, `bluez5=enabled` de pipewire falla
+# con «Dependency sbc not found» aunque la librería esté.
+[ -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; }
+[ -f /out/usr/include/sbc/sbc.h ] || { echo "sbc: falta include/sbc/sbc.h" >&2; exit 1; }
+
+# ⚠ GUARDIÁN DE PIC — **LA PRIMERA VERSIÓN ESTABA MAL Y RECHAZÓ UN BUILD CORRECTO.**
+#
+# Contaba reubicaciones `R_X86_64_32/32S` en el `.o` y abortaba con «321 reubicaciones absolutas
+# ⇒ la .a NO es PIC». Falso: al mirar en QUÉ SECCIÓN están, todas eran de `.debug_abbrev` y
+# `.debug_str` —información DWARF—, que siempre lleva reubicaciones absolutas y **no se carga en
+# ejecución**. O sea que el control medía algo real y concluía algo falso, y de paso me hizo
+# perseguir un `-fPIC` que ya estaba surtiendo efecto.
+#
+# ⇒ Se reemplaza por la prueba AUTORITATIVA: **enlazar la `.a` dentro de una `.so`**, que es
+# literalmente lo que hace el consumidor (`libspa-bluez5.so`). Si el objeto no fuera PIC, el
+# enlazador lo diría con «relocation R_X86_64_32S against ... can not be used when making a shared
+# object». No hay forma de que esto pase y el consumidor falle.
+printf 'extern int sbc_init(void*,unsigned long);\nint envoltura(void*p){return sbc_init(p,0);}\n' > /tmp/so.c
+/opt/zig/zig cc -mcpu=baseline -shared -fPIC -o /tmp/libprueba.so /tmp/so.c /out/usr/lib/libsbc.a 2>/tmp/so.err \
+  || { 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; }
+[ -f /tmp/libprueba.so ] || { echo "sbc: el enlace de la .so dijo OK y no dejó fichero" >&2; exit 1; }
+
+# ⚠ GUARDIÁN 2: que la librería SIRVA, compilando y EJECUTANDO contra ella.
+# «Existe libsbc.a» no prueba nada: una .a se crea igual con objetos inútiles. Acá se inicializa un
+# codificador A2DP de verdad y se piden sus tamaños de trama, que es lo primero que hace el plugin
+# de PipeWire. Si `sbc_init` o `sbc_get_frame_length` no funcionan, no se sella.
+cat > /tmp/t.c <<'EOF'
+#include <sbc/sbc.h>
+#include <stdio.h>
+int main(void) {
+    sbc_t s;
+    if (sbc_init(&s, 0) < 0) { printf("sbc_init FALLO\n"); return 1; }
+    s.frequency = SBC_FREQ_44100; s.blocks = SBC_BLK_16;
+    s.subbands = SBC_SB_8; s.mode = SBC_MODE_STEREO;
+    s.allocation = SBC_AM_LOUDNESS; s.bitpool = 53;
+    size_t fl = sbc_get_frame_length(&s), cs = sbc_get_codesize(&s);
+    sbc_finish(&s);
+    if (fl == 0 || cs == 0) { printf("tamanos en cero: fl=%zu cs=%zu\n", fl, cs); return 1; }
+    printf("OK frame=%zu codesize=%zu\n", fl, cs);
+    return 0;
+}
+EOF
+/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 \
+  || { echo "sbc: el programa de prueba NO ENLAZA contra libsbc.a" >&2; tail -12 /tmp/cc.err >&2; exit 1; }
+sal=$(/tmp/t) || { echo "sbc: el programa de prueba enlaza y FALLA al correr: $sal" >&2; exit 1; }
+case "$sal" in OK*) : ;; *) echo "sbc: salida inesperada del programa de prueba: $sal" >&2; exit 1 ;; esac
+
+echo "sbc: libsbc.a PIC + sbc.pc + cabecera · codificador A2DP probado ($sal)"
+'''
+
+[deps]
+# `binutils` por `readelf`/`ar` del guardián de PIC: sin ellos el control se caería por falta de
+# herramienta en vez de por el defecto que busca.
+build = ["make", "pkgconf", "binutils"]