Streaming RTP usando GStreamer

— [ edit | raw ] migrated from node/655
Veremos como podemos hacer streaming utilizando el protocolo RTP (Real Time Protocol), usando tuberías GStreamer. Transmitiremos tanto audio como vídeo a través de las tuberías.

Introducción

Necesitas tener instalados los paquetes gstreamer0.10-tools y los plugins necesarios: gstreamer0.10-plugins-base, gstreamer0.10-farsight, gstreamer0.10-plugins-good, gstreamer0.10-plugins-ugly, gstreamer0.10-alsa, gstreamer0.10-ffmpeg y gstreamer0.10-x. El soporte de RTP en GStreamer actualmente es parcial, aún así, ya es posible ejecutar algunas tuberías que nos transmiten vídeo y audio utilizando RTP encima de UDP. RTP se compone de dos protocolos: RTP y RTCP. RTP es el encargado de transmitir los datos del flujo multimedia, y RTCP se encarga del control de flujo. En los elementos udpsrc y udpsink de la tubería es necesario especificar un puerto par, que será usado por RTP. RTCP usará justo el siguiente, un puerto impar. También puede especificarse un host opcional para especificar la interfaz por donde mandaremos los paquetes.

Vídeo MPEG4

Ejecuta la siguiente tubería (emisor) con la opción verbose:
$ gst-launch-0.10 -v filesrc location=~/streams/sincity.mp4 ! ffdemux_mov_mp4_m4a_3gp_3g2_mj2 ! rtpmp4vpay ! udpsink port=5000
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
/pipeline0/rtpmp4vpay0.src: caps = application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)MP4V-ES, ssrc=(guint)2642861434, clock-base=(guint)3234275225, seqnum-base=(guint)52397, profile-level-id=(string)0, config=(string)000001200086c5d4c307d314043c1463000001b25876694430303334
/pipeline0/rtpmp4vpay0.sink: caps = video/mpeg, width=(int)640, height=(int)480, framerate=(fraction)30000/1001, systemstream=(boolean)false, mpegversion=(int)4, codec_data=(buffer)000001200086c5d4c307d314043c1463000001b25876694430303334
/pipeline0/rtpmp4vpay0.src: caps = application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)MP4V-ES, ssrc=(guint)2642861434, clock-base=(guint)3234275225, seqnum-base=(guint)52397, profile-level-id=(string)3, config=(string)000001b003000001b50900000100000001200086c5d4c307d314043c1463000001b25876694430303334
/pipeline0/udpsink0.sink: caps = application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)MP4V-ES, ssrc=(guint)2642861434, clock-base=(guint)3234275225, seqnum-base=(guint)52397, profile-level-id=(string)3, config=(string)000001b003000001b50900000100000001200086c5d4c307d314043c1463000001b25876694430303334
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
Presta atención a las Capabilities (caps) de udpsink, es el tipo de datos que se transmitirán por la red. Copia los caps del elemento udpsink. Los caps cambian cada vez que se ejecuta el emisor. A continuación, lanza el receptor, especificando los caps en el udpsrc
$ gst-launch-0.10 udpsrc port=5000 caps ="application/x-rtp, media=(string)video, payload=(int)96, clock-rate=(int)90000, encoding-name=(string)MP4V-ES, ssrc=(guint)2642861434, clock-base=(guint)3234275225, seqnum-base=(guint)52397, profile-level-id=(string)3, config=(string)000001b003000001b50900000100000001200086c5d4c307d314043c1463000001b25876694430303334" ! rtpmp4vdepay ! ffdec_mpeg4 ! xvimagesink sync=false
En el receptor, la opción "sync=false" en los sink finales (xvimagesink, alsasink, etc) es importante, puesto que aún no hay implementado un gestor de la sesión RTP que controle la sincronización en la tubería. Puedes especificar en el udpsrc una URI del tipo "udp://host:puerto" de donde debe coger los datos.

Audio Vorbis

