Capítulo 8 Uso de JNI en el cliente DVB-H 8.1. Introducción Durante el desarrollo de la aplicación cliente de DVB-H, dentro del cual se encuadra este proyecto, surgió la necesidad de combinar módulos escritos en Java con otros escritos en código nativo. En un principio se sabía que la comunicación con el receptor se debía producir con una API en código nativo. Por otro lado, ya que lo que se pretendía con el proyecto era la implementación de la API JSR-272 y una interfaz de usuario que hiciera uso de sus funcionalidades, estos dos módulos debían escribirse en Java. Además, era imprescindible implementar un receptor FLUTE a fin recibir la información necesaria para conocer los servicios difundidos por cada plataforma. Dado que ya existían implementaciones de FLUTE en C/C++ en código libre, se decidió adaptar alguna de estas implementaciones y definir una API tal y como se ha explicado en capítulos anteriores. En lo referente a los protocolos de difusión de contenidos en tiempo real, la implementación de la API de Java sobre la que se desarrolla la JSR-272 y la interfaz de usuario no implementa reproducción de contenidos difundidos en RTP ni los formatos de codificación de vídeo y audio utilizados en IPDC. Así pues, surgió la necesidad de desarrollar o adquirir una API para la reproducción de vídeo y audio e integrarla con una API homóloga en Java. Finalmente, debido a que IPDC contempla entre sus servicios interactivos la inclusión de enlaces a páginas web, era necesario contar con un navegador con el que poder abrir las URL. Dado que en Windows Mobile cuenta con un navegador web (Internet Explorer) se optó por utilizarlo en vez de implementar otro nuevo. Así pues, fue necesario ejecutar el navegador del sistema desde la interfaz de usuario. Todas estas interacciones entre módulos en C y Java se decidió realizarlas usando la interfaz JNI. La interfaz JNI no se incluye en la especificación de la máquina virtual utilizada en el proyecto del clienete DVB-H, ya que se considera que no es eficiente en memoria y produce agujeros en el modelo de seguridad de la máquina virtual. En los siguientes apartados se explicará como se ha podido utilizar JNI en una plataforma que en teoría no lo permite así como el desglose de la utilización de JNI como conexión de los distintos módulos Java y nativos. 8.2. JNI y J2ME Debido a la ubicuidad de Java, esta plataforma se ha dividido en ediciones, clasificadas según su funcionalidad y los dispositivos sobre los que se ejecutan. Estas ediciones son las siguientes: Java Standard Edition (Java SE): es la plataforma Java más popular ya que está enfocada a ordenadores de escritorio y portátiles y a aplicaciones de propósito general. 65 66 8.2. JNI y J2ME Java Enterprise Edition (Java EE): es una extensión de JavaSE enfocada al desarrollo de aplicaciones empresariales y ejecutadas en un entorno distribuido haciendo uso de internet. Java Micro Edition (Java ME): es la plataforma destinada a instalarse en dispositivos portátiles como teléfonos móviles, PDA, así como en dispositivos de capacidad de procesamiento limitada, como set-top-boxes (DVD, sintonizadores TDT, discos duros multimedia, etc). Java Card Edition (Java Card): está diseñada para ejecutar applets en tarjetas inteligentes, como tarjetas SIM. La siguiente figura muestra un esquema de la arquitectura de las distintas ediciones Java: Figura 8.1: Las ediciones Java y sus componentes. Tal y como se puede observar, las ediciones JavaSE y Java EE comparten máquina virtual (Java Virtual Machine), aunque se diferencian en las API ofrecidas. En cuanto a Java ME, la máquina virtual sobre la que se basa la plataforma depende de la configuración. El concepto de configuración así como otros propios de Java ME se detallarán en el próximo apartado. 8.2.1. Configuraciones y perfiles en J2ME Java ME está destinado a ejecutarse en muy distintas clases de dispositivos. Para poder adaptarse a las necesidades y requisitos de cada uno de ellos, esta plataforma se estructura de forma modular y escalable, usando los conceptos de configuración y perfil: Configuración: hace referencia a la definición de las características de la máquina virtual, a la definición del lenguaje de programación y al conjunto mínimo de librerías que deben estar disponibles. Perfil: define las funcionalidades de una familia de dispositivos más concreta que a la que hace referencia la configuración, ofreciendo librerías que completan las disponibles en la configuración y que se adaptan a las características propias de un determinado segmento de dispositivos. Los perfiles se situan justo por encima de la configuración. Las configuraciones posibles en Java ME son la CDC (Connected Device Configuration, configuración de dispositivos conectados) y CLDC (Connected Limitated Device Configuration, configuración de dispositivos con conexión limitada). La configuración CLDC está destinada a los dispositivos que puedan reservar un mínimo de 256 KB de memoria para la plataforma. Cuenta con un subconjunto de la API de Java más reducido que el disponible en CDC y una máquina virtual reducida (Kilobyte Virtual Machine, KVM), además de APIs propias de esta configuración. 8. Uso de JNI en el cliente DVB-H 67 La configuración CDC está enfocada a dispositivos con la más alta capacidad de procesamiento dentro de Java ME (set-top-boxes y PDA con más de 2 MB de memoria RAM dedicada a la plataforma) y utiliza la máquina virtual estándar (Java Virtual Machine, JVM). Ofrece además un subconjunto de la API de JavaSE así como librerías propias y otras corespondientes a CLDC, para hacer posible la compatibilidad con las aplicaciones implementadas en esta última plataforma. 8.2.1.1. Perfiles de CLDC En CLDC sólo exite un perfil: el MIDP (Mobile Information Device Profile, perfil de dispositivo información móvil). Este perfil permite la ejecución de interfaces gráficas de usuario (llamadas Midlets) así como la reproducción de contenidos multimedia, conexiones por sockets y comunicación por HTTP entre otras funcionalidades. Existen además librerías adicionales que pueden incorporarse al conjunto básico definido por MIDP. Estas librerías suplementarias engloban funcionalidades como la gestión de ficheros, comunicación por bluetooth o localización. 8.2.1.2. Perfiles de CDC En CDC los perfiles se disponen en capas, pudiéndose combinar según las caracteristicas de la aplicación que se pretende ejecutar. A continuación se desglosa estos perfiles: FP (Foundation Profile, perfil fundamental): es el perfil de más bajo nivel y carece de soporte para interfaces de usuario. PP (Personal Profile, perfil personal): ofrece las funcionalidades necesarias para crear interfaces de usuario complejas. Este perfil cuenta con la librería completa de Advanced Windows Toolkit (AWT) y permite la ejecución de applets implementados para JavaSE. PBP (Personal Basis Profile, perfil básico personal): es un subconjunto del perfil PP y permite la ejecución de interfaces de usuario básicas. Al igual que MIDP, a los perfiles de CDC pueden añadirse API complementarias. 8.2.2. Ejecución de código nativo en CLDC Una vez introducida la plataforma Java ME, se pasa a describir ahora la forma en que se puede ejecutar código nativo en CLDC según la especificación. En la especificación de CLDC, a diferencia de CDC, no se contempla el uso de la interfaz JNI. En lugar de esta interfaz, se utiliza otra definida para la máquina virtual KVM llamada KNI [27](KVM Native Interface, interfaz nativa de KVM). En la JVM, la interfaz JNI tiene los siguientes propósitos: Definición de una interfaz común para los desarrolladores de máquinas virtuales, de manera que todas las funciones nativas funcionen en diferentes implementaciones de la máquina virtual. Definición de una API para que los programadores Java puedan ejecutar código nativo desde Java. En cambio en el ámbito de la implementación de CLDC, hay características de JNI que no es posible soportar. En primer lugar, la gran flexibilidad que ofrece JNI acarrea un consumo de memoria considerable, que muchos de los dispositivos huéspedes de CLDC no pueden permitirse. En segundo lugar, la carga de librerías nativas, así como la ejecución arbitraria de código nativo resulta incompatible con el modelo de seguridad de CLDC, en el cual el concepto de “sandbox” (caja de arena) es muy estricto. Este modelo de seguridad, muy utilizado en las máquinas virtuales, 68 8.2. JNI y J2ME recibe su nombre de las cajas de arena presentes en los parques infantiles. Esta caja supone un entorno controlado, en el que los niños pueden jugar sin riesgo de sufrir cortes o golpes. Pues bien, en una máquina virtual, el papel de los niños lo interpretan las aplicaciones. La caja de arena está formada por el gestor de seguridad, que controla el acceso a recursos externos, estableciendo una serie de barreras a las aplicaciones, más o menos estrictas según el grado de confianza de la aplicación. Este grado de confianza viene determinado por la certificación que posee. Por ejemplo, sin una certificación adecuada, la máquina virtual lanzará avisos cada vez que la aplicación intente conectarse a internet o leer un fichero. Para adecuarse a estas limitaciones, KNI está formada por un subconjunto de JNI adaptado a las bajas capacidades de memoria y procesamiento de los dispositivos y al modelo de seguridad de CLDC. Entre los objetivos de KNI se encuentran: Portabilidad de código nativo a nivel de código fuente. KNI persigue compartir el mismo código fuente de las funciones natoivas en múltiples implementaciones de la máquina virtual (para un mismo sistema operativo). Aislamiento de las caracteristicas de implementación de la máquina virtual y de las funciones nativas. Esto significa que los implementadores de funciones nativas no tienen que conocer cómo las diferentes implementaciones de la máquina virtual resuelven la estructura de los objetos y clases o la gestión de la memoria. Eficiencia en memoria y sobrecarga mínima. KNI es más compacta y eficiente que JNI. KNI no se limita a ser un subconjunto de JNI. Además se han realizado cambios para lograr una mayor eficiencia. A continuación se resumen estas diferencias: KNI es una API del nivel de implementación. Esto significa que KNI es completamente invisible para los programadores de aplicaciones en CLDC. KNI está diseñada para ser usada por desarrolladores de máquinas virtuales o fabricantes que deseen ampliar las funcionalidades de máquinas virtuales ya implementadas, y no para que los programadores accedan por ellos mismos a recursos del sistema operativo o a librerías nativas externas. No es posible la compatibilidad binaria de código nativo. Es decir, al ofrecer KNI portabilidad a nivel de código fuente, no se garantiza que se puedan enlazar librerías nativas ya compiladas con diferentes máquinas virtuales sin que se produzca una nueva compilación. No se contempla el uso de librerías nativas dinámicas. A diferencia de JNI, el código nativo no puede cargarse durante la ejecución de una aplicación Java. Toda función nativa que se desee utilizar debe añadirse a la tabla de funciones nativas y después compilarse todo el código nativo junto con la implementación de la máquina virtual, de acuerdo con la estricta política de seguridad de CLDC. No se puede acceder a funciones nativas desde código Java. Al contrario que lo que ocurre en JNI, no es posible llamar a funciones nativas que no se hayan incorporado a la implementación de la máquina virtual. No se permite la creación de objetos, la instanciación de clases ni la llamada de métodos de Java desde código nativo. Esto tiene como objetivo la simplificación de la implementación de KNI. Si una función nativa tiene que devolver un objeto, éste se le pasa como argumento de entrada desde el nivel Java. Cambio en el paso de parámetros. Para ser más eficiente, se pasan los parámetros de una manera completamente diferente a JNI. Los parámetros se pasan a través de un técnica basada en registros, en el que los argumentos se pueden leer directamente de una pila. 8. Uso de JNI en el cliente DVB-H 69 Por lo tanto, y a modo de resumen, en CLDC teóricamente no es posible para un programador definir métodos nativos. Esto se convirtió en un escollo importante en el desarrollo del proyecto, ya que suponía la eliminación de la vía más factible de comunicación entre los módulos implementados en Java y aquellos implementados en código nativo. Afortunadamente, se encontró una implementación de CLDC propietaria que incluía la interfaz JNI. Esta implementación es la máquina virtual J9, ahora llamada Websphere Everyplace Micro Environment (WEME), desarrollada por IBM. Concretamente, las máquinas virtuales utilizadas en el proyecto fueron proporcionadas gratuitamente por MicroDoc, que además ofrecieron soporte técnico a través del correo electrónico. 8.2.2.1. JNI en la máquina virtual J9 El uso de JNI en J9 es ligeramente distinto al de la plataforma Java estándar. La diferencia estriba en el método que permite cargar las librerías dinámicas que contiene los métodos nativos. Si se recuerda la forma en la que se realizaba esto en Java SE: ... System . loadLibrary (" HolaMundo "); ... En cambio, en J9 la carga de la misma librería se realizaría de la siguiente forma: ... com . ibm . oti . vm . loadLibrary (" HolaMundo ); ... Esto es debido a que al quedar JNI fuera de la especificación de CLDC, no pueden modificarse las clases definidas por la configuración, y por tanto tiene que recurrirse a la creación de paquetes específicos para estas modificaciones, como es el caso del método loadLibrary. Por lo tanto para desarrollar los métodos nativos fue preciso contar con estas clases propias de J9 ya empaquetadas e incluirlas en el código Java, clases proporcionadas junto a la máquina virtual. 8.3. Descripción de los módulos JNI en el cliente DVB-H Como se hizo referencia en la introducción, el uso de JNI se aplica en el cliente DVB-H en los siguientes casos: Comunicación de las partes de la JSR-272 relativas a la selección de plataformas con la API abierta de recepción DVB-H, desarrollada en el proyecto del cliente DVB-H. Obtención de los ficheros necesarios para el descubrimiento y actualización de los servicios por parte de la JSR-272 a través de OpenFluteApi. Reproducción de los servicios multimedia mediante el uso de una API nativa externa controlada mediante Java. Apertura de enlaces web desde la interfaz de usuario, como parte de un servicio interactivo en IPDC. Toda interacción entre Java y código nativo a través de JNI en la implementación de la JSR-272 se agrupa en el nivel Java dentro de la clase trajano.dvbh.jni.OpenJni. Por lo tanto, esta clase cuenta con los métodos que comunican la JSR-272 tanto con el receptor DVB-H como con el receptor FLUTE. En cuanto al código nativo, todos los métodos nativos utilizados por la clase OpenJni se implementan en la librería dinámica openjni.dll. 70 8.3. Descripción de los módulos JNI en el cliente DVB-H Por otro lado, la comunicación de la interfaz de usuario con el navegador web se implementa en el nivel Java dentro de la clase es.us.esi.lynx.Comm.JniCommHndlr. El método nativo se implementa en la librería linkviewer.dll. En los siguientes apartados se detallarán ambos lados del módulo de comunicación del dominio Java con el nativo en el cliente DVB-H, tanto en la JSR-272 como en la interfaz de usuario. 8.3.1. Comunicación de la JSR-272 con la API de recepción DVB-H A fin de abstraer los módulos superiores de los diferentes receptores DVB-H que pueda soportar el cliente, se define una API abierta y genérica llamada OpenDvbhApi. Es con esta API con la que la JSR-272 tiene que comunicarse a fin de que se produzca la selección de una plataforma IPDC y los servicios que difunde. Para ello se definen en OpenJni métodos nativos que se corresponden con las funciones de OpenDvbhApi. En la implementación de estos métodos es donde se realiza la conversión de los datos de Java a C/C++, se ejecuta la función de la API y se devuelve un valor o un objeto creado en el mismo método nativo, según coresponda. A continuación se describirán los métodos nativos junto con sus correspondientes funciones de la API de recepción DVB-H. 8.3.1.1. Inicialización del receptor Para la inicialización del receptor se llama al método nativo initReceiver: public native int initReceiver (); El método nativo initReceiver devuelve 0 en caso de éxito. Este método nativo envuelve a la función OpenDvbhInitReceiver de OpenDvbhApi: OPEN_DVBH_ERR OpenDvbhInitReceiver (); Esta función devuelve un código de error propia de la API. El valor que representa a la ejecución sin errores es el cero. Además de la inicialización del receptor propiamente dicho, se utiliza el método nativo initReceiver para inicializar la librería OpenFluteApi (imprescindible para una posterior recepción FLUTE) mediante la llamada a initFluteApi. 8.3.1.2. Descubrimiento y selección de plataformas Para el descubrimiento de las plataformas se definen dos métodos nativos: discoverIpPlatforms y tuneChannel. El método discoverIpPlatforms realiza un escaneo dentro del rango de frecuencia indicado con un ancho de banda determinado: public native int discoverIpPlatforms ( int startFreq , int stopFreq , int bw ); Su función de OpenDvbhApi correspondiente es OpenDvbhDiscoverIpPlatforms, con los mismos argumentos que el método nativo. Las frecuencias se expresan en Hz y el ancho de banda en MHz tanto en el método nativo como la función de OpenDvbhApi. El método tuneChannel se utiliza para intentar localizar una plataforma en una determinada frecuencia y ancho de banda: public native int tuneChannel ( int freq , int bw ); correspondiéndose dicho método nativo con la función OpenDvbhTuneChannel, que al igual que ocurre con OpenDvbhDiscoverIpPlatforms, tiene los mismos argumentos que el método tuneChannel. Una vez realizada la sintonización, se supone que están disponibles los datos de las plataformas disponibles. Dicha información se obtiene mediante la llamada al método nativo getIpPlatforms: public native OpenJniIpPlatform [] getIpPlatforms (); 8. Uso de JNI en el cliente DVB-H 71 Este método nativo devuelve una cadena de objetos OpenJniIpPlatform, objetos que contienen la información relativa a las plataformas encontradas (nombre e identificador). Los datos necesarios para la construcción de la tabla de objetos se obtienen de la función nativa OpenDvbhGetIpPlatform: OPEN_DVBH_ERR Op en Dv bh Get Ip Pl atf or ms ( OPEN_DVBH_IP_PLAT * ipPlatforms , UINT32 * pNumIpPlatforms ); A esta función se le pasa una tabla de estructuras OPEN_DVBH_IP_PLAT que contienen la misma información que OpenJniIpPlatform. Una vez que la JSR-272 ya conoce las plataformas disponibles, procede a seleccionar una mediante el método nativo selectIpPlatform: public native int selectIpPlatform ( long ipPlatformId ); Este último método nativo en su implementación envuelve a la función de OpenDvbhApi homóloga: OPEN_DVBH_ERR O p e n Dv b h S el e c t Ip P l a tf o r m ( UINT32 ipPlatformId ); Para conocer la plataforma seleccionada, se define el siguinete método nativo: public native OpenJniIpPlatform getSelectedPlatform (); El anterior método devuelve el objeto OpenJniPlatform con la información de la plataforma seleccionada en caso de que se haya seleccionado alguna. Si no es así devuelve una tabla de cero elementos. El método getSelectedPlatform llama a la función nativa correspondiente: OPEN_DVBH_ERR O p e n D v b h G e t S e l e c t e d P l a t f o r m ( OPEN_DVBH_IP_PLAT * pIpPlatform ); 8.3.1.3. Obtención de la hora del emisor DVB-H Entre las tablas PSI/SI del flujo de transporte de DVB-H se encuentra la tabla TDT (Time and Date Table) que sirve para dar a conocer a los receptores la hora de la red de difusión. Esta hora es útil para fijar la referencia horaria del receptor, ya que todas las marcas de tiempo que se indiquen (como eventos de la programación) serán relativos al valor de tiempo difundido en esta tabla. El método nativo que permite la obtención de los valores actuales de la tabla TDT es getTime: public native OpenJniTime getTime (); La hora de la red de difusión se describe en un objeto de la clase OpenJniTime, que tiene como campos la hora (hora, minutos y segundos) así como también la fecha (día/mes/año). Este código nativo se implementa apoyándose en la función de OpenDvbhApi OpenDvbhGetTime. 8.3.1.4. Parada del receptor La JSR-272 puede parar el receptor usando el método stopReceiver: public native int stopReceiver (); El anterior método nativo envuelve a la función nativa OpenDvbhStopReceiver, así como a la función de OpenFluteApi closeFluteApi, ya que de igual manera que initReceiver indicaba el comienzo de la recepción DVB-H, stopReceiver indica el final, por lo que no se necesitará más de la API de recepción FLUTE. 8.3.2. Recepción FLUTE en la JSR-272 La JSR-272 utiliza la API de recepción FLUTE (OpenFluteApi) para descargar tanto los archivos necesarios para el descubrimiento de los servicios ofrecidos por una plataforma como aquellos archivos disponibles en un servicio de descarga FLUTE. 72 8.3. Descripción de los módulos JNI en el cliente DVB-H La interacción entre la JSR-272 y OpenFluteApi se realiza a través de JNI como ya se ha indicado, siendo cometido de OpenFluteApi la recepción de los datos que forman los ficheros a descargar así como su almacenamiento en el sistema de ficheros. Así pues, en última instancia, la JSR-272 (una vez que ha abierto la sesión FLUTE y obtenido los ficheros difundidos en la sesión) indica los ficheros a descargar, el nombre y la localización de los archivos en el sistema a la API de FLUTE. Cuando la API de FLUTE le indica que los archivos se han descargado completamente, es la propia JSR-272 la que abre los ficheros en caso de que sea preciso analizarlos. Una vez esbozado el funcionamiento de la recepción FLUTE en la JSR-272 se procede a una descripción más detallada de la interacción entre la JSR-272 y OpenFluteApi. 8.3.2.1. Inicio de la recepción de una sesión Al igual que ocurre en la recepción DVB-H, la JSR-272 se comunica con OpenFluteApi a través de JNI, en la que los métodos nativos se corresponden con funciones de la API de FLUTE. Antes de abrir la sesión FLUTE es necesaria la inicialización de la librería, que tal y como se indicó en el apartado 8.3.1.1, se produce dentro del método nativo initReceiver. Para abrir una sesión FLUTE e iniciar la recepción y procesado de las instancias FDT, se llama al método nativo startFluteSession: public native int startFluteSession ( OpenJniFluteSession fluteSession ); Este método nativo, extrae los datos del objeto fluteSession (IP destino y origen, puerto, TSI de la sesión o la descripción de la sesión en formato SDP) y construye una estrutura homóloga flute_session_t para después llamar a la función openFluteSesion: OPEN_FLUTE_CODE openFluteSession ( flute_session_t * session , int * s_id ); Si se inicia correctamente la recepción de la sesión FLUTE, se devuelve un identificador de la sesión abierta. Este identificador es el que permite a la JSR-272 hacer referencia a una determinada sesión entre las múltiples sesiones que puede tener abiertas en el transcurso de la ejecución del cliente DVB-H. 8.3.2.2. Obtención de los ficheros difundidos Si se ha abierto la sesión FLUTE adecuadamente, la JSR-272 puede solicitar la relación de ficheros que se están difundiendo en la sesión así como las características principales de la última FDT recibida usando el método nativo getFluteFileList: public native OpenJniFileInfo [] getFluteFileList ( int s_id , OpenJniFdtInfo fdtInfo ); Este método tiene como argumentos de entrada el identificador de la sesión y un objeto OpenJniFdtInfo donde se describe la información más relevante de la FDT (tiempo de caducidad, identificador de instancia o valor del atributo FullFDT ) y cuyos campos serán establecidos en la implementación. La función sobre la que se basa la implementación de este método es la función de OpenFluteApi getFluteFileInfo. Tras la llamada a esta función, y en caso de que ésta se desarrolle sin errores, se establecen los campos de fdtInfo y se crea la tabla de objetos OpenJniFileInfo, que contiene la información relevante de cada archivo difundido en la sesión (Content-Location, TOI o Content-Type por ejemplo). Si ha ocurrido algún error durante la ejecución del método, este devuelve null. 8.3.2.3. Recepción de ficheros Para recibir los ficheros difundidos por FLUTE, una vez conocidos gracias a la llamada a getFluteFileList, se pueden utilizar tres métodos nativos (receiveFluteFileByToi, 8. Uso de JNI en el cliente DVB-H 73 receiveFluteFile y receiveAllFluteFiles) que se corresponden con otras tantas funciones de OpenFluteApi (receiveFluteFileByToi, receiveFluteFileByUri y receiveAllFluteFiles). Las implementaciones de los métodos nativos se ocupan simplemente de convertir los argumentos de entrada para llamar a las funciones de OpenFluteApi correspondientes y comunicar el éxito o error de la operación. Ya que se explicó la definición de cada una de las funciones de recepción de OpenFluteApi en el capítulo 6, se pasa a especificar las definiciones de los métodos nativos. En primer lugar, para la recepción según el TOI del fiichero dentro de la sesión se utiliza el método receiveFluteByToi: public native int rec eive Flut eFile ByTo i ( int s_id , int toi , String dir , String fileName ); donde además del TOI, se le indica el nombre con el que se desea que se almacene el fichero, y el directorio donde situarlo. El s_id se utiliza para identificar la sesión de la que se quiere descargar el archivo en cuestión. Para la recepción de archivos según el URN definido en el atributo Content-Location del fichero en la FDT, se llama al metodo nativo: public native int receiveFluteFile ( int s_id , String fileName , String dir ); donde fileName se corresponde con el Content-Location, y dir con el directorio donde se quiere que se guarde el archivo. Finalmente, si lo que se pretende es descargar todos los ficheros difundidos en una sesión FLUTE, se usa el método nativo receiveAllFluteFiles: public native int receiveAllFluteFiles ( int s_id , String dir ); Como en todas las anteriores funciones, el directorio de la descarga se fija mediante el argumento dir. Tanto en este método nativo como en los dos anteriores, se devuelve 0 si se ha recibido el fichero (o ficheros para el caso de receiveAllFluteFiles) satisfactoriamente. 8.3.2.4. Actualización de la sesión Los servicios que una plataforma difunde pueden variar a lo largo del tiempo: es posible que se modifiquen algunos de los servicios existentes, que se añadan nuevos o que se eliminen otros. Estas variaciones deben ser monitorizadas por la JSR-272 para ofrecer a la aplicación cliente una relación lo más fidedigna posible de los servicios disponibles en cada momento. A nivel del protocolo FLUTE, los cambios en los servicios se plasman en un cambio en la FDT de la sesión que transporta los contenedores, según se explicó en el apartado 6.4.4. Luego tiene que dotarse a la JSR-272 de alguna forma de conocer cuándo se producen esta modificación, para que vuelva a pedir la relación de ficheros de la sesión y decida qué contenedores han cambiado, para descargarlos y analizarlos a continuación. Para ello se hace uso del mecanismo proporcionado por OpenFluteApi basado en eventos de Windows para crear un disparador de eventos Java desde código nativo. Antes de entrar en los detalles de la implementación es preciso introducir brevemente la gestión de eventos mediante objetos “listener” en Java. En Java, los eventos que se producen en un sistema pueden gestionarse a través de una serie de disparadores que realizan alguna acción sobre otro objeto (llamados “listeners”) cuando se produce un determinado evento. Los objetos “listener” se suscriben a un determinado lanzador para que este realice las acciones oportunas sobre los primeros cuando se active un evento. Por ejemplo, en una interfaz de usuario se puede crear un disparador para que se active cuando se intenta cerrar la aplicación. Si se quiere imponer al usuario un cuadro de diálogo que le avise de que va a cerrar el programa, el objeto que crea ese cuadro de diálogo debe suscribirse al lanzador anterior para que este muestre automáticamente el cuadro al producirse el evento del cierre de la sesión. 74 8.3. Descripción de los módulos JNI en el cliente DVB-H En el caso de la interfaz JNI de la JSR-272, se define el lanzador del evento de actualización de la sesión FLUTE en código nativo. Para la suscripción a este evento se crea el método nativo: public native int addFluteListener ( OpenJniFluteListener listener , int s_id ); donde OpenJniFluteListener es una interfaz cuya definición es la siguiente: public interface OpenJniFluteListener { public void fdtUpdated ( int s_id ); } La implementación de este código nativo crea un hilo llamado updating_monitor que escucha el evento de Windows creado por OpenFluteApi y cuyo manejador se obtiene mediante la llamada a la función getUpdatedEventHandle. Cuando se activa el evento de actualización, updating_monitor llama al método fdtUpdated de la instancia de la implementación de la interfaz OpenJniFluteListener pasado por addFluteListener. El método fdtUpdated es el encargado de realizar las acciones oportunas para llevar a cabo la actualización de los servicios. Cuando no se desea que un objeto OpenJniFluteListener siga escuchando el evento de actualización de la FDT por parte de un determinado objeto, se usa la función removeListener: public native int removeFluteListener ( OpenJniFluteListener listener ); La llamada a este método nativo provoca la finalización del hilo de monitorización del evento de actualización de la FDT (updating_monitor) asociado al objeto listener. 8.3.2.5. Cierre de la sesión El cierre de una sesión FLUTE por parte de la JSR-272 se realiza mediante la llamada al método nativo stopFluteSession: public native int stopFluteSession ( int s_id ); El anterior método se limita a llamar a la función closeFluteSession de OpenFluteApi. 8.3.3. Reproducción de servicios multimedia La reproducción de servicios multimedia en el caso de la JSR-272 se realiza usando la librería adicional MMAPI (JSR-135: Mobile Multimedia API ). La MMAPI proporciona funcionalidades de reproducción de contenidos multimedia a MIDP, incluyéndose parte de ella dentro de la propia MIDP desde la versión 2.0. La arquitectura de MMAPI se basa en cuatro componentes fundamentales: Manager, DataSource, Player y Control. En la figura 8.2 se puede observar la relación entre ellas. El componente Datasource abstrae al programador de cómo se realiza la lectura de los datos desde la fuente (un archivo, un flujo IP , etc). Para crear Players según el Datasource o InputStream que se quiera reproducir, se utiliza la clase Manager. De los Players creados pueden extraerse objetos Control que permiten gestionar la reproducción del contenido multimedia. De un Player se pueden extraer multitud de interfaces Control, dependiendo de los contenidos que se reproduzcan. Entre estas interfaces se encuentran VideoControl y VolumeControl. La variedad de protocolos y tipos de contenidos que pueden ser reproducidos en las implementaciones de la MMAPI suelen ser reducidos. Concretamente, la máquina virtual con la que se ha trabajado durante el desarrollo del cliente DVB-H no soporta la reprodución de contenidos difundidos mediante RTP, ni decodificación de audio en formato AAC ni de vídeo en H.264, formatos que se utilizan para la codificación de los servicios multimedia en DVB-H. Así pues, es necesario recurrir a librerías externas en código nativo que reproduzcan los servicios difindidos en DVB-H y que sean controlados por código Java a través de JNI. Esto implica que 8. Uso de JNI en el cliente DVB-H 75 Figura 8.2: Arquitectura de MMAPI. la implementación de la JSR-272 del proyecto del cliente DVB-H no cumple rigurosamente con la especificación, ya que no basa la reproducción de servicios en MMAPI. Lo que provoca una ruptura con la especificación ante la falta de implementaciones MMAPI que se adapten a las características de los contenidos difundidos. A la fecha de realización de esta memoria no se dispone de librerías capaces de reproducir estos contenidos multimedia, aunque se está en trámites de obtenerlas. Por consiguiente, la conexión entre esta librería de reproducción de contenidos y la JSR-272 aún no se ha podido realizar. 8.3.4. Links externos en la interfaz de usuario Entre los servicios interactivos que ofrece IPDC se encuentran los links externos. Los links externos son enlaces web donde el usuario puede obtener algún servicio, como por ejemplo participar en el foro de un programa de televisión o descargar archivos vía web. Durante el desarrollo de la interfaz de usuario del proyecto del cliente DVB-H, llamada LinceVision, se optó por no implementar un navegador en Java, ya que la mayoría de los teléfonos móviles de gama media-alta dispone de un navegador propio. Sin embargo, para utilizar el navegador propio del terminal es necesario ejecutar código nativo, ya que no es posible llamar a un ejecutable desde una aplicación Java. Para este cometido se usó JNI. El código que contiene todo lo relacionado con JNI dentro de LinceVision se agrupa en la clase JNICommHndlr del paquete es.us.esi.lynx.Comm. Esta clase se define de la siguiente forma: public class JNICommHndlr { private static boolean isLibLoaded = false ; private native void viewLink ( String url ); static boolean try { loadLinkviewer (){ com . ibm . oti . vm . VM . loadLibrary (" linkviewer "); isLibLoaded = true ; } catch ( Throwable t ) { t . printStackTrace (); } return isLibLoaded ; } 76 8.3. Descripción de los módulos JNI en el cliente DVB-H JNICommHndlr cuenta con dos métodos, uno para cargar la librería nativa necesaria para la ejecución del navegador (loadLinkViewer) y el método nativo que está implementado en la librería (viewLink). En cuante al dominio C/C++, la implementación del método nativo consiste en la transformación del objeto String que representa el link externo en una cadena de caracteres nativa, tal y como se ha explicado en el capítulo anterior. Una vez hecho esto se llama a la función de Win32 ShellExecuteEx, que se encarga de abrir el enlace a través del explorador de Windows Mobile (Internet Explorer).