LiveStream de tu webcam con AngularJS

Preámbulo

El otro día fui al banco a activar la banca electrónica para la nueva cuenta que recientemente abrí. Cuando uno hace eso en el banco en el que tengo mi cuenta actualmente (Banorte) se le provee con un token ya sea físico o en el celular (smartphone app) para tener un método de autenticación a 2 pasos y así asegurar (reforzar la seguridad de) el acceso electrónico a las cuentas y su manejo.

El único beneficio real (al menos a mi manera de ver) del token en el celular vs el físico es la posibilidad de tener acceso a tu banca electrónica desde donde sea mientras traigas el móvil contigo. Sin embargo usar el token celular conlleva uno que otro problema de los que no hablaré para no alargar esto... El punto es que terminé pidiendo un token físico para la nueva cuenta y para tener el beneficio de la obicuidad del token celular en mi token físico sin moverlo del escritorio se me ocurrió montar una tokenCam que pudiera revisar desde donde sea cuando así lo necesitara. Hay varias maneras de hacer esto, (transmitir una webcam en vivo de forma semi-privada) de las más conocidas:

El método con VLC es la elección por excelencia de muchos, pero requiere una computadora razonablemente potente con un sistema operativo razonablemente nuevo (Lo que significa que mi servidor CentOS 6 de 32 bits desde donde quiero servir la LiveCam queda descartado por ejemplo).

El método con FFmpeg es más amigable con el hardware usado, pero al necesitar una cantidad fija de banda ancha por cliente, termina consumiendo todo el Internet disponible con pocas conexiones a la LiveCam rápidamente, no es escalable ni rentable por lo tanto.

El método con HTML5 Websockets funciona muy bien en localhost, pero al exponerlo al Internet, el consumo de banda ancha vuelve a ser un problema haciendo que haya una mala experiencia de uso generalmente al tratar de ver el streaming desde un cliente externo.

El software especializado para cámaras IP (como podrían ser motion o bien, zoneminder por ejemplo) resultó ser por demás complejo de configurar y abarcaba muchas más cosas aparte del objetivo que quería cumplir, además de proveer de resultados malos (pésima calidad de streaming, con bastante lag encima).

Intentar con WebRTC era otra opción, pero implementar algo con dicha tecnología quedó fuera de mis opciones al ver que (al menos de momento) sigue muy verde del lado de los móviles. En mi caso específico es importante poder acceder la tokenCam desde mi iPhone sin mayor problema.

Entonces, ¿Cómo haremos nuestro streaming?

Para empezar, necesito que todos estemos en la misma página: Hacer un Livestreaming directo es "carísimo" (hablando de banda ancha e incluso recursos de hardware) es por esto que las plataformas y/o servicios que ofrecen esta funcionalidad delegan la carga de la transcodificación y el streaming como tal por medio de plugins propietarios del lado del cliente (Flash, Silverlight, Complemento de Google Hangouts) evitando gastar demás en recursos propios y/o del servidor. Para mi implementación no quise depender de un plugin externo con la finalidad de asegurar la máxima compatibilidad multiplataforma del lado de los clientes; Sin embargo los problemas de recursos seguían siendo los mismos: Para que se hagan una idea, un streaming de baja resolución (VGA) a 320Kbps (por ejemplo) representa un gasto de banda ancha de más o menos 3GB diarios en transferencia sin que nadie lo vea. Cada 24 horas de alguien viéndolo (incluso si es únicamente un solo cliente atendiendo al streaming sin parar) añade otros 3GB de banda ancha en transferencia a los previos tres ¡Demasiado! La solución con la que ataqué este problema fue muy sencilla y creo que a más de un lector le va a parecer incluso gracioso... Continúa leyendo para conocer los detalles.

NOTA: Para este tutorial usaré Fedora Linux como sistema operativo de referencia, pero estas mismas instrucciones aplican para otras distros linux e incluso otras plataformas (como Windows u OS X) mientras instales los programas/dependencias utilizados según el método correcto para tu sistema con algunas pequeñas modificaciones en los pasos citados según corresponda.

Primero: Instalar dependencias

1. sudo yum -y install python ffmpeg git git-core

Segundo: El servidor y la webapp

Ahora necesitarás descargar la aplicación de AjaxCam (que yo hice) desde Github con la finalidad de ahorrarte varios pasos extra:

1. cd ~
2. git clone https://github.com/Jmlevick/ajaxcam.git
3. cd ajaxcam
4. python pythonserverGzip.py 9229

Esto debería iniciar la interfaz web de nuestra livecam en el puerto 9229 de nuestro localhost, ahora sólo nos falta el streaming para transmitir:



Tercero: Comenzar Streaming

