El fallo que quería corregir ya estaba corregido — y la corrección era errónea
Anatomía de una primera contribución: tres hallazgos que no buscaba, una predicción fallida, y lo que dice el hardware cuando el código calla
En dos minutos
- Punto de partida: quería contribuir y no sabía por dónde. Seguí el consejo estándar — una tarea bien delimitada, en un proyecto activo, sobre hardware que poseo.
- Primera sorpresa: la tarea que había elegido se había hecho seis semanas antes. El aviso que veía en mi teléfono no era una carencia, era un desfase de versión.
- Segunda sorpresa: la corrección reciente contenía un error. Dos valores intercambiados, en código revisado por cuatro personas incluido el mantenedor, y marcado como probado.
- El trabajo de verdad: un sensor ausente de las tablas. Valores tomados del aparato, salvo uno que no pude demostrar — y señalado como tal en el mensaje del commit.
- La predicción fallida: anuncié fotos más claras tras la corrección. Medido: 39,2 % antes, 38,1 % después. Nada. La explicación es aritmética, y es instructiva.
- El hallazgo real: buscando por qué se congelaba la aplicación, un defecto de rendimiento que el mantenedor del paquete había instrumentado él mismo, escribiendo que «indica problemas accionables en los controladores V4L2 o de GPU». Nadie lo había reportado.
Este artículo trata de método más que de cámaras. Debería leerse aunque libcamera no le diga nada. La parte técnica está en el artículo anterior.
El problema de la primera contribución
Todo el mundo da el mismo consejo: «busca una incidencia etiquetada good first issue». Es un buen consejo y no funciona demasiado bien, por una razón sencilla: esas tareas son o bien triviales hasta el punto de no enseñar nada, o bien ya las ha cogido alguien más rápido.
Lo que funciona mejor, creo, es partir de lo que uno tiene y los demás no. En mi caso: un teléfono viejo que había decidido hacer funcionar con Linux, y por tanto hardware físico sobre el que ejecutar código que otros escriben a ciegas.
Es una ventaja más rara de lo que parece. Muchos desarrolladores trabajan sobre controladores de hardware que no poseen, fiándose de fichas técnicas y de informes de usuarios. Quien tiene el aparato conectado en su escritorio puede responder preguntas que nadie más puede zanjar.
El hilo: una tarea bien delimitada
El proyecto postmarketOS mantiene una lista de tareas por aparato. Para el mío, una incidencia paraguas titulada «Camera TODOs» enumeraba una docena de puntos, todos marcados salvo uno:
imx355 driver (front camera) is missing features for libcamera, makes the later complain (e.g. when running
cam -l)
Alcance claro, síntoma reproducible, un comando para constatarlo. Exactamente lo que se busca.
Ejecuté el comando en el teléfono. Se quejó, tal como anunciaba. Abrí el repositorio de libcamera para escribir la corrección.
Y la entrada ya estaba ahí.
Primera lección: una incidencia abierta no está necesariamente abierta
El soporte del sensor se había añadido el 17 de julio de 2026, por un ingeniero de Raspberry Pi. La versión instalada en mi teléfono era del 10 de julio.
Siete días de diferencia. El aviso que veía no era una carencia del proyecto, era un desfase entre la versión publicada y el repositorio.
Es una confusión fácil de cometer, y merece un reflejo: antes de escribir código, comprobar que el problema sigue existiendo aguas arriba, no solo en la propia máquina. Se hace con dos peticiones.
Para profundizar: comparar una versión publicada con el repositorio
La mayoría de las forjas permiten recuperar un fichero en una referencia dada. Basta con comparar la versión que uno ejecuta con la rama principal:
F="src/libcamera/sensor/camera_sensor_properties.cpp"
for ref in v0.7.2 master; do
echo -n "$ref: "
curl -s ".../repository/files/$(urlencode $F)/raw?ref=$ref" | grep -c '"imx355"'
done
# v0.7.2: 0
# master: 1
Cero apariciones en la versión publicada, una en el repositorio: el trabajo está hecho pero aún no ha salido. No hay nada que escribir.
El mismo reflejo vale para el núcleo. El segundo punto de la lista se refería a un controlador que no respondía a cierta consulta; también allí la corrección ya existía aguas arriba — ausente de las versiones 6.18, 7.0 y 7.1, presente en la rama principal. Simplemente no se había publicado todavía.
Dos tareas de tres se habían evaporado. Podría haberme detenido ahí con la sensación de haber perdido la tarde. Salvo que, al leer la entrada recién añadida, algo no cuadraba.
Segunda lección: la revisión no ve las tablas
La entrada asociaba dos patrones de prueba a valores numéricos. Pero el controlador del sensor, en el núcleo, define esos valores en el orden inverso. «Color sólido» y «barras de color» estaban intercambiados.
Lo verifiqué tres veces, por caminos independientes:
| Fuente | Lo que dice |
|---|---|
| El controlador en mi teléfono, consultado directamente | 1 = color sólido, 2 = barras de color |
| El código fuente del controlador en el núcleo oficial | mismo orden |
| Otros dos sensores con menú idéntico, descritos justo al lado | correspondencia correcta |
Y busqué activamente el contexto en el que el autor habría tenido razón: su empresa mantiene su propio núcleo, a veces con controladores distintos. Lo comprobé: mismo orden en ambos árboles. El error era real, también en su propio hardware.
Lo interesante de esta historia no es el error. Es que el commit había sido revisado por cuatro personas, incluido el mantenedor del proyecto, y llevaba la marca de «probado» de una quinta.
¿Cómo se les escapan a cinco personas competentes dos valores intercambiados?
Porque no hay lógica que revisar. Es una correspondencia entre dos documentos que no viven en el mismo repositorio: una tabla por un lado, una lista de cadenas en el núcleo por el otro. Para verificarla hay que abrir ambos ficheros lado a lado, o tener el aparato a mano. Y «probado» significaba aquí la cámara funciona, apunto y obtengo una imagen — lo cual es cierto, y nunca ejercita los patrones de prueba, que son una herramienta de diagnóstico.
El resto de la entrada era correcto. El tamaño del fotosito, los retardos del sensor: todo lo que se ejercita a diario estaba bien. Solo la parte que nadie hace funcionar estaba mal.
Ahí es donde poseer el aparato se vuelve una ventaja decisiva. No porque yo sea mejor, sino porque era el único que podía plantearle la pregunta al hardware.
Lo que debe contener un parche de dos líneas
El parche son dos líneas. Su mensaje de commit son treinta, deliberadamente.
Un revisor no debe tener que ir a buscar nada. Así que incluí la tabla del controlador del núcleo citada tal cual, la salida del comando que interroga a mi teléfono, la consecuencia concreta en una frase — pedir un color sólido produce barras de color, y viceversa — y el argumento de coherencia con los dos sensores correctamente descritos.
Y me adelanté a la pregunta que iba a llegar: «¿y las demás entradas, están mal también?». No, y lo verifiqué controlador por controlador: otros dos sensores sí usan el orden inverso, porque sus controladores lo definen así. Una frase en el mensaje ahorra un ida y vuelta de tres días.
Para profundizar: la trampa del identificador de commit
Estos proyectos usan una convención para designar el commit que se corrige:
Fixes: a1db25dabaee ("libcamera: camera_sensor: Add Sony IMX355 sensor properties")
El identificador tiene doce caracteres. Yo tenía siete delante y completé los cinco que faltaban de memoria. Estaban mal. Un identificador inventado que parece real es peor que uno ausente: nadie lo comprueba, y no apunta a nada.
La forma correcta se lo pregunta a la herramienta, no a la memoria:
git rev-parse --short=12 <referencia>
Es una nimiedad. Y es exactamente el tipo de detalle por el que devuelven un primer parche.
El trabajo de verdad, y el valor que no se puede demostrar
Quedaba algo realmente ausente: el segundo sensor del teléfono no figuraba en ninguna parte. Ni en la biblioteca, ni en el núcleo oficial — su controlador se escribió en un fabricante de chips hace ocho años y nunca se envió aguas arriba.
Tomé los valores del aparato y del código del controlador. Tres se deducen con rigor. El cuarto no: es un parámetro que se lee en una ficha técnica, y las fichas técnicas de estos sensores no son públicas.
Intenté respaldarlo. Encontré un fichero de configuración, en mi propio teléfono, que declaraba exactamente el valor que había supuesto. Excelente noticia durante unos tres minutos — hasta que comprobé si era independiente. No lo era: los diez ficheros equivalentes del proyecto declaran el mismo valor, incluso para sensores de tres fabricantes distintos. No eran diez mediciones concordantes, era un valor por defecto copiado diez veces.
Así que lo escribí tal cual en el mensaje del commit: este valor sigue lo documentado para los sensores de la misma familia. No «según la ficha técnica», ninguna formulación que sugiriera una medición. Un revisor que tenga la documentación lo corregirá en un mensaje, y está muy bien así.
Vestir una incertidumbre es la forma más eficaz de perder la confianza de un proyecto en el primer parche.
La predicción que fallé
Leyendo el código, había entendido qué provocaba la ausencia de ese sensor: sin él, la biblioteca deja de convertir la ganancia de la cámara. Escribe un número de ajuste donde debería escribir un factor de amplificación, y relee el número como si fuera el factor. El bucle de exposición automática queda falseado en ambos sentidos.
De ahí saqué una predicción: tras la corrección, las fotos con poca luz deberían mejorar. Monté un protocolo limpio — teléfono fijado y nunca movido, mediciones objetivas de luminancia y ruido, una serie antes y una serie después.
Resultado: 39,2 % de luminancia antes, 38,1 % después. Nada.
La explicación es aritmética, y la había señalado como riesgo antes de lanzar la prueba — sin sacar la consecuencia, que fue el error.
| Ganancia pedida | Fórmula correcta | Fórmula errónea |
|---|---|---|
| 0 | 1,0× | ≈ 1,0× |
| 50 | 1,1× | 50× |
| 300 | 2,4× | 300× |
El error solo es enorme con ganancia alta. Mi escena de prueba tenía una zona blanca saturada: la exposición automática tenía luz de sobra, se mantenía cerca de cero de ganancia, y en ese punto ambas fórmulas dan el mismo resultado.
El parche sigue siendo correcto, y funciona — lo verifiqué de otro modo. Tras la instalación, la biblioteca no emite ningún aviso para ese sensor, mientras que el otro sensor del teléfono, sin corregir, los emite todos. Un testigo negativo limpio.
Pero no demostré una mejora visible, así que no lo escribiré. La marca de «probado» que adjuntaré dirá que el sensor ya está reconocido. No que las imágenes sean mejores.
Es una distinción que suena quisquillosa y no lo es. Un razonamiento correcto sobre un mecanismo no dice nada de su magnitud en las condiciones de la prueba. Tenía el mecanismo; no tenía la magnitud.
El hallazgo que no estaba en ninguna lista
Durante todo este trabajo, la aplicación de cámara se congelaba con regularidad. Una molestia, que al principio sorteaba reiniciando.
Luego la instrumenté, a falta de algo mejor. Y resultó que una sola línea, emitida una vez en la inicialización, lo explicaba todo:
Importing input DMABuf failed, falling back to upload
El procesamiento de imagen se hace en el procesador gráfico. Normalmente la imagen en bruto le llega sin copia, mediante memoria compartida. Aquí la importación falla — y la biblioteca pasa a modo copia: doce megapíxeles transferidos a la GPU en cada imagen. El bus de memoria se satura, la pantalla ya no obtiene ancho de banda para sus propias operaciones, y el pipeline acaba estrangulado.
No es un fallo abrupto. Es una asfixia, lo que explica que no dejara ningún rastro aprovechable.
Y aquí está lo que da todo su valor a esa línea. Viene de un parche añadido por el mantenedor del paquete, que la había restaurado deliberadamente después de que el proyecto aguas arriba la retirara. Su argumento:
Las importaciones fallidas degradan enormemente el rendimiento e indican problemas accionables en los controladores V4L2 o de GPU.
El mantenedor había colocado el detector, dejando por escrito que su disparo merece una investigación. Se disparó en mi teléfono. Nadie lo había reportado.
Todavía no he demostrado la causalidad — la correlación es clara, el mecanismo coherente, y una prueba sencilla lo zanjaría. Pero es con diferencia lo más útil que encontré aquella mañana, y no estaba en ninguna lista de tareas.
Lo que me llevo, para la próxima
Lo que uno aporta no es talento, es una posición. No fui mejor que nadie. Tenía el aparato conectado y cinco revisores competentes no. Eso es todo, y basta.
Comprobar que el problema sigue existiendo antes de escribir. Dos tareas de tres se habían evaporado, corregidas aguas arriba pero aún sin publicar. Dos peticiones lo habrían dicho de entrada.
Una fuente que confirma solo sirve si es independiente. Diez documentos que copian el mismo valor por defecto no valen más que uno.
Separar lo demostrado de lo deducido — en un mensaje de commit como en una conversación. Tenía un mecanismo correcto y una predicción errónea; decir uno sin el otro habría sido una mentira educada.
Y instrumentar lo que molesta. La congelación de la aplicación era una molestia que llevaba horas sorteando. Fue al dejar de sortearla cuando encontré lo único que nadie más buscaba.
Es realmente un ovillo: uno tira de un hilo de dos líneas, y salen tres cosas que no había pedido.