La forma de proceder es la misma. Primero ejecuta el emisor:
$ gst-launch-0.10 -v audiotestsrc ! audioconvert ! vorbisenc ! rtpvorbispay ! udpsink port=5000
Ahora debes copiar los caps en la tubería del receptor, y ejecutar el receptor.
$ gst-launch-0.10 udpsrc port=5000 caps="CAPS_DEL_EMISOR" ! rtpvorbisdepay ! vorbisdec ! audioconvert ! alsasink sync=false
Todo esto teóricamente se podría utilizar para transmitir en directo el escritorio de nuestro equipo a numerosos equipos mediante multicast, útil por ejemplo, para que los alumnos vean la pantalla del profesor en su ordenador. También hay soporte para transmitir vídeo Theora y vídeo H.263, pero no está suficientemente probado.

Referencias

GStreamer: RTP support Código fuente de los plugins Good, donde se encuentra RTP, versión 0.10.5 Elemento udpsrc

Reproductores GStreamer

— [ edit | raw ] migrated from node/654
Vamos a ver como podemos crear reproductores GStreamer para los distintos códecs de audio y vídeo existentes, usando gst-launch y construyendo las distintas tuberías usando los distintos plugins de GStreamer.

Ingredientes

Necesitas tener instalados los paquetes gstreamer0.10-tools y los plugins necesarios: gstreamer0.10-plugins-base, gstreamer0.10-farsight, gstreamer0.10-plugins-good, gstreamer0.10-plugins-ugly, gstreamer0.10-alsa, gstreamer0.10-ffmpeg y gstreamer0.10-x. Veremos como podemos crear reproductores genéricos, de audio y de vídeo. Según el códec del fichero que quieras reproducir, tienes que ejecutar en una consola la tubería GStreamer oportuna.

Reproductor Genérico

$ gst-launch-0.10 playbin uri=file:///home/usuario/file.mpg

Reproductor de Audio

Genérico
$ gst-launch-0.10 filesrc location=file.ogg ! decodebin ! audioconvert ! alsasink
Ogg Vorbis
$ gst-launch-0.10 filesrc location=file.ogg ! oggdemux ! vorbisdec ! audioconvert ! alsasink
FLAC
$ gst-launch-0.10 filesrc location=file.flac ! flacdec ! audioconvert ! alsasink
MP3
$ gst-launch-0.10 filesrc location=file.mp3 ! mad ! audioconvert ! alsasink

Reproductor de Vídeo

Genérico
$ gst-launch-0.10 filesrc location=file.ogg ! decodebin ! ffmpegcolorspace ! xvimagesink
Ogg Theora
$ gst-launch-0.10 filesrc location=file.ogg ! oggdemux ! theoradec ! xvimagesink
MPEG2
$ gst-launch-0.10 filesrc location=file.mpg ! ffdemux_mpegts ! mpeg2dec ! xvimagesink
MPEG4
$ gst-launch-0.10 filesrc location=file.mp4 ! ffdemux_mov_mp4_m4a_3gp_3g2_mj2 ! ffdec_mpeg4 ! xvimagesink

Reproductor de Audio y Vídeo

Genérico
$ gst-launch-0.10 filesrc location=file.ogg ! decodebin name=decoder
decoder. ! ffmpegcolorspace ! xvimagesink
decoder. ! audioconvert ! alsasink
Ogg Vorbis/Theora
$ gst-launch-0.10 filesrc location=file.ogg ! oggdemux name=demuxer
demuxer. ! theoradec ! xvimagesink
demuxer. ! vorbisdec ! audioconvert ! alsasink

Más información

Si quieres saber los plugins GStreamer instalados en el sistema, puedes usar gst-inspect-0.10. Son bienvenidas las tuberías para reproducir otros tipos de ficheros.

Emule y torrents en GNU/Linux

— [ edit | raw ] migrated from node/653
Hola buenas! todos nosotros somos adictos en mayor/menor medida a las redes P2P, de ellas descargamos películas, discos y algunos delincuentes programas y juegos. No es ningún secreto que la mayoría de los internautas españoles usan su conexión a internet para esto, las ISP's lo saben y como pasan de que su red se colapse realizan un filtro de protocolo a emule y torrents (las compañías evidentemente te lo negarán). No vale cambiar el puerto de escucha porque no se hace un filtro de puertos, se realiza filtro de protocolo. Se sospecha que Telefónica y Ono lo hacen; si es verdad que lo hacen deberían por lo menos avisar, así que nosotros vamos a intentar hacerles la pirula.

