Uso de JNI en el cliente DVB-H

Anuncio
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).
Descargar