Hace no demasiado tiempo, cuando queríamos meter fotografías o vídeos en el ordenador, debíamos capturarlo con sistemas analógicos tradicionales (normalmente cámaras de fotos "de carrete", cámaras de vídeo VHS), después de llevarlas a revelar y volver a recogerlas, debíamos escanearlas (o utilizar una tarjeta capturadora de vídeo) y sólo entonces disponíamos de ellas en el ordenador.
Con la llegada de las cámaras digitales y su masiva proliferación en los terminales móviles (teléfonos y PDAs) el proceso ha mejorado considerablemente (aunque de momento la calidad es mucho peor) pues las imagenes se obtienen directamente en un formato digital y sólo se trata de guardarlas en el ordenador mediante un cable que se conecta al terminal.
Los inconvenientes de tener una capacidad limitada de memoria en los terminales móviles y de tener periodicamente que "descargar" las imagenes al ordenador (y tener que conectar el cable) se pueden solventar a fecha de hoy si tu terminal dispone de conexión a Internet, en este caso, puedes ir "subiendo" las imágenes a un servidor a medida que las vas haciendo. Sin embargo, de momento este tipo de conexiones no son baratas.
Otra forma inalámbrica de tomar fotos consiste en utilizar la conexión Bluetooth del terminal. Lógicamente la utilidad a quedado enormemente reducida al tener que estar cerca del ordenador, sin embargo esto puede ser útil si se quieren hacer fotos en una ubicación cercana (por ejemplo un fotógrafo, un clasificador de contenidos, un perito, etc...) o si se quiere utilizar el terminal como una WebCam.
Vamos a implementar dos aplicaciones, una de ellas para móviles con las siguientes características (a día de hoy casi todos):
La otra aplicación será para Windows y estará en C# .NET utilizando la librería InTheHand para conectar con Bluetooth.
Pues bien, la primera aplicación (la del móvil) se quedará a la espera atendiendo a un servicio Bluetooth propietario (que no es estandar) SPP (Serial Port Profile) y cuando la segunda conecte con ella, enviará las fotos que se hagan mediante Bluetooth. Adicionalmente se podrá poner en auto-disparo, con lo que enviará fotos una detrás de otra.
Aquí ya no me detengo en explicar como conectar dispositivos mediante Bluetooth, en esta misma sección hay otro artículo que lo explica mejor. Así, no me dentré en detalles, con los comentarios del código deberá ser suficiente para entenderlo.
Para acceder a las capacidades multimedia de nuestro terminal móvil mediante Java lo suyo es usar la API JSR-135, es muy sencilla de utilizar y te abre el acceso a la práctica totalidad de terminales del mercado. Con el código que pongo acontinuación no creo que tengas problemas en hacer tu propia implementación:
... // Creamos dos configuraciones de captura: // Una para capturar fotos "grandes": private static final String RES_HIGH = "width=768&height=1024"; // Otra para capturar tiras rápidas de fotos: private static final String RES_LOW = "width=240&height=320"; // Obviamente podrías guardarlo en la configuración del móvil y permitir // al usuario que las establezca (usando por ejemplo objetos RecordStore). ... // Para inicializar el control de la cámara es símplemente: // Obtenemos un objeto Player indicando el dispositivo y si se quiere, // algunos parámetros de inicialización: player = Manager.createPlayer( "capture://image?width=240&height=320" ); // Le decimos que se inicie: player.realize(); // Ahora, para acceder a funciones específicas, creamos un control de // acceso a la cámara, este tipo de controles son específicos y existen // clases concretas expuestas por diversos fabricantes, no obstante la // implementación genérica suministra los típicos: videoc = (VideoControl) player.getControl( "GUIControl" ); // Si ha sido posible, entonces lo iniciamos: if( videoc != null ) { // Podemos decirle que lo muestre directamente en un Canvas, para // eso le decimos que sea USE_DIRECT_VIDEO: videoc.initDisplayMode( videoc.USE_DIRECT_VIDEO, this ); // Lo querremos fullscreen... videoc.setDisplayFullScreen( true ); // ...cuando lo hagamos visible, ahora que no nos oculte nuestro // propio contenido: videoc.setVisible( false ); } else W.nl( "ERROR getControl(VideoControl)." ); W.nl( "Iniciamos Player..."); player.start(); ... // Más tarde, allí donde sea preciso tomar una foto, haremos: byte [] buff = videoc.getSnapshot( "encoding=jpeg&" + ( high ? RES_HIGH : RES_LOW ) + "&quality=9" ); // Como ves, podemos especificar el tipo de contenido a generar y el nivel de compresión, // los datos devueltos contienen la imagen lista por ejemplo para guardar en un archivo.
Pues es casi igual de sencillo, pero desafortunadamente, Nokia tiene un bug en su S40 (si Nokia, el mayor fabricante de teléfonos móviles del mundo) detallado en Forum Nokia con ID = KIJ000730 que impide sostener de manera continuada un stream de vídeo, sólo esta disponible cuando se finaliza la grabación. Es decir, no se puede ir grabando y enviando la información al ordenador, primero debe grabarse, detener la grabación y entonces enviar el vídeo al ordenador. Esto impide mantener una grabación en tiempo real entre el terminal (Nokia) y el PC. Cuando me cambie de móvil ya implementaré esa parte, de momento habrá que esperar aunque si a tí no te ocurre (tienes un S60 u otro terminal que funcione bien) puedes implementarlo fácilmente.
Vaya, eres impaciente, bien, aquí el código completo, tampoco es para tanto:
Aquí el código de BluetoothWebCam el formulario que tendrás que meter en un MIDlet y que se encarga de abrir la cámara y conexiones Bluetooth, así como activar el modo disparo, etc...:
/* * JJBM, 09/11/2008, BluetoothWebCam.java * */ import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.media.*; import javax.microedition.media.control.*; public class BluetoothWebCam extends Canvas implements Experimento, EventoIntervaloConsumidor { private static final String exp_NOMBRE = "Bluetooth Web Cam"; private static long exp_INTERVALO = 200; private static final String RES_HIGH = "width=768&height=1024"; private static final String RES_LOW = "width=240&height=320"; private int vpw, vph; private long proxima_toma = 0; private Player player; private VideoControl videoc; private Texto W = null; private EventoIntervalo intervalo; private BluetoothServer server = null; private boolean auto = false; private boolean shot = false; private boolean shoted = false; private boolean fulls = false; public BluetoothWebCam() { } public void keyPressed( int keyCode ) { switch( getGameAction( keyCode ) ) { case Canvas.DOWN: W.avanzar(); break; case Canvas.UP: W.retroceder(); break; case Canvas.LEFT: auto = !auto; break; case Canvas.RIGHT: fulls = !fulls; videoc.setVisible( fulls ); break; case Canvas.FIRE: shot = true; break; } } private void capturar( boolean high ) { if( videoc != null ) { try { byte [] buff = videoc.getSnapshot( "encoding=jpeg&" + ( high ? RES_HIGH : RES_LOW ) + "&quality=9" ); if( ! server.Enviar( buff ) ) W.nl( "No se pudo enviar la imagen." ); } catch( Exception e ) { W.nl( "ERROR CAPTURANDO: " + e.toString() ); } } } public void Msg( String msg ) { W.nl( msg ); } public void paint( Graphics g ) { intervalo.activo = false; if( server != null && server.Disponible() ) { if( ( auto && proxima_toma < System.currentTimeMillis() ) || shot ) { capturar( shot ); shoted = shot; shot = false; proxima_toma = System.currentTimeMillis() + exp_INTERVALO; } else { if( shoted ) { shoted = false; W.nl( "Foto guardada." ); } } } W.dibujar( g, 0, 0, vpw, vph ); intervalo.activo = true; System.gc(); } public void Preparar( MIDlet m ) { setFullScreenMode( true ); vpw = this.getWidth(); vph = this.getHeight(); W = new Texto(); try { player = Manager.createPlayer( "capture://image?width=240&height=320" ); player.realize(); videoc = (VideoControl) player.getControl( "GUIControl" ); if( videoc != null ) { videoc.initDisplayMode( videoc.USE_DIRECT_VIDEO, this ); videoc.setDisplayFullScreen( true ); videoc.setVisible( false ); } else W.nl( "ERROR getControl(VideoControl)." ); W.nl( "Iniciamos Player..."); player.start(); W.nl( "Player iniciado."); } catch( Exception e ) { player = null; videoc = null; W.nl( "ERROR CREANDO Player: " + e.toString() ); } intervalo = new EventoIntervalo( this, 250 ); intervalo.start(); W.nl( "Esperando cliente..." ); server = new BluetoothServer( this ); server.start(); proxima_toma = System.currentTimeMillis(); } public String getNombre() { return exp_NOMBRE; } public Image getIcono() { return null; } public Displayable getDisplayable() { return this; } public void intervaloConcluido() { repaint(); } }
El servidor Bluetooth es también muy sencillo:
/* * JJBM, 09/11/2008, BluetoothServer.java * */ import java.io.*; import javax.bluetooth.*; import javax.microedition.io.*; public class BluetoothServer extends Thread { private static final int INTERVALO_ESPERA = 1000; private boolean activo = true; private boolean conectado = false; private BluetoothWebCam app = null; private byte [] datos = null; private String semaforo = new String( "Semaforo" ); public final UUID uuid = new UUID("27012f0c68af4fbf8dbe6bbaf7aa432a", false); public final String name = "Jose Juan Web Cam Server"; public BluetoothServer( BluetoothWebCam _app ) { app = _app; } private void Di( String msg ) { if( app != null ) app.Msg( "BTsrv: " + msg ); } public void Cerrar() { activo = false; } public boolean Conectado() { return conectado; } public boolean Disponible() { synchronized( semaforo ) { return Conectado() && datos == null; } } public boolean Enviar( byte [] _datos ) { if( !conectado ) return false; synchronized( semaforo ) { if( datos != null ) return false; datos = _datos; return true; } } public void run() { try { Di( "Abriendo..." ); String url = "btspp://localhost:"; url += uuid; url += ";name=" + name; LocalDevice local = LocalDevice.getLocalDevice(); local.setDiscoverable( DiscoveryAgent.GIAC ); StreamConnectionNotifier server = (StreamConnectionNotifier) Connector.open( url ); StreamConnection conn = server.acceptAndOpen(); DataOutputStream dos = conn.openDataOutputStream(); Di( "Listo para enviar imágenes." ); conectado = true; try { while( activo ) { Thread.sleep( INTERVALO_ESPERA ); synchronized( semaforo ) { if( datos != null ) { dos.writeByte( datos.length >> 8 ); dos.writeByte( datos.length & 0xFF ); dos.write( datos ); datos = null; } } } } catch( Exception ex ) { Di( "ERROR: " + ex.getMessage() ); } conectado = false; Di( "Cerrando..." ); dos.close(); conn.close(); server.close(); Di( "Cerrado." ); } catch( Exception ex ) { Di( "ERROR: " + ex.getMessage() ); } } }
Bueno, si has leído el artículo Bluetooth poco tenemos que añadir aquí, recibir la imagen y mostrarla al usuario es tan trivial que casi no merece la pena ponerlo... en fin, aquí va:
// Este método sería cuestión de llamarlo dentro de un Thread dedicado. public void BTClient() { DeviceItem dev = (DeviceItem) lista.SelectedItem; BluetoothEndPoint ep = new BluetoothEndPoint( dev.Device.DeviceAddress, GUID_BLUETOOTH_JJ_WEB_CAM ); BluetoothClient c = new BluetoothClient(); c.Connect( ep ); NetworkStream s = c.GetStream(); while( activo && c.Connected ) { // Longitud del archivo enviado: int l; l = s.ReadByte() << 8; l += s.ReadByte(); // Lo leemos completamente: byte [] buff = new byte [ l ]; int r = 0; while( r < l ) { int pr = s.Read( buff, r, l - r ); r += pr; } // Metemos la imagen en un control Image: imagen.Image = Image.FromStream( new MemoryStream( buff ) ); } s.Close(); c.Close(); this.Text = "Desconectado."; }
Ya ves, esto es todo el cliente que debemos implementar.
Pues sólo tienes que descargar el siguiente archivo, descomprimirlo y meter el JAR y el JAD en tu móvil y ejecutarlo (dale permisos para acceder a la cámara y a comunicaciones sino te estará preguntando todo el rato). Luego que esté ejecutándose, ejecuta el cliente en tu ordenador, dale a buscar dispositivos y tendrá que salir tu móvil, lo seleccionas, pulsas en "Conectar" y luego usa los controles del teléfono:
Cliente instalado en el PC
Recreación del estado del terminal
Cliente que ha obtenido una imagen del terminal