Conexiones ofuscadas

Nuestro problema está en que si un router de nuestro ISP sabe qué protocolo está transportando, podrá aplicar unas reglas de QoS (calidad de servicio) que probablemente a nosotros no nos haga ninguna gracia. La solución es más o menos sencilla: ofuscar el protocolo. Ofuscar una conexión es "desbaratar" el paquete saliente de forma que sea irreconocible, cuando llega a su destino, el receptor conoce la forma de recomponer el paquete. Hasta lo que sé, por ahora es la propia aplicación la que debe ofuscar el protocolo.

Emule

En GNU/Linux no disponemos de un buen cliente para edonkey que permita ofuscación de protocolo... debemos recurrir al maravilloso y archiconocido eMule. Tranquilos porque tiene licencia GPL :-P. Pero Int-0 se ha vuelto loco!! si eso es para hassefroch!!. Pues si... pero nosotros tenemos wine ;-) Pues bien, el emule funciona bastante bien con wine pero existe un problema: para alcanzar buenas velocidades es necesario abrir un raw socket UDP y esto sólo se puede hacer con wine si se es administrador :O. La forma en la que yo lanzo emule (no la versión con instalador, sino la que está lista para ejecutar) es la siguiente:
# sudo wine emule.exe
Hay que advertir que esto es PELIGROSO, para paliar un poco los problemas, podríamos crear una jaula chroot o algo así, si alguno lo hace que mande un post o modifique la receta :-P.
Una vez arrancado la mula, nos vamos a Preferencias > Seguridad > Activar la Ofuscación de Protocolo y listo!. El único problema de todo esto es que no hay mucha gente que use ofuscación, por ese motivo sólo descargaréis a máxima velocidad de clientes que usen también ofuscación.

Torrent

Bueno, este es el protocolo de p2p de GNU por excelencia :-). Aquí tenemos clientes en muchos sabores... pero que permitan la ofuscación de protocolo reduce bastante el abanico, concretamente (que yo conozca): Azureus y ktorrent. El Azureus está muy chulo pero tiene un inconveniente: carga mucho la máquina. El Ktorrent es más ligerito. Tanto Azureus como ktorrent permiten conexiones ofuscadas (pero en las opciones aparece como permitir cifrado de protocolo), así que ya sabeís, cualquiera de los dos os vale, además que ambos son paquete Debian. Y con esto termino... ya sabéis que la descarga de cierto software es absolutamente ilegal y la desapruebo completamente. Esta receta NO sirve para delinquir sino para paliar parcialmente la política restrictiva de los ISP's (no la discuto, pero me parece FATAL que no lo avisen oficialmente).

El software es un producto?

— [ edit | raw ] migrated from node/652
Rotundamente SI. Me explico:

Bogoutil: completa bogofilter

— [ edit | raw ] migrated from node/651
Bogofilter es un filtro antispam que es muy sencillo de combinar con sylpheed y otros clientes de correo. El paquete bogoutils nos permite acceder a las estadísticas y opciones de la base de datos de bogofilter para obtener información adicional. Aquí se muestran algunas de las opciones

Ingredientes

Evidentemente, nos hará falta bogofilter (que ya incluye bogoutil), algún cliente de correo configurado con bogofilter y varias semanas de filtrado de spam.

Notas y cosas

Una nota aclaratoria para todos los ejemplos que se vean en este artículo: He reutilizado la base de datos que tuve hace tiempo, donde no filtré correctamente todos los mailes que me llegaron. Eso ha repercutido en algunas de las estadísticas que mostraré a continuación (como por ejemplo, en la cantidad de mailes que muestran como ham algunas de las palabras, cuando son claramente spam). Otra cosa: las estadísticas que se muestran aquí sólo son de 3 meses, por lo que no se pueden generalizar (harían falta años para eso).

Ham vs. Spam

Como nota aclaratoria, diré que siempre que me refiera a "Ham" (jamón) me referiré a los mails deseados, mientras que "Spam" (la historia tiene algo que ver con una marca que hacía "Ham" malo) hace referencia a todo mail no deseado.

Cómo funciona

Bogofilter es un filtro bayesiano. Ni idea de qué significa eso, pero por lo que he podido ir observando, utiliza un sistema para puntuar cada palabra, y después calcula algún tipo de media con respecto a las palabras que hay (o algo así). Por ello, cada palabra recibe una puntuación. Este articulillo hará referencia a estas puntuaciones.

Histórico

¿No sentís curiosidad por saber si realmente se puede decidir si un mail es bueno o no por un conjunto de palabras? ¿No encontraremos palabras que son Ham y Spam al mismo tiempo? ¿Será eso lo corriente? Bueno, pues la manera de saberlo es simple:
 $ bogoutil -H ~/.bogofilter/wordlist.db
Histogram
score   count  pct  histogram
0.00   122021 61.10 ################################################
0.05      434  0.22 #
0.10      521  0.26 #
0.15      679  0.34 #
0.20     1118  0.56 #
0.25     1977  0.99 #
0.30      924  0.46 #
0.35     1845  0.92 #
0.40      712  0.36 #
0.45     4503  2.25 ##
0.50      558  0.28 #
0.55     5490  2.75 ###
0.60      507  0.25 #
0.65      577  0.29 #
0.70     1174  0.59 #
0.75      788  0.39 #
0.80      522  0.26 #
0.85      570  0.29 #
0.90      392  0.20 #
0.95    54405 27.24 ######################
tot    199717
hapaxes:  ham  109390 (54.77%), spam   31986 (16.02%)
   pure:  ham  121951 (61.06%), spam   54197 (27.14%)
Resulta curioso observar que el 61.20% de las palabras que recibo sólo se encuentran en correos Ham, y que el 27.24% sólo están en correos Spam. Así que se puede observar que es cierto que hay palabras que, directamente, deciden cuándo un correo es Spam. Fijaros que las concentraciones principales están en los extremos y el centro. Es lógico: los conectores (preposiciones, artículos, pronombres, ...) no serán decisorios.

¿Qué probabilidad hay de que "Sexo", "sex", "viagra", "ordenador" estén entre el spam

$ bogoutil -p ~/.bogofilter/wordlist.db
                                spam    good    Fisher
sexo
sexo                                3       3  0.558117
sex
sex                                25       0  0.999658
viagra
viagra                              2       3  0.457457
ordenador
ordenador                          21      48  0.356059
En cuanto introduzcáis la orden arriba citada, el cursor se quedará esperando que tecleéis las palabras a buscar o bien que pulséis CTRL+D para salir. Observando las estadísticas de arriba se observa que 3 buenos y 3 malos no son un 50%... Bueno, dije que bogofilter hacía "algún tipo de media", no la media aritmética :-D

Despedida

He escrito esto porque me parece curioso, no excesivamente útil. De todas maneras, permite comprobar que nuestro filtro bogofilter está funcionando realmente (y si no lo creéis, probad a mirar las posibilidades de una palabra y después marcad como spam un mail que la contenga). Espero que a alguien más también le parezca algo en lo que perder un ratejo ;)

Referencias

 $ man bogoutil

¡Ayuda! Audio en gnu/linux

— [ edit | raw ] migrated from node/661
Hola a todos, a ver si entre las cabezas pensantes de aquí se pude llegar a una solución óptima.

¿Soy un albañil?

— [ edit | raw ]
migrated from node/648
Cada vez escucho con más frecuencia la frase "Los programadores son albañiles"... y cada vez me preocupan más las ideas de algunas personas, sobre todo Ingenieros o Doctores en Informática. Básicamente, la comparación viene a decir que los Ingenieros en Informática serían una especie de arquitectos, mientras que los programadores en cuestión serían los albañiles o los obreros.

VideoLAN Streaming

— [ edit | raw ] migrated from node/647
VideoLAN es un proyecto que permite tanto visualizar como servir vídeos en forma de streaming y bajo demanda. Es una alternativa más completa a Flumotion, ya que soporta protocolos como RTSP y MMS muy útiles para dispositivos móviles como PDAs. En esta receta no voy a entrar a explicar como funciona el cliente VLC ni nada por el estilo, para eso esta el manual de VideoLAN. Yo solo voy a hacer referencia a como utilizar el core vlm que permite ejecutar VLC como servidor. También he probado VLS como servidor, pero como en las últimas versiones de VideoLAN se ha quedado obsoleto no voy a hacer más referencia a él a no ser que alguien lo pida explícitamente.

Fichero de configuración

Lo primero será crearse el fichero de configuración del servidor. Éste fichero es un simple fichero de texto que puede llamarse de cualquier forma. La nueva sintaxis que han introducido los desarrolladores de VideoLAN es muy coñazo, así que yo creo que mejor explico cada ejemplo por separado.

Ejemplo Unicast

# VLC media player VLM command batch
# http://www.videolan.org/vlc/

### Prueba de Unicast (Cambiar el PC destino)
new prueba1 broadcast enabled loop
setup prueba1 input "/home/.../LOTR-CD3.avi"
setup prueba1 output #duplicate{dst=std{access=udp,mux=ts,dst=EQUIPO_DESTINO:4321}}
control prueba1 play
Lo primero son los comentarios, que como se ve son con la almohadilla. Este ejemplo es de unicast a EQUIPO_DESTINO. Aquí se pone la IP del host cliente al cual se quiere servir el vídeo. En la primera línea se crea un nuevo componente con "new" de nombre "prueba1", lo activamos con "enabled" y hacemos que se ejecute en bucle "loop". En la segunda linea indicamos el vídeo fuente que se va a servir con "input". En la tercera indicamos que el protocolo que se utilizará sera "udp" utilizando el formato MPEG-TS "ts" y cuyo destino será "EQUIPO_DESTINO" al puerto "4321". Por último activamos el streming en el servidor con "play". El cliente simplemente ejecutará en su host:
$ vlc udp://@:4321
O dentro del GUI "Abrir volcado de Red" en la pestaña UDP/RTP y elegir el puerto.

Ejemplo Broadcast

En el fichero de configuración incluimos lo siguiente:
### Prueba de BROADCAST (Solo PCs)
new prueba2 broadcast enabled loop
setup prueba2 input "/home/..../Matrix_f900.avi"
setup prueba2 output #standard{mux=ts,access=udp,dst=225.0.0.1,sap,name="Matrix Trailer"}
control prueba2 play
Este ejemplo es igual que el anterior, pero en el output indicamos una dirección multicast "225.0.0.1" y además incluimos un anuncio SAP del vídeo con el nombre "Matrix Trailer". El cliente debe ejecutar:
$ vlc udp://@225.0.0.1

VoD

### Prueba de VoD con RTSP
new Test vod enabled
setup Test input "/home/...../prueba.mpg"
Este ejemplo es distinto a los anteriores porque el elemento que aquí se necesita es un "vod" en vez de un "broadcast". Como entrada sólo se necesita el vídeo que demanda el cliente. El servidor se deberá ejecutar con las siguientes opciones:
$ vlc -vvv --color -I telnet --rtsp-host SERVIDOR_IP:5554
Y el cliente:
$ vlc rtsp://SERVER_IP:5554/Test

HTTP Streaming

### Prueba de HTTP Straming para la PDA
new PDA broadcast enabled loop
setup PDA input "/home/..../dekkers.avi"
setup PDA output #duplicate{dst=std{access=http,mux=ts,dst=EQUIPO_DESTINO:8080}}
control PDA play
Ahora en el output se utiliza el protocolo "http". Es igual que el primer ejemplo pero sobre el protocolo HTTP. El cliente para visualizarlo:
$ vlc http://SERVER_IP:8080

MMSH de M$

### Prueba con MMSH
new procesado5 broadcast enabled loop
setup procesado5 input "/home/..../dekkers.avi"
setup procesado5 output #duplicate{dst=std{access=mmsh,mux=ts,dst=EQUIPO_DESTINO:5000}}
control procesado5 play
Igual que antes pero utilizando el protocolo "mmsh". Este protocolo también nos vale para el cliente ese raro "Media Player", o algo así creo que se llama. Para cliente GNU/Linux:
$ vlc mmsh://SERVER_IP:8080
Para cliente mierdero (M$) desde el "Media Player" acceder a: mms://SERVER_IP:8080.

Post-procesado

Hasta aquí hemos configurado casi todos los protocolos posibles que nos permite utilizar VideoLAN. Pero hay veces que para determinados dispositivos móviles el bit-rate del streaming es demasiado alto y hay que reducirlo para poder verlos. En esta sección vamos a ver un par de ejemplos de como hacerlo.
### Prueba de post-procesado (reduccion del bit-rate) para PDAs
new procesado1 broadcast enabled loop
setup procesado1 input "/home/.../invalid_display_width.mpeg"
setup procesado1 output #transcode{vcodec=mp2v,vb=512,scale=1,acodec=mp2a,ab=192,channels=2}:duplicate{dst=std{access=http,mux=ts,dst=EQUIPO_DESTINO:8081}}
control procesado1 play
Igual que antes configurábamos para HTTP pero aqui antes de hacer el "duplicate" hacemos un "transcode" que consiste en reducir el ratio de video a 512k y el de audio a 192k. Para ello se ha utilizado los codecs mp2v y mp2a para video y audio respectivamente. Para ver que codecs hay disponibles empollate la documentación que hay mucha. Otro ejemplo:
new procesado4 broadcast enabled loop
setup procesado4 input "/home/.../Matrix_f900.ogg"
setup procesado4 output #transcode{vcodec=theo,vb=128,scale=1,acodec=vorb,ab=64,channels=2}:duplicate{dst=std{access=http,mux=ogg,dst=EQUIPO_DESTINO:8084}}
control procesado4 play
¿ Todo claro ?. Vamos, creo que se explica por sí solo. Un saludo, Arturo.

Referencias

Ejecución del servidor

Se me olvidaba que para ejecutar el servidor en modo "daemon" hay que hacer:
$ vlc -vvv --color --daemon -I telnet --vlm-conf prueba.cfg --ttl 2 --rtsp-host 0.0.0.0:5554

TODO

Elementos scheduled

SWIG: donde C y Python se dan la mano

— [ edit | raw ] migrated from node/646
Cómo crear un módulo de Python a partir de uno en C: python wrappers para C.
Me encanta Python. La sencillez de uso y la potencia que te ofrece el lenguaje dejan mella en cualquiera. Tiene una gran cantidad de bibliotecas para hacer casi cualquier cosa. Pero claro, no todo está hecho en Python. Hay mucho código escrito en C, particularmente bibliotecas, drivers, etc. que no es accesible desde Python por cuestiones evidentes: ¡está hecho en C! Por otro lado, es posible que tengas un proyecto en el que haya partes que son especialmente críticas en cuanto al tiempo de ejecución, y quizá te planteés hacerlas en C. Pero... ¿es posible combinarlas con el resto del código, hecho en Python? Pues sí. Gracias a SWIG

Ingredientes

  1. swig, por supuesto
  2. el módulo a exportar
  3. paciencia (a veces mucha...)

Preparándonos

Como puedes ver por el ingrediente número tres, es posible que exportar un módulo de C a Python se convierta en una verdadera odisea, sobre todo al comenzar. Pero, como todo en la vida, con paciencia es posible. ¡No te quemes! Otra cosa a contemplar sobre Swig es que no sólo sirve para portar interfaces de C a Python, sino que lo puedes hacer a un montón de lenguajes: Tcl, Perl, Guile, Ruby, Java, PHP... (aquí está la lista completa). Y por si fuera poco, tiene algo de soporte para C++, aunque este no está completo (si quieres portar cosas de C++ a Python, yo mejor probaría con Boost.Python). Bien, con eso en mente podemos empezar. Swig crea un wrapper (o envoltorio) de las interfaces que queremos tener disponibles para Python. Luego, hemos de especificar de alguna forma qué queremos exportar: qué funciones, qué estructuras, etc. Para eso, tenemos dos opciones. En primer lugar, podemos modificar los ficheros de cabecera (en donde se supone deben estar los prototipos de las funciones y esas cosas) y añadir instrucciones de compilación condicional que le digan a swig qué cosas debe hacer. Esté método no se aconseja por varias razones, entre ellas, es necesario modificar el código fuente original y eso queremos evitarlo (hazme caso, QUIERES evitarlo :-p). El segundo método es crear un fichero en el que se haga explícito lo que queremos envolver para Python. Suele tener la extensión .i, por convenio. Este fichero, que estará escrito prácticamente en ANSI C, contiene además las directivas para swig. Pero para muestra, un botón: veamos un ejemplo.

Primer ejemplo

Supongamos que tenemos un módulo sencillo que tiene algunas funciones matemáticas. Su cabecera es la siguiente:
/* ejemplo.h */
/* Modulo de ejemplo: operaciones */

#ifndef __EJEMPLO_H__
#define  __EJEMPLO_H__

typedef struct {
  float real;
  float imag;
}Complex;

/* Prototipos */
extern int potencia(int, int);
extern int factorial(int);
extern Complex* sumaCompleja(Complex*, Complex*, Complex*);
extern void error_msg(char*);

#endif /* __EJEMPLO_H__ */
No tiene nada especial: una estructura y cuatro funciones, que están implementadas en ejemplo.c. Para exportarlo todo a Python, podríamos modificar el código añadiendo esto después del #define __EJEMPLO_H__:
#ifdef SWIG
%module ejemplo
%{
#include "ejemplo.h"
%}
Con esto, ya tendríamos las modificaciones necesarias. Pero como esta es la manera que se desaconseja, veamos la otra. Creamos un fichero nuevo que se llame ejemplo.i y metemos dentro lo siguiente:
%module ejemplo
%{
#include "ejemplo.h"
%}

typedef struct {
  float real;
  float imag;
}Complex;

/* Prototipos */
extern int potencia(int, int);
extern int factorial(int);
extern Complex* sumaCompleja(Complex*, Complex*, Complex*);
extern void error_msg(char*);
La primera línea, %module ejemplo especifica el nombre del módulo y es obligatorio ponerlo, a menos que lo indiques por la línea de comandos. El bloque compuesto por '%{' y '%}' es opcional. Todo lo que haya dentro, se copiará tal cual en el fichero nuevo que swig va a crear y del cual es buen momento para hablar. Cuando swig parsee este fichero, generará uno nuevo, llamado algo_wrap.c por defecto (aunque le puedes decir el nombre que quieras), donde algo es el nombre del fichero original y que contendrá todo el código en C necesario para crear el módulo de Python, es efectivamente el wrapper que queremos. Por eso, es necesario que el wrapper sepa cosas de nuestras funciones, como por ejemplo, donde están los prototipos... El resto del código es exactamente igual al que tiene la cabecera. Con esto, le decimos qué queremos que contenga el wrapper. Si eliminamos la función potencia() de esta lista, no la veremos en el módulo de Python. Así podemos elegir que exportar y que no.

Compilando

Bueno, es hora de terminar con esto. Sólo nos queda generar el wrapper y compilarlo todo. Para generar el wrapper, usamos swig evidentemente:
$ ls
ejemplo.c  ejemplo.h  ejemplo.i
$ swig -python ejemplo.i
$ ls
ejemplo.c  ejemplo.h  ejemplo.i  ejemplo.py  ejemplo_wrap.c
$
La opción -python es para indicarle que el wrapper lo queremos para Python. Podríamos haber puesto otras opciones, como por ejemplo -o wrapper.c, para indicarle el nombre del fichero, pero no es necesario, como ves ha creado uno llamado ejemplo_wrap.c. También hay otro nuevo, ejemplo.py, que es el módulo final que cargaremos y que a su vez cargará el que está en C. Ahora podemos compilarlo todo:
$gcc -c ejemplo.c
$gcc -c ejemplo_wrap.c -I /usr/include/python2.4/
$gcc -shared ejemplo.o ejemplo_wrap.o -o _ejemplo.so
$ls
ejemplo.c  ejemplo.h  ejemplo.i  ejemplo.o  ejemplo.py  _ejemplo.so  ejemplo_wrap.c  ejemplo_wrap.o
$
Cuando compilamos el wrapper, le especificamos la ruta de las cabeceras de Python que necesita (para este caso, las de Python2.4). Y cuando lo enlazamos, le especificamos que sea -shared. Y el nombre objeto final, ha de ser '_'+nombre del módulo+'.so'. ¡Y ya está! Ahora podemos probarlo.
$python
Python 2.4.4 (#2, Apr  5 2007, 20:11:18)
[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import ejemplo
>>> ejemplo.potencia(2, 10)
1024
>>> ejemplo.Complex()
<ejemplo.Complex; proxy of <Swig Object of type 'Complex *' at 0x81a06c0> >
>>>
Como puedes ver, se pueden usar sin ningún problema. Quizá te sea útil saber las conversiones que se hacen (en este caso, la estructura Complex se convierte en una clase que es un proxy de esa estructura). Lo puedes ver en la documentación.

Referencias

  1. Página oficial de SWIG
  2. Para C++: Boost.Python

GLib IO Channels con C

— [ edit | raw ] migrated from node/645
Programación de bucles de eventos en C para GNU/Linux utilizando GLib

Introducción

Siguiendo con la línea de david en su entrada GLib IO Channels con python en el que implementaba el patrón reactor en python, ahora haré una pequeña introducción a la programación de éste mismo patrón en C.

Patrón reactor en C.

El código de ejemplo para ésta receta se encuentra disponible en el repo público de Arco y puede ser descargado utilizando subversion. Aquí se muestra el contenido del archivo con la implementación del patrón reactor:
#include <stdio.h>
#include <glib.h>

gboolean handler1(gpointer data) {
  printf ("Manejador 1\n");
  return TRUE;
}

gboolean handler2(gpointer data) {
  printf ("Manejador 2\n");
  return TRUE;
}

gboolean handler3(GIOChannel *source, GIOCondition condition, gpointer data) {
  int fd;
  char buff[100];

  for (fd = 0; fd < 100; fd++) {
    buff[fd] = 0;
  }

  printf ("Manejador 3\n");
  fd = g_io_channel_unix_get_fd (source);
  read(fd, buff, 100);
  printf ("Buffer: %s", buff);
  return TRUE;
}

int main (void) {
  gpointer data;

  g_timeout_add(1000, handler1, data);
  g_timeout_add(500, handler2, data);

  g_io_add_watch(g_io_channel_unix_new(0), G_IO_IN, handler3, NULL);
  g_main_loop_run(g_main_loop_new(NULL, FALSE));
  return 0;
}
El código que se muestra es bastante simple y fácil de entender. Al principio, después de las inclusiones de las cabeceras necesarias, se declaran tres funciones que consisten en los manejadores de los eventos capturados. Los dos primeros son exactamente iguales y lo único que hacen es escribir un mensaje por pantalla. El tercer manejador se llama cuando se introducen datos por teclado. Lo único que hace es inicializar un buffer en el que después copiaremos los datos introducidos y finalmente los imprimiremos. Podemos ver que éstas tres funciones devuelven TRUE, mas adelante se verá el porqué. La "chicha" del asunto está en la función main, que es la que se encarga de asignar los manejadores anteriores a cada una de las señales y de inicializar el bucle de eventos. Las llamadas a g_timeout_add asignan los dos primeros manejadores a un evento de tiempo, que se generará, respectivamente, cada segundo y medio segundo. Por su parte, la llamada a g_io_channel_unix_new asigna el tercer manejador a los eventos de escritura generados a través de la entrada estándar (el teclado). Finalmente, la penúltima línea se encarga de poner en funcionamiento el bucle principal, creado por la función que se llama como primer parámetro. En ese momento, el programa se queda "congelado" ahí y lo único que hace es esperar a que se produzcan los eventos programados (los dos de temporización y la entrada por el teclado). Antes comenté que los manejadores siempre devolvian TRUE. Ésto se debe a que si devolviesen FALSE, no volverían a ser llamados. Así conseguimos que los manejadores vuelvan a ser ejecutados cuando se producen de nuevo los eventos.

Referencias

API de GLib.