¿Recuerdan la solución de la que les hablaba? Se trata de lo siguiente: Tomaremos una fotografía de 1 a 15 fotogramas según nuestro setup (sí, por raro que esto suene) cada segundo con nuestra cámara web vía ffmpeg y la imagen resultante de la captura será guardada a 1 solo contenedor JPEG que refrescaremos en tiempo real sobreescribiéndolo. Luego vía ajax (gracias a AngularJS en este caso) lo que hace la interfaz que descargaste y estás corriendo en tu localhost en estos momentos es refrescar la imagen en vivo cada segundo también, dando la ilusión de estar emitiendo un "feed de video en vivo" o bien, Livefeed. Lo mejor de este enfoque es que no sólo es "amigable con el hardware" al no consumir demasiados recursos sino que también es amistoso con tu banda ancha, al no consumirla a menos que un cliente accese al streaming per sé, en cuyo caso consumirá (según el peso de tus imágenes) alrededor de 3+ veces menos (gracias al setup montado y al servidor Python con compresión Gzip que utilizaremos) que un livefeed real (con calidad equivalente) asumiendo 24 horas continuas de estar enganchado al stream. La única "pega" que podría encontrarle a este enfoque es que tiene un poco de lag sí o sí, pero a final de cuentas todos los métodos de streaming directo lo tienen en mayor o menor cantidad. En cuanto al audio, si es algo que te interesa no es difícil de implementar y si lo necesitas estás más que invitado a implementarlo y hacer una pull request al proyecto en Github de la AjaxCam (yo no lo he implementado porque no lo necesito en mi setup para la tokenCam).

Una vez explicado esto, prosigamos:

Posiciona tu cámara donde la vayas a dejar haciendo streaming. Puedes "calibrar" la posición con cualquier app de cámara que tengas a la mano, (en Linux está Cheese por ejemplo) y después correremos los siguientes comandos en otra pestaña de nuestra consola:

1. cd ~/ajaxcam
2. ffmpeg -y -f v4l2 -s 320x240 -i /dev/video0 -update 1 -r 15 -threads 0 -qscale:v 2 output.jpeg

Del segundo comando lo que nos importa en parámetros es lo siguiente:

  • -s 320x240: De qué tamaño queremos las imágenes
  • -i /dev/video0: El dispositivo de captura. En Linux todas las webcam se "localizan" como /dev/videoX si sólo tienes una conectada y/o disponible, es la 0 (como en mi caso), la segunda sería la 1 y así.
  • -r 15: Número de fotogramas por segundo. Si el lag es un problema, déjalo en un rango de los 15 a los 30 para evitar lo más posible la latencia (o intenta incrementarlo incluso) Si la disponibilidad se ve afectada (que la imagen desaparezca de pronto y/o muy seguido) reduce este número. Puedes dejarlo hasta en 1 para obtener la mayor disponibilidad a costa de unos segundos extra de latencia. En equipos poco potentes es recomendable dejar este parámetro en 15.

NOTA: El comando de ffmpeg para otras plataformas puede variar un poco, te recomiendo pedir asesoría en sus listas de correo o en sus foros para hallar el comando correcto de captura según tu caso... Los parámetros sin embargo, deberían ser los mismos en todos los casos.

En mi caso tengo una (algo antigua) cámara VGA que usaré para mi setup, so el comando que se ve arriba es perfecto para mi situación específica (además cada segundo cuenta cuando hablamos de un token, estos parámetros garantizan también poco lag). Ahora nos vamos a http://lvh.me:9229 o bien http://localhost:9229 y deberíamos ver nuestra LiveCam activa en la interfaz web. Si esto no es así, trata cortando el streaming de ffmpeg y reiniciándolo o bien, cambiando el parámetro de los FPS (de 15 a 25 por ejemplo) y volviéndolo a iniciar.

Finalizando: ¡A transmitir!

Todo esto está muy bien, pero ¿cómo podemos (por ejemplo) acceder este live feed desde nuestro móvil o bien darle acceso a otros? Sencillo. Suponiendo que no tengamos una IP pública/fija, utilizaremos ngrok (clic en el enlace para conocer más y hacer el setup si no lo tienes en tu sistema); Iniciar un servidor accesible al público para nuestra LiveCam con esta herramienta es tan sencillo como correr:

ngrok 9229

Eso nos soltará una URL en la consola del tipo:

http://234ab123.ngrok.com

Que podemos utilizar/visitar desde cualquier cliente externo para revisar nuestra LiveCam cuando queramos. Cabe destacar que ngrok puede bajar (de manera notable) el rendimiento de nuestro streaming por razones totalmente ajenas a nuestra AjaxCam. Si realmente piensas servir esta LiveCam, no olvides añadir al inicio del sistema los siguientes comandos (que deben correr como tu usuario desde tu Home); Puedes establecer este comportamiento de muchas maneras: con autostart (aplicaciones al inicio del escritorio) un cronjob o bien, desde tu rc.local:

1. cd ajaxcam
2. nohup ffmpeg -y -f v4l2 -s 320x240 -i /dev/video0 -update 1 -r 15 -threads 0 -qscale:v 2 output.jpeg >/dev/null 2>&1 &
3. nohup python pythonserverGzip.py 9229 >/dev/null 2>&1 &
4. ngrok start mycam

NOTA: Como explicamos en nuestro tutorial de Ngrok, es posible personalizar la URL de nuestro túnel para evitar que ésta cambie aleatoriamente con el paso de los días, en este caso estoy iniciando el túnel mycam previamente establecido en la configuración de Ngrok directamente.

Y pues bueno, esto es todo... Si te gustó el post, no olvides compartirlo, darle like, recomendar este blog y demás; Cabe destacar que esto es sólo una implementación básica y cada quien puede "extenderla" según sus necesidades (control de acceso para mayor seguridad por ejemplo). Eeen fin, eso ya depende de cada uno.

Nos leemos en los comentarios.