Set Top Box - MADS Group
Transcripción
Set Top Box - MADS Group
Facultad de Informática Departamento de Computación PROYECTO FIN DE CARRERA INGENIERÍA INFORMÁTICA Desarrollo de un Set Top Box basado en Linux para un servicio de vı́deo bajo demanda Autor: Samuel Rivas González Tutor: Vı́ctor M. Gulı́as Fernández A Coruña, 30 de Junio de 2003 Especificación Tı́tulo del proyecto: Desarrollo de un Set Top Box basado en Linux para un servicio de vı́deo bajo demanda Clase: Proyecto clásico de ingenierı́a. Nombre del alumno: Samuel Rivas González. Nombre director : Vı́ctor M. Gulı́as Fernández. Nombre del tutor : Vı́ctor M. Gulı́as Fernández. Miembros del tribunal : Miembros suplentes: Fecha de lectura: Calificación: A mi abuelo Luı́s Agradecimientos Supongo que será innecesario decir que no he sido yo el único artı́fice de que llegara el momento de escribir estas lı́neas; son tantas las personas a las que debo mucho por el apoyo y ayuda que he recibido durante todos estos años de carrera que serı́a imposible nombrarlas a todas sin adjuntar un nuevo tomo a este documento. Me gustarı́a, sin embargo, mencionar a algunas personas que han tenido una especial influencia en el hecho que que ahora mismo esté a punto de terminar este proyecto: A mis padres, por darme la oportunidad de llegar hasta el final de este carrera y el apoyo necesario para conseguirlo. A mis hermanos, mis abuelos y mis tı́as que me llevan aguantando tantos años. A Rubén y Julio, por las prácticas y los exámenes que preparamos juntos. A Pablo, por las horas de estudio que compartimos. A todos los que soportaron la convivencia conmigo: Iván durante 5 años y Xavi, Fernando y Julio (otra vez) durante este último año de carrera. A Vı́ctor, por llevarme el proyecto. A la gente del laboratorio LFCIA por toda la ayuda que me ofrecieron durante este trabajo. Y a todos mis amigos que en algún momento de mi vida me han ayudado o sencillamente han compartido parte de su tiempo conmigo. Sin eso serı́a imposible haber llegado hasta aquı́. Muchas gracias. Resumen El objetivo de este proyecto es desarrollar un Set Top Box que permita al usuario final acceder a un servicio de vı́deo bajo demanda a través de una interfaz definida en un servidor remoto. Las limitaciones de espacio a las que se debe atener el software desarrollado para realizar las funcionalidades requeridas en el Set Top Box obligan a la utilización de bibliotecas de bajo nivel para el control del hardware gráfico. Para ello se ha elegido la biblioteca DirectFB, que permite controlar directamente el dispositivo framebuffer de Linux a través de un API escrito en C, que será el lenguaje utilizado para implementar la aplicación encargada de comunicarse con el usuario. La interfaz presentada por el Set Top Box al usuario será definida utilizando el estándar XML. Para ello se ha desarrollado una aplicación que proporcionará los documentos XML necesarios a la aplicación cliente residente en el Set Top Box. Para el desarrollo de esta aplicación se ha elegido el lenguaje funcional Erlang. El software desarrollado para el Set Top Box deberá ser capaz de realizar las siguientes funciones: Comunicarse con el servidor de documentos XML utilizando el protocolo HTTP. Comprender los documentos XML recibidos y mostrar al usuario una interfaz de acuerdo con dichos documentos. Comprender eventos generados por el usuario a través de los periféricos de entrada pertinentes (por ejemplo, el mando a distancia). Realizar las acciones necesarias ante los eventos de entrada generados por el usuario; de acuerdo con las especificaciones de los documentos XML. Dichas acciones comprenden, entre otras, solicitud de nuevos documentos XML, reproducción de vı́deo, cambios en las preferencias del usuario ... La aplicación encargada de proporcionar los documentos XML se desarrollará de forma que realice las funciones mı́nimas necesarias para poder validar el correcto funcionamiento del software implementado para el Set Top Box. Esta aplicación recibirá peticiones HTTP y responderá enviando documentos XML utilizando el mismo protocolo. La información necesaria sobre los distintos usuarios y los medios disponibles se mantendrá en una base de datos. Palabras clave DirectFB, framebuffer, Set Top Box, C, vı́deo bajo demanda, XML, HTTP, Erlang, vı́deo, interfaz. Índice general 1. Introducción 1.1. Introducción . . . . . . . . 1.2. Objetivos . . . . . . . . . 1.3. Etapas del proyecto . . . . 1.4. Estructura de la memoria . . . . 1 1 2 3 4 . . . . . . . . . . . . . . . . . . . 5 6 6 7 8 9 9 12 14 15 17 17 19 20 21 23 23 24 26 27 3. Análisis 3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Protocolo de comunicación . . . . . . . . . . . . . . . . . . . . . . 3.3. Set Top Box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 29 30 31 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. Contextualización 2.1. Vı́deo bajo demanda . . . . . . . . . . 2.1.1. Introducción . . . . . . . . . . . 2.1.2. Servidores de vı́deo comerciales 2.1.3. El servidor VoDKA . . . . . . . 2.2. Set Top Boxes . . . . . . . . . . . . . . 2.2.1. Introducción . . . . . . . . . . . 2.2.2. Middleware . . . . . . . . . . . 2.2.3. Funcionalidades tı́picas . . . . . 2.2.4. Ejemplos comerciales . . . . . . 2.3. Programación gráfica en Linux . . . . . 2.3.1. Introducción . . . . . . . . . . . 2.3.2. Framebuffer . . . . . . . . . . . 2.3.3. Microwindows . . . . . . . . . . 2.3.4. Qt . . . . . . . . . . . . . . . . 2.4. DirectFB . . . . . . . . . . . . . . . 2.4.1. Introducción . . . . . . . . . . . 2.4.2. Arquitectura . . . . . . . . . . . 2.4.3. Términos importantes . . . . . 2.4.4. API . . . . . . . . . . . . . . . i . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii Índice general 3.4. Aplicación DFBCIn . . . . . . . . . 3.4.1. Introducción . . . . . . . . . 3.4.2. Módulo gráfico . . . . . . . 3.4.3. Parser . . . . . . . . . . . . 3.4.4. Conclusiones . . . . . . . . 3.5. Servidor XML (VXMLS) . . . . . . 3.6. Ciclo de Desarrollo . . . . . . . . . 3.6.1. Introducción . . . . . . . . . 3.6.2. Prototipo . . . . . . . . . . 3.6.3. DFBCIn . . . . . . . . . . . 3.6.4. VXMLS . . . . . . . . . . . 3.6.5. Prueba del sistema conjunto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 32 32 34 35 36 37 37 37 39 40 40 . . . . . . . . . . . . . . . . . . . . . . 41 42 42 42 43 45 48 48 48 54 55 55 56 58 59 65 71 75 75 76 78 78 80 5. Implementación de DFBCIn 5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2. Estándares de codificación . . . . . . . . . . . . . . . . . . . . . . 5.2.1. Nombres . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 86 87 87 4. Diseño 4.1. Introducción . . . . . . . . . . . . . . . . . . . 4.2. Definición de la interfaz y objetos del dominio 4.2.1. Introducción . . . . . . . . . . . . . . . 4.2.2. La clase Menu . . . . . . . . . . . . . . 4.2.3. La interfaz Action . . . . . . . . . . . 4.3. Documentos XML . . . . . . . . . . . . . . . . 4.3.1. Introducción . . . . . . . . . . . . . . . 4.3.2. Descripción de los menús . . . . . . . . 4.4. Protocolo de comunicación . . . . . . . . . . . 4.5. Diseño de DFBCIn . . . . . . . . . . . . . . . 4.5.1. División en subsistemas . . . . . . . . 4.5.2. Interacción entre módulos . . . . . . . 4.5.3. Objetos comunes . . . . . . . . . . . . 4.5.4. Diseño del motor de la aplicación . . . 4.5.5. Diseño del módulo VXMLS . . . . . . 4.5.6. Diseño del módulo gráfico . . . . . . . 4.6. Diseño de VXMLS . . . . . . . . . . . . . . . 4.6.1. Introducción . . . . . . . . . . . . . . . 4.6.2. Objetos del dominio . . . . . . . . . . 4.6.3. Modelo entidad-relación . . . . . . . . 4.6.4. Diseño de la capa modelo . . . . . . . 4.6.5. Diseño de la vista y el controlador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Índice general 5.2.2. Objetos . . . . . . . . . . . . . . . . . 5.3. Control de errores . . . . . . . . . . . . . . . . 5.4. Control de concurrencia . . . . . . . . . . . . 5.5. Tipos de datos de uso general . . . . . . . . . 5.5.1. Pilas . . . . . . . . . . . . . . . . . . . 5.5.2. Arrays . . . . . . . . . . . . . . . . . . 5.6. La clase Menu . . . . . . . . . . . . . . . . . . 5.7. La clase Option . . . . . . . . . . . . . . . . . 5.8. La clase Key . . . . . . . . . . . . . . . . . . . 5.9. La interfaz Action . . . . . . . . . . . . . . . . 5.9.1. Introducción . . . . . . . . . . . . . . . 5.9.2. Tipos de datos . . . . . . . . . . . . . 5.9.3. Funciones . . . . . . . . . . . . . . . . 5.9.4. ¿Cómo instanciar una acción? . . . . . 5.9.5. ¿Cómo añadir una nueva acción? . . . 5.10. Implementación del motor de la aplicación . . 5.10.1. Main . . . . . . . . . . . . . . . . . . . 5.10.2. Interface . . . . . . . . . . . . . . . . . 5.10.3. InterfaceInput . . . . . . . . . . . . . . 5.10.4. Mp3Player . . . . . . . . . . . . . . . 5.11. Implementación del módulo VXMLS . . . . . 5.11.1. Módulo HTTP . . . . . . . . . . . . . 5.11.2. Parser VXMLS . . . . . . . . . . . . . 5.11.3. Parser para los documentos menu . . . 5.11.4. Implementación de la fachada VXMLS 5.12. Implementación del Módulo gráfico . . . . . . 5.12.1. Introducción . . . . . . . . . . . . . . . 5.12.2. Estructuración del código . . . . . . . 5.12.3. La fachada graphics.c . . . . . . . . 5.12.4. La inicialización de DirectFB . . . . 5.12.5. Control de eventos de entrada . . . . . 5.12.6. Impresión de menús y texto . . . . . . 5.12.7. Reproducción de vı́deo . . . . . . . . . 5.12.8. Módulo de pruebas . . . . . . . . . . . 5.13. Sistema de propiedades . . . . . . . . . . . . . 5.14. Etapas de codificación . . . . . . . . . . . . . 5.14.1. Desarrollo del núcleo de la aplicación . 5.14.2. Reproducción básica de vı́deo . . . . . 5.14.3. Pruebas en el Set Top Box . . . . . . . 5.14.4. Varias mejoras . . . . . . . . . . . . . 5.14.5. Comunicación HTTP . . . . . . . . . . iii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 90 92 94 94 95 96 98 99 100 100 100 104 107 108 108 108 110 114 116 118 118 120 121 133 136 136 137 137 138 138 140 141 144 145 145 145 146 147 147 148 iv Índice general 5.14.6. Integración con VXMLS . . . . . . . . . . . . . . . . . . . 148 5.14.7. Integración con VoDKA . . . . . . . . . . . . . . . . . . . 149 5.14.8. Reproductor Mp3 y acciones de texto . . . . . . . . . . . . 149 6. Implementación de VXMLS 6.1. Introducción . . . . . . . . . . . . . . . . . 6.1.1. Generalidades sobre Erlang/OTP . 6.1.2. ¿Cómo funciona Inets? . . . . . . . 6.1.3. ¿Cómo funciona Mnesia? . . . . . . 6.1.4. Servidores genéricos . . . . . . . . . 6.1.5. Estructura de la aplicación VXMLS 6.1.6. Tipos de datos . . . . . . . . . . . 6.1.7. Control de errores . . . . . . . . . . 6.2. Capa Modelo . . . . . . . . . . . . . . . . 6.2.1. Tipos de datos . . . . . . . . . . . 6.2.2. Administración de la base de datos 6.2.3. Funciones de la fachada . . . . . . 6.3. Capa Vista . . . . . . . . . . . . . . . . . 6.4. Capa Controlador . . . . . . . . . . . . . . 6.4.1. Generación de la respuesta HTTP . 6.4.2. Traducción de mensajes . . . . . . 6.4.3. Fachada . . . . . . . . . . . . . . . 6.4.4. Creación y destrucción de sesiones . 6.4.5. Propiedades de personalización . . 6.4.6. Generación de menús . . . . . . . . 6.4.7. Composición de objetos Menu . . . 6.4.8. Menús definidos . . . . . . . . . . . 6.4.9. Ejemplos de vxmls get menu . . . 6.5. Extensibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 152 152 152 153 154 154 155 156 157 157 159 159 165 167 167 167 168 168 169 169 171 172 173 175 7. Validación 7.1. Despliegue del sistema . . . . . . . . . . 7.2. Tamaño del software para el Set Top Box 7.3. DFBCIn . . . . . . . . . . . . . . . . . . 7.3.1. Problemas de rendimiento . . . . 7.3.2. Problemas con avifile . . . . . 7.4. VXMLS . . . . . . . . . . . . . . . . . . 7.4.1. Comportamiento general . . . . . 7.4.2. Problemas con Inets . . . . . . . 7.5. Streaming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 179 180 181 181 182 184 184 184 184 . . . . . . . . . Índice general v 8. Conclusiones y lı́neas futuras 8.1. Conclusiones . . . . . . . . . . 8.2. Continuación de este proyecto 8.2.1. DFBCIn . . . . . . . . 8.2.2. VXMLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 187 189 189 190 A. DTD utilizados 193 A.1. DTD utilizado para la definición de menús . . . . . . . . . . . . . 193 A.2. DTD utilizado para la especificación de las respuestas de VXMLS 195 B. API del módulo gráfico de DFBCIn 197 C. Definición de IDirectFBVideoProvider 199 D. Tipos de datos en Erlang 203 E. Instalación y ejecución E.1. Estructura de la distribución E.2. Instalación de VXMLS . . . E.2.1. Compilación . . . . . E.2.2. Instalación . . . . . . E.3. Instalación de DFBCIn . . . E.3.1. Requisitos previos . . E.3.2. Compilación . . . . . E.3.3. Ejecución . . . . . . 205 205 205 205 206 207 207 207 208 F. Glosario Bibliografı́a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 213 Índice de figuras 1.1. Relación entre el usuario y el servidor de VoD . . . . . . . . . . . 2 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7. Configuración interna de un Set Top Box . . . . . . . . . Capas de un Set Top Box . . . . . . . . . . . . . . . . . Prismiq MediaPlayer . . . . . . . . . . . . . . . . . . . . Set Top Box de HMMI . . . . . . . . . . . . . . . . . . . XPC SS50 . . . . . . . . . . . . . . . . . . . . . . . . . . Acceso de una aplicación DirectFB al hardware gráfico Relación entre las interfaces de DirectFB . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 11 16 16 17 25 27 3.1. 3.2. 3.3. 3.4. Prototipo de interfaz con el usuario Creación de parsers con expat . . . Aplicación DFBSee . . . . . . . . . Aplicación DFBPoint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 35 38 39 4.1. Primera aproximación a la clase Menu . . . . . . . . . 4.2. Segunda aproximación a la clase Menu . . . . . . . . . 4.3. Diseño definitivo de la clase Menu . . . . . . . . . . . . 4.4. Acciones para el manejo de menús . . . . . . . . . . . . 4.5. Acciones para el control de vı́deos . . . . . . . . . . . . 4.6. Resto de acciones del sistema . . . . . . . . . . . . . . 4.7. Módulos de DFBCIn . . . . . . . . . . . . . . . . . . . 4.8. Interacción entre los módulos de DFBCIn . . . . . . . . 4.9. Clase Menu en DFBCIn . . . . . . . . . . . . . . . . . 4.10. Clases para el almacenamiento de datos . . . . . . . . . 4.11. Clases del motor . . . . . . . . . . . . . . . . . . . . . 4.12. Detección de eventos de entrada . . . . . . . . . . . . . 4.13. Ciclo de ejecución . . . . . . . . . . . . . . . . . . . . . 4.14. Estados de InterfaceInput e Interface . . . . . . . . . . 4.15. Máquina de estados de Interface . . . . . . . . . . . . . 4.16. Interacción entre los componentes del módulo VXMLS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 43 44 46 47 47 56 57 59 60 61 62 62 63 64 66 vii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . viii Índice de figuras 4.17. Clases del módulo VXMLS . . . . . . . . . . . . . . . . . . . . . . 4.18. Clases utilizadas para la construcción del parser para descripciones de menús . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.19. Clases utilizadas para la construcción módulo gráfico . . . . . . . 4.20. Máquina de estados de VideoPlayer . . . . . . . . . . . . . . . . . 4.21. Aspecto visual de las dos implementaciones del módulo gráfico . . 4.22. Patrón Model/View/Controller en el diseño de VXMLS . . . . . . 4.23. Objetos del dominio . . . . . . . . . . . . . . . . . . . . . . . . . 4.24. Objetos valor utilizados para definir objetos Menu . . . . . . . . . 4.25. Objetos valor utilizados por VXMLS . . . . . . . . . . . . . . . . 4.26. Modelo entidad-relación . . . . . . . . . . . . . . . . . . . . . . . 4.27. Clases de la vista y el controlador de VXMLS . . . . . . . . . . . 7.1. 7.2. 7.3. 7.4. Despliegue del sistema final . . . . . . . DFBCIn funcionando en el Set Top Box Capas de acceso al servicio de streaming Cluster servidor de vı́deo utilizado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 68 73 74 75 76 77 77 78 79 81 180 181 185 186 Capı́tulo 1 Introducción Índice General 1.1. 1.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.3. Etapas del proyecto . . . . . . . . . . . . . . . . . . . . 3 1.4. Estructura de la memoria . . . . . . . . . . . . . . . . 4 Introducción Un servicio de vı́deo bajo demanda (VoD de aquı́ en adelante para simplificar) permite a los usuarios finales el acceso a medios de vı́deo de forma interactiva sin ningún tipo de programación preestablecida. Para proporcionar este tipo de servicio es necesario un servidor de streaming que sea capaz de distribuir los medios en un flujo continuo de datos de forma que puedan ser reproducidos mientras se reciben. Para el usuario final, el servidor de streaming puede ser visto como un gran conjunto de medios sin estructura a los que puede acceder. Permitir el uso de esos recursos pasa por la obligación de desarrollar un sistema que posibilite el acceso de forma controlada a dichos medios y que los presente de forma comprensible y ordenada. Este es el objetivo que persigue este proyecto: desarrollar un Set Top Box que proporcione acceso desde el hogar a los medios distribuidos por un servidor VoD. 1 2 Capı́tulo 1. Introducción Para mantener centralizado el control de las interfaces presentadas a los usuarios, el software desarrollado seguirá el paradigma cliente-servidor, de forma que el software que controla el funcionamiento del Set Top Box obtendrá la información necesaria de un servidor, con el que se comunicará utilizando el estándar XML sobre el protocolo HTTP. Flujo Servidor VoD Actor Set Top Box n 1 Servidor XML Figura 1.1: Relación entre el usuario y el servidor de VoD El desarrollo de la aplicación cliente se ha realizado utilizando el lenguaje C y las bibliotecas DirectFB [1], para el control del hardware gráfico, y expat [2], para la implementación del parser XML. Esta aplicación recibe el nombre de DFBCIn (acrónimo de DirectFB Client Interface). El servidor XML se ha implementado utilizando el lenguaje funcional Erlang [3] y diversas herramientas proporcionadas por la plataforma de desarrollo Erlang/OTP [4]. Como servidor de VoD se utilizará VoDKA [5]. Por ello, a la aplicación encargada de servir los documentos XML se le ha bautizado como VXMLS (VoDKA XML Server ). 1.2. Objetivos El principal objetivo del proyecto es el diseño y desarrollo de un Set Top Box basado en Linux que permita el acceso de forma controlada al servidor de VoD VoDKA. El software desarrollado deberá ajustarse a las restricciones de espacio Etapas del proyecto 3 presentadas por este tipo de sistemas. Como objetivo secundario también se desarrollará una aplicación que actúe como servidora de los documentos XML que necesitará el Set Top Box para generar la interfaz que presentará al usuario. Tanto el desarrollo del Set Top Box como de la aplicación encargada de proporcionar la descripción de la interfaz deberán cumplir los siguientes objetivos: Permitir al usuario acceder a los servicios de VoDKA a través de un televisor convencional. Controlar el acceso a los medios, manteniendo información sobre los usuarios que disfrutan de los mismos. Permitir a los usuarios elegir ciertas configuraciones para adaptar los servicios a sus necesidades. Por ejemplo, cada usuario deberı́a poder elegir el idioma en el que se le proporcione la información que precise. Mantener la interfaz presentada a los usuarios siempre actualizada, pudiendo variar según, por ejemplo, la hora del dı́a, las preferencias del usuario, etc. 1.3. Etapas del proyecto Durante el desarrollo del proyecto se ha pasado por las siguientes etapas: 1. Estudio de las diferentes alternativas existentes para programar accediendo directamente al dispositivo framebuffer de Linux. 2. Elección de una de las alternativas (en este caso DirectFB) e implementación de una aplicación de ejemplo para comprobar su idoneidad. 3. Elección de la estructura de los documentos XML que deberá interpretar la aplicación cliente. 4. Diseño e implementación de la aplicación cliente DFBCIn siguiendo el paradigma de desarrollo incremental. Como no se dispone todavı́a del servidor de documentos los incrementos iniciales trabajarán con documentos estáticos: en las primeras fases con ficheros y, una vez avanzado el desarrollo, solicitando los documentos de un directorio remoto utilizando el protocolo HTTP. 4 Capı́tulo 1. Introducción 5. Estudio de las herramientas proporcionadas por Erlang/OTP para el desarrollo de aplicaciones HTTP. 6. Diseño e implementación del servidor de documentos XML VXMLS. 7. Último incremento de la aplicación cliente, en el que se añadirá la funcionalidad necesaria para permitir la comunicación con el servidor implementado en el paso anterior. 8. Integración del sistema resultante con VoDKA. 9. Validación del sistema. 1.4. Estructura de la memoria En el capı́tulo 2 se expone una introducción a la tecnologı́a de vı́deo bajo demanda, en la que se hace una breve descripción del servidor VoDKA. También se exponen las caracterı́sticas generales de los Set Top Boxes y de la programación gráfica bajo Linux, finalizando con un apartado dedicado a los aspectos generales de la biblioteca DirectFB. Los capı́tulos 3 y 4 describen las fases de análisis y diseño de este proyecto. La fase de implementación se divide dos capı́tulos: en el capı́tulo 5 se documenta la implementación del software diseñado para el Set Top Box y en el capı́tulo 6 se documenta la implementación del servidor de documentos XML. En el capı́tulo 7 se documenta la fase de validación del sistema. Por último, en el capı́tulo 8 se exponen las conclusiones obtenidas tras la finalización del proyecto y se exponen una serie de lı́neas de trabajo que quedan abiertas. Se incluyen varios apéndices con diversa información que puede ser de utilidad para comprender alguna de las secciones de esta memoria, información sobre la instalación y ejecución del software desarrollado y un glosario en el que se explican los acrónimos utilizados a lo largo de este documento. Capı́tulo 2 Contextualización Índice General 2.1. Vı́deo bajo demanda . . . . . . . . . . . . . . . . . . . 6 2.1.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 6 2.1.2. Servidores de vı́deo comerciales . . . . . . . . . . . . . 7 2.1.3. El servidor VoDKA . . . . . . . . . . . . . . . . . . . 8 2.2. Set Top Boxes . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 9 2.2.2. Middleware . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2.3. Funcionalidades tı́picas . . . . . . . . . . . . . . . . . 14 2.2.4. Ejemplos comerciales . . . . . . . . . . . . . . . . . . . 15 2.3. Programación gráfica en Linux . . . . . . . . . . . . . 17 2.3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 17 2.3.2. Framebuffer . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3.3. Microwindows . . . . . . . . . . . . . . . . . . . . . . . 20 2.3.4. Qt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.4. DirectFB . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 23 2.4.2. Arquitectura . . . . . . . . . . . . . . . . . . . . . . . 24 2.4.3. Términos importantes . . . . . . . . . . . . . . . . . . 26 2.4.4. API . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5 6 Capı́tulo 2. Contextualización 2.1. Vı́deo bajo demanda 2.1.1. Introducción La intención de un servicio de VoD es proporcionar un servicio de vı́deo en el que múltiples usuarios pueden solicitar un objeto de vı́deo en cualquier momento. Las posibles aplicaciones de un sistema de este tipo pueden ser: pelı́culas bajo demanda, noticias interactivas, aprendizaje a distancia, etc. Un sistema de este tipo debe incluir los siguientes elementos: El servidor de vı́deo: Es similar a un servidor de ficheros normal, pero a diferencia de estos, debe priorizar el mantenimiento del flujo de datos constante. Un servicio de vı́deo no puede proporcionar el vı́deo completo ante una petición, puesto que el tamaño de los datos es demasiado grande. En lugar de esperar a recibir el vı́deo completo, el terminal cliente recibe un flujo constante de datos, que irá reproduciendo a medida que le van llegando. Por lo tanto, el servidor de vı́deo tiene que mantener el flujo de datos de manera ininterrumpida y con la cadencia adecuada para que la reproducción del vı́deo en el terminal sea continua. Esta técnica se conoce como streaming. Para este proyecto se ha utilizado VoDKA como servidor de vı́deo. El servidor de aplicaciones: Debe incluir dos módulos básicos: • La aplicación de usuario: Garantiza la conectividad de los usuarios desde sus puestos remotos y le permite explorar entre los contenidos a los que está autorizado. Asimismo, esta aplicación también controla los datos y estadı́sticas de los distintos usuarios registrados. En el caso de este proyecto, se desarrollará la aplicación de usuario VXMLS. • La aplicación de gestión: La aplicación de gestión de un sistema de vı́deo bajo demanda incluye: alta, clasificación, modificación y borrado de los contenidos y los meta-datos; gestión y asignación de los perfiles de usuario; asignación de claves de acceso para los distintos niveles de contenido y monitorizado en tiempo real del estado del sistema. Los datos manejados por esta aplicación estarán en una base de datos a la que también accederá la aplicación de usuario. En [6] se ha desarrollado una aplicación de gestión para VoDKA que permite gestionar y administrar los usuarios y medios asociados al servidor VoDKA. Los codificadores en lı́nea: Permiten ofrecer material no pregrabado. Deben ser capaces de obtener el vı́deo y comprimirlo en tiempo real para proporcional material visual en directo. Vı́deo bajo demanda 7 Los terminales: En ellos es donde el usuario puede ver el vı́deo que ha solicitado. Estos terminales pueden ser de dos tipos: ordenadores personales o Set Top Boxes (En este proyecto se desarrolla un terminal del segundo tipo). Los terminales reciben el vı́deo, lo decodifican y lo presentan por pantalla al usuario. Normalmente, el software que controla el terminal también debe proporcionar la posibilidad de comunicarse con la aplicación de usuario. En este proyecto se desarrolla la aplicación DFBCIn para este propósito. La red : Deben tenerse en cuenta los factores de QoS, protocolos unicast y multicast y otros factores especı́ficos para la transmisión de vı́deo. 2.1.2. Servidores de vı́deo comerciales En los últimos años las empresas más importantes que comercializan productos multimedia han desarrollado sistemas relacionados con el vı́deo bajo demanda. Algunas de las soluciones están más adaptadas a redes de bajo ancho de banda, como es el caso de RealVideo Server, de Real Networks, que hacen uso de tecnologı́as de caché de los streams para optimizar el uso de una red con las caracterı́sticas de Internet, o Microsoft Windows Media Server, que utiliza protocolos propietarios y puede ser utilizado en entornos de un mayor ancho de banda con un rendimiento menor. Otras soluciones se enfocan más a entornos LAN o WAN, con mayor disponibilidad de ancho de banda. Las más representativas son Darwing Streaming Server [7], de Apple, que es la versión de código abierto de Quicktime Streaming Server, y sólo es un servidor de streaming RTP, sin incluir la gestión del almacenamiento; DB2 Digital Library Video Charger [8], de IBM; Oracle Video Server [9, 10], quizá el más utilizado de todos, con arquitectura cliente-servidor, pero con limitaciones de escalabilidad; Kassenna MediaBase, una evolución de WebForce MediaBase, de SGI, con caracterı́sticas comunes al diseño de VoDKA (modular, separación de funciones de adquisición, distribución y streaming, basado en sistemas UNIX), pero una menor flexibilidad y capacidad de adaptación; Philips WebCine Server [11], servidor de streaming MPEG4 basado en Linux; Cisco IP/TV [12], solución cerrada con herramientas orientadas al mercado de formación; y el sistema de SUN: StorEdge Media Central [13]. También existen soluciones de caja negra, que consisten en la combinación de un hardware especializado con alguno de los productos anteriores. En el contexto académico la mayorı́a de los proyectos poseen un carácter altamente experimental. En [14] se analiza una solución jerárquica para la construc- 8 Capı́tulo 2. Contextualización ción de servidores multimedia. En Stony Brook Video Server Project [15, 16, 17] se intenta crear un servidor distribuido que proporcione al usuario caracterı́sticas de indexación, búsqueda y streaming de vı́deos, y está desarrollada con una interesante arquitectura cliente servidor, adaptada para redes locales. En [18] se presenta una alternativa totalmente distinta a la expuesta en este artı́culo, utilizando una arquitectura de memoria compartida. Un estudio más detallado de algunas de estas soluciones se puede encontrar en [19]. 2.1.3. El servidor VoDKA 2.1.3.1. Caracterı́sticas generales de VoDKA El sistema VoDKA es un servidor de streaming multimedia bajo demanda extremadamente flexible, pero optimizado para el entorno de una operadora de telecomunicaciones. Caracterı́sticas principales: Multiprotocolo: soporte para streaming de medios arbitrarios CBR por HTTP, MPEG-4 y Quicktime sobre RTSP/RTP, MPEG Layer3 (MP3) sobre RTP, MP3 sobre Icecast y ASF sobre HTTP con control del medio. Multiproveedor: previsión de múltiples proveedores de medios integrados en una única red de distribución de contenidos, cada uno en su dominio administrativo. Flexibilidad en el almacenamiento: posibilidad de utilizar almacenamiento en disco o cinta propio, servidores webDAV [20] u otros servidores VoDKA encadenados como fuentes de medios. Modularidad: el sistema está compuesto de una serie de módulos que se interconectan de forma flexible para adaptarse a configuraciones muy diferentes. Distribuido: los distintos componentes del sistema se comunican entre sı́ de forma transparente a su localización. El servidor puede configurarse de forma adaptada a la topologı́a de red, reduciendo costes de despliegue. Además, se modelan mediante funciones de coste las caracterı́sticas de red y equipos para optimizar la utilización de enlaces y equilibrar la carga, asegurando calidad de servicio. Set Top Boxes 9 Escalabilidad: gracias a su arquitectura, es posible configurar tanto un microservidor contenido en un equipo embebido como un cluster de cientos de nodos, e incluso metaclusters o agrupaciones de clusters. 2.1.3.2. Plataformas soportadas La mayor parte de VoDKA está desarrollada en Erlang/OTP (Open Telecom Platform). El código Erlang se ejecuta dentro de un runtime (ERTS), que incluye una máquina virtual (en la que se ejecuta o bien bytecode o bien código nativo), bibliotecas y una interfaz al sistema operativo y al hardware común a todas las plataformas. Todos los módulos desarrollados en Erlang, por tanto, pueden, en principio, instalarse sobre cualquier plataforma soportada por el ERTS: Solaris, AIX, BSD, VxWorks, Win32 y Linux sobre múltiples arquitecturas (x86, SPARC, ARM, PowerPC, S/390). Algunas partes de VoDKA están desarrolladas en C y C++ , y puede que no estén disponibles para todas las arquitecturas. 2.1.3.3. Red de interconexión Cuando una instalación de VoDKA abarca más de un nodo fı́sico, se supone que existe una red IP que interconecta todos los nodos entre sı́. Es posible, sin embargo, dividir la red de nodos VoDKA en clusters independientes, pero complica el mantenimiento. En casos excepcionales es factible utilizar una red no IP, pero no es aconsejable en absoluto. VoDKA utiliza toda la infraestructura del sistema operativo para acceder a la red, con lo que podrá emplear cualquier interfaz soportado por éste. Habitualmente se desarrolla y prueba sobre interfaces Ethernet, Fast Ethernet y Gigabit Ethernet en Linux, y Fast Ethernet y ATM con LANE en Solaris. 2.2. Set Top Boxes 2.2.1. Introducción Un Set Top Box puede ser visto como un dispositivo que permite realizar ciertas operaciones como el acceso a Internet o la recepción de vı́deo digital desde el hogar, utilizando un televisor convencional como interfaz de salida y (posiblemente) un mando a distancia como dispositivo de entrada. En algunas ocasiones 10 Capı́tulo 2. Contextualización se denomina a los Set Top Boxes como decodificadores, o IRDs. Como ejemplos conocidos de Set Top Boxes se pueden citar los decodificadores de Canal+, Canal Satélite, Vı́a Digital, Quiero TV, etc. Internamente, un Set Top Box es similar a un ordenador personal convencional, pero con algunas modificaciones para adaptarlo especı́ficamente a la ejecución de aplicaciones multimedia, como pueden ser decodificadores MPEG hardware, salidas de televisión digital, salida de audio estéreo, puerto de infrarrojos para el mando a distancia, etc. Como medios de almacenamiento secundario pueden disponer de un disco duro y/o memoria flash. También deben disponer de algún tipo de dispositivo de red para recibir la información multimedia, receptores de señal de televisión y un dispositivo de acceso condicional para controlar la utilización de los servicios de pago tales como el pago por visión. Codificador NTSC/PAL Puerto de infrafojos Control de pantalla DACs y generacion de graficos de audio Decodificador de video via satelite Logica de Control DACs de video video por cable Decodificador de Control de E/S Decodificador de Memoria CPU Modem video via terrestre Distribucion de reloj Decodificador MPEG RS232 xDSL Logica de Control Unidad de Acceso Condicional Interfaz HDD Disco Duro/Flash IEEE1394 USB Figura 2.1: Configuración interna de un Set Top Box Sobre la capa hardware se apilan las capas software necesarias para el correcto funcionamiento del Set Top Box, de forma similar a como se harı́a en un PC, como se puede ver en la figura 2.2. 1. Receptor de televisión digital : Dispositivos hardware necesarios para la recepción de la información necesaria. En la capa hardware también se con- Set Top Boxes 11 Aplicaciones Capa de aplicacion API Plataforma Software Middleware Sistema Operativo de Tiempo Real Capa RTOS Drivers Receptor de television digital Capa Hardware Figura 2.2: Capas de un Set Top Box templan los dispositivos básicos para el funcionamiento del sistema (memoria, CPU, etc), pero se denomina receptor de televisión digital para señalar que no es una plataforma hardware genérica, sino que está orientada a esa tarea en particular. 2. Sistema Operativo: Se utilizan sistemas operativos de tiempo real (RTOS) como pSOS, WindowsCE, Linux, VxWorks, OS20 o Nucleus. 3. Plataforma software: También conocido como middleware. Conjunto de módulos que facilitan un desarrollo más eficiente. Proporciona un API para cada lenguaje de programación soportado. Para la implementación del API, el middleware puede incluir un gestor de aplicaciones, una máquina virtual, las bibliotecas, el motor interactivo y las bases de datos. 4. Aplicaciones: Esta capa no necesita estar residente permanentemente, sino que puede ser descargada bajo demanda. Un Set Top Box está pensado para ser una máquina asequible, que el usuario colocará en su hogar cerca del televisor para que realice las tareas necesarias que permitan el disfrute de un servicio de vı́deo digital. Por lo tanto, es necesario aplicar ciertas restricciones sobre el hardware a utilizar. 12 Capı́tulo 2. Contextualización Debe ser razonablemente económico, esto implica que la CPU no tendrá toda la potencia de las últimas generaciones además de constreñir la cantidad de memoria RAM y la capacidad del dispositivo de almacenamiento secundario utilizado (disco duro o memoria flash). No debe ser voluminoso ni ruidoso. Esta restricción afecta a la utilización de discos duros y de chips o fuentes de alimentación que requieran dispositivos de ventilación para su correcta refrigeración. Para compensar las restricciones de potencia de la CPU, se suelen incluir decodificadores MPEG hardware para liberar a la CPU de la carga de esta tarea. Para disminuir los costes, ruidos y consumo, los Set Top Boxes no suelen incluir disco duro, sino que utilizan memoria flash como dispositivo de almacenamiento secundario. Además, es deseable que el bloque de memoria utilizado sea de la menor capacidad posible para no encarecer el sistema final. 2.2.2. Middleware En la actualidad existen varias organizaciones desarrolladoras de estándares para el desarrollo de plataformas middleware. DVB : El proyecto DVB [21] es un consorcio compuesto por alrededor de 300 operadores de vı́deo, operadores de red, desarrolladores de software, fabricantes, etc. creado para el desarrollo de estándares para la transmisión de televisión digital y servicios de datos. De este proyecto han salido cuatro estándares mundialmente aceptados: • DVB-S Estándar para la transmisión de vı́deo digital vı́a satélite. • DVB-T Estándar para la transmisión de vı́deo digital por vı́a terrestre. Actualmente no está instaurado en América, en donde se utiliza ATSC, ni en Japón, en dónde se sigue el estándar ISDB-T. • DVB-C Estándar para la transmisión de vı́deo digital por cable. • MHP Estándar de televisión digital interactiva. Define un interfaz genérico entre las aplicaciones interactivas y el terminal que las debe ejecutar. Un middleware que proporcione el interfaz MHP podrá ejecutar las aplicaciones desarrolladas de acuerdo con este estándar independientemente de la plataforma hardware que utilice. ATSC : Es una organización internacional sin ánimo de lucro para desarrollar estándares voluntarios para televisión digital. ATSC ha desarrollado Set Top Boxes 13 numerosos estándares para transmisión de vı́deo, codificación, compresión de audio digital, acceso condicional, etc. En cuanto a estándar de middleware ATSC ha propuesto el estándar DASE [22]. OpenCable: Es una iniciativa para dotar a la industria de televisión por cable de los estándares necesarios para desarrollar servicios interactivos. Como estándar de middleware propone OCAP [23]. JavaTV [24]: Especifica un API que proporciona una plataforma ideal de desarrollo y utilización de aplicaciones sobre la plataforma Java. Dicho API proporciona operaciones para realizar streaming tanto de audio como de vı́deo, acceso condicional, control de canales y control de gráficos en pantalla. Algunas de las plataformas más utilizadas son: OpenTV [25]: Es la plataforma más utilizada en la actualidad para el desarrollo de sistemas de televisión digital sobre Set Top Boxes. Es una solución comercial ampliamente utilizada por compañı́as de televisión interactiva. Proporciona la plataforma middleware, aplicaciones, herramientas de desarrollo y soporte técnico. Las aplicaciones proporcionadas por OpenTV abarcan campos como el t-comercio, el streaming, telebanca, juegos, navegadores, etc. Es la tecnologı́a utilizada por el Set Top Box de Vı́a Digital y QueroTV. Media Highway: Es un middleware propietario desarrollado por Canal+ Technologies [26]. Soporta los estándares DVB, OpenCable, y ATSC. Puede ejecutar aplicaciones interactivas escritas en lenguajes soportados por dichos estándares como Java, HTML o XDML. Es el middleware que utiliza el Set Top Box de Canal Satélite. Alticast [27]: Construye aplicaciones de televisión digital para una cantidad importante de proveedores en Asia. Su middleware soporta los estándares de aplicación MHP y DASE. Como máquina virtual en la que ejecutar las aplicaciones utiliza AltiJVM, que es una implementación de la máquina virtual de Java. TV Navigator : Desarrollado por Liberate [28], es un middleware con una representación importante en los Set Top Boxes de Estados Unidos. Soporta Java y HTML. 14 Capı́tulo 2. Contextualización MSTV [29]: Es el middleware desarrollado por Microsoft. Engloba varios productos como WebTV, PersonalTV y UltimateTV. Estos productos permitieron obtener a Microsoft el liderazgo en el mundo de la televisión interactiva, pero con el tiempo han ido perdiendo viabilidad comercial y están siendo abandonados. Ahora, Microsoft concentra sus esfuerzos en un middleware basado en su sistema operativo WindowsCE para compañı́as de cable. NDS Core [30]: Middleware desarrollado por NDS, principalmente instalado en Set Top Boxes de América Latina y Asia. Pretende ser compatible tanto con MHP como con OCAP y soporta Java y HTML. 2.2.3. Funcionalidades tı́picas Además de la recepción y reproducción de canales de audio y vı́deo digital, los Set Top Boxes pueden realizar otro tipo de actividades como pueden ser la utilización de Internet, descarga de juegos, telecompra, telebanca, tienda virtual, solicitud de información de forma interactiva ... Para los Set Top Boxes comerciales más conocidos en España, las funcionalidades van desde la más básica del Set Top Box de Canal+ hasta las más avanzadas ofrecidas por Canal Satélite y Vı́a Digital. El Set Top Box de Canal+ simplemente decodifica la señal recibida para permitir al abonado visualizar la programación que no se distribuye en abierto. Los Set Top Boxes de Canal Satélite y Vı́a Digital ofrecen servicios interactivos, además de proporcionar el acceso a los diferentes canales de vı́deo. Algunas de las operaciones que permiten realizar son: Compra de estrenos o partidos. Consulta de información bursátil. Operaciones bancarias. Recarga de telefonı́a. Servicios de mensajes a móviles. Información deportiva. Guı́a de programación. Set Top Boxes 15 Comercio por televisión (t-comercio). Teniendo en cuenta las posibilidades ofrecidas por un Set Top Box, se puede establecer la siguiente clasificación: Set Top Box para TV por broadcast: Es el nivel más básico de Set Top Box. No dispone de canal de retorno, simplemente recibe una señal de vı́deo que decodifica para que el usuario pueda disfrutarla (por ejemplo, el decodificador de Canal+). Set Top Box para TV digital : Dispone de un canal de retorno para permitir la interacción del usuario con el proveedor del servicio de TV. Set Top Box avanzado: Tiene caracterı́sticas muy similares a un PC: tienen una CPU potente, disco duro de gran capacidad, etc. Disponen de acceso de alta velocidad a Internet, posibilidad de grabación digital de vı́deo, juegos, capacidad para el envı́o y recepción de correo electrónico, etc. Sidecar : Proporciona un nuevo canal de recepción de datos para trabajar conjuntamente con el canal recibido por el cliente en su Set Top Box original. Set Top Box hı́brido: Set Top Box especializado para televisión por cable y que incluye otras funciones, por ejemplo, reproducción de DVDs. 2.2.4. Ejemplos comerciales 2.2.4.1. Prismiq MediaPlayer Este Set Top Box obtiene medios audiovisuales de la red y los presenta de forma ordenada, permitiendo al usuario disfrutarlos en un televisor. Puede reproducir vı́deo en formato MPEG 1 y 2 y DIVX con ayuda de un PC. La configuración de este Set Top Box es bastante simple, ya que el trabajo de localizar, adaptar y enviar los medios lo realiza una aplicación servidora que debe ser instalada en un PC: CPU : microprocesador NEC uPD61130 32-bit MIPS con un decodificador MPEG integrado. Memoria: 64 megabytes de SDRAM Interfaz de red : 10/100 Fast Ethernet integrada. 16 Capı́tulo 2. Contextualización Figura 2.3: Prismiq MediaPlayer Salidas de vı́deo: S-Video y vı́deo compuesto. Salidas de audio: S/P DIF y RCA (estéreo). Software: Linux 2.4, navegador y cliente AOL integrados. 2.2.4.2. Set Top Box de HMMI En este Set Top Box se hicieron algunas pruebas al principio del proyecto, pero el soporte de framebuffer para la tarjeta gráfica que tiene dio bastantes problemas y no se consiguió hacer funcionar correctamente la aplicación DFBCIn en él. Figura 2.4: Set Top Box de HMMI 2.2.4.3. Shuttle Spacewalker XPC SS50 Este es el Set Top Box que se utilizó para la validación del sistema, se le instaló un receptor de infrarrojos para recibir la señal del mando a distancia y Programación gráfica en Linux 17 un disco duro para facilitar las pruebas. La configuración original utiliza una memoria flash como almacenamiento secundario. Figura 2.5: XPC SS50 Las caracterı́sticas de este Set Top Box son muy próximas a las de un PC: CPU : Intel Pentium 4 Socket478. Tarjeta de sonido: C-Media Electronics Inc CM8738 integrada. Tarjeta de vı́deo: Silicon Integrated Systems SiS650/AGP VGA Display Adapter integrada. Memoria: 2 slots DIMM con capacidad de hasta 2 gigabytes de memoria DDR. Interfaz de red : 10/100 Fast Ethernet integrada. Salida de televisión/S-Video. Slots de expansión AGP y PCI. Puertos USB y Firewire. 2.3. Programación gráfica en Linux 2.3.1. Introducción Hoy en dı́a, la programación de interfaces gráficas para Linux se hace habitualmente sobre el sistema X Windows que proporciona una capa de abstracción 18 Capı́tulo 2. Contextualización sobre el hardware gráfico y un API para desarrollar interfaces basadas en ventanas y eventos (tanto de teclado como de ratón). El sistema X Windows se ha transformado en el estándar de facto para la programación gráfica en Linux y Unix. X Windows permite el desarrollo de aplicaciones gráficas independientes del sistema operativo y la plataforma hardware, es transparente a la red y soporta numerosos entornos de escritorio muy populares hoy en dı́a. Sin embargo, La utilización del sistema X Windows supondrı́a una sobrecarga demasiado pesada para el Set Top Box. Como se dijo en la sección 2.2.1, un sistema de este tipo debe ser asequible y silencioso, lo que implica poco espacio en el almacenamiento secundario y una CPU de potencia limitada. Para aprovechar el hardware disponible será necesario utilizar algún framework de orientación menos genérica que X Windows. Por lo tanto será necesario buscar otras alternativas para la programación de la interfaz gráfica. La idea es bajar el nivel de programación y acceder directamente al hardware gráfico. Se utilizará un lenguaje de nivel medio-bajo para escribir directamente en el framebuffer (ver la sección 2.3.2). Sin embargo, trabajar directamente sobre el framebuffer es complicado y hace el código dependiente de la plataforma (si se quieren utilizar las posibilidades de aceleración gráfica). Por ello, se han estudiado distintos frameworks que proporcionen una mayor abstracción en forma de un API independiente del hardware gráfico. Las opciones estudiadas fueron: Microwindows [31]: Proporciona un API escrito en C que permite realizar operaciones gráficas básicas (rectángulos, cı́rculos, ...) y operaciones con ventanas. Está orientado a la implementación de aplicaciones gráficas con ventanas, pero prescindiendo de sistemas como Microsoft Windows o X Windows. Qt [32]: Proporciona un API C++ y una gran colección de objetos que permiten realizar aplicaciones con un look and feel nativo independiente de la plataforma. Para este proyecto se estudió su versión embebida (Qt/Embedded), que trabaja directamente sobre el framebuffer para evitar sobrecargas. Programación gráfica en Linux 19 DirectFB [33]: Proporciona un API escrito en C, pero con diseño objetual. Está pensado para realizar operaciones gráficas con aceleración, evitando la utilización del sistema X Windows, en favor de la utilización del dispositivo framebuffer directamente [1]. Proporciona una abstracción de proveedor de imagen, de proveedor de fuentes y de proveedor de vı́deo; Tiene operaciones para manejar la entrada ya sea por teclado, mando a distancia, ratón, joystick o pantalla táctil y define un sistema de gestor de ventanas. El framework elegido para la programación gráfica fue DirectFB, ya que proporciona muchas opciones interesantes para el desarrollo de software para un Set Top Box. Principalmente, la opción más útil es la abstracción que proporciona para el manejo de vı́deo, que desacopla la aplicación del formato de vı́deo utilizado. 2.3.2. Framebuffer El framebuffer es una parte de la memoria RAM de la computadora en la que se almacena la información para una imagen o frame. Esta información consiste tı́picamente en los valores de color para cada pixel de la pantalla. El kernel de Linux ha incorporado (a partir de su versión 2.1.107) el dispositivo framebuffer [34], con el que permite la programación gráfica en una consola. Este dispositivo fue desarrollado con el objetivo inicial de permitir al kernel de Linux emular una consola de texto en sistemas como Apple Macintosh, que no soportan el modo texto VGA. Finalmente, el dispositivo framebuffer pasó a estar completamente integrado en el kernel de Linux y se espera que llegue a ser el modo estándar de acceso al hardware gráfico. El dispositivo framebuffer presenta una abstracción del hardware gráfico y permite a las aplicaciones software acceder al hardware gráfico utilizando una interfaz bien definida. De esta forma, el software no necesita conocer los detalles de bajo nivel (registros hardware), excepto para la aceleración hardware. La utilización del dispositivo framebuffer supone las siguientes ventajas: Proporciona un método unificado para el acceso al hardware gráfico a través de diferentes plataformas. Los controladores pueden ser compartidos entre distintas arquitecturas, lo que disminuye la duplicación de código. 20 Capı́tulo 2. Contextualización Proporciona control tanto para una como para varias pantallas activadas, se soportan hasta 8 dispositivos framebuffer (8 pantallas). El dispositivo framebuffer puede ser utilizado desde el espacio de usuario accediendo al dispositivo especial /dev/fb[0-7], de la misma forma que se accede al resto de dispositivos. E/S normal : Se puede acceder a los contenidos del framebuffer mediante las operaciones de lectura y escritura read() y write(). nmap: Utilizando la llamada nmap() se puede hacer mapping de los contenidos del framebuffer en la memoria de usuario, de forma que el framebuffer se vuelve una pieza de memoria accesible. También se puede hacer mapping de los registros MMIO en el espacio de usuario, pero para ello es necesario desactivar la aceleración hardware para la consola de texto para evitar conflictos en el acceso a dichos registros. ioclt: La llamada ioctl() permite realizar otro tipo de operaciones como cambiar el modo gráfico, obtener información sobre el hardware subyacente... Cada dispositivo framebuffer particular puede definir ioctls para operaciones especificas del dispositivo de ser necesario. No hay código genérico de aceleración gráfica en el kernel (solo para consola de texto), todas las operaciones genéricas de aceleración hardware deberán hacerse desde el espacio de usuario utilizando mapping sobre los registros MMIO. 2.3.3. Microwindows Microwindows [31] es un proyecto de Código Abierto que tiene como objetivo dotar las caracterı́sticas de los sistemas de ventanas modernos a dispositivos y plataformas más pequeños. Permite desarrollar aplicaciones y probarlas en el escritorio de Linux para luego compilarlas para el dispositivo deseado. Proporciona dos APIs, uno compatible con el API del sistema de ventanas de los sistemas Win32/WinCE, llamado Microwindows, y otro similar al de Xlib, la biblioteca de desarrollo de aplicaciones para el sistema X Windows, llamado Nano-X. Bajo Linux es más utilizado el API Nano-X ya que soporta la arquitectura cliente-servidor. Actualmente funciona en sistemas Linux de 32 bits utilizando el soporte framebuffer del kernel (ver sección 2.3.2), bajo el sistema X Windows (útil para el Programación gráfica en Linux 21 desarrollo) y utilizando la biblioteca SVGAlib 1 . Adicionalmente, Microwindows ha sido portado para los sistemas operativos ELKS (Linux embebido de 16 bits), MS-DOS, tanto en modo protegido como en modo real, y RTEMS (sistema operativo de tiempo real). Las desventajas que presenta este framework con respecto a los objetivos que se persiguen en este proyecto son: Está orientado a la creación de interfaces con ventanas. La interfaz que se pretende desarrollar está basada en un sistema de menús a pantalla completa. El API proporcionado no es orientado a objetos, esto complicarı́a la estructuración del código, obligando a la implementación de una biblioteca propia para ocultar la utilización de este framework al resto del código de la aplicación. No proporciona operaciones de alto nivel como la reproducción de vı́deo, aunque sı́ tiene operaciones para manipular imágenes y fuentes. 2.3.4. Qt Qt es un [32] framework para la creación de aplicaciones e interfaces gráficas multiplataforma. Incluye una biblioteca C++ y herramientas para facilitar el desarrollo rápido de aplicaciones. La biblioteca C++ proporciona una gran cantidad de clases completamente funcionales e interfaces consistentes. Está desarrollada totalmente siguiendo el paradigma de orientación a objetos. Las aplicaciones hechas con Qt se ejecutarán nativamente en las plataformas soportadas, las aplicaciones podrán ser portadas a nuevas plataformas simplemente recompilándolas con la biblioteca Qt correspondiente a la nueva plataforma. Este framework se distribuye para cuatro tipos de sistemas: Qt/Windows: Permite compilar las aplicaciones Qt para Microsoft Windows 95/98/Me, NT4, 2000 y XP. Qt/X11 : Para Linux, Solaris, HP-UX, IRIX, AIX y otras variantes de UNIX. 1 biblioteca que facilita el acceso a hardware de vı́deo compatible con los estándares VGA y SVGA. 22 Capı́tulo 2. Contextualización Qt/Mac: Aplicaciones que funcionen sobre Apple Mac OS X. Qt/Embedded : Sistemas Linux embebidos utilizando el framebuffer en lugar del sistema X Windows como hace Qt/X11. La solución estudiada para el desarrollo del proyecto fue Qt/Embedded. Este framework no utiliza capas de emulación ni máquinas virtuales para posibilitar la portabilidad, cada edición es una implementación en código nativo del API ofrecido. La utilización de Qt ofrece las siguientes ventajas: Independencia real de plataforma. Para este proyecto no es especialmente importante, ya que la aplicación se desarrollará sólo para sistemas embebidos. Acceso a bases de datos utilizando un API independiente para bases de datos SQL. Esta caracterı́stica tampoco es necesaria para el software a desarrollar en este proyecto. Posibilidad de desarrollo rápido de aplicaciones. La plataforma Qt proporciona herramientas de productividad como QtDesigner, una herramienta para desarrollar interfaces gráficas. Soporta internacionalización. Velocidad de ejecución (se ejecuta en código nativo). Aspecto visual (look and feel ) configurable. Incluye módulos de espacio de trabajo (soporte MDI), vista de iconos, redes, XML, canvas, OpenGL, ... Sin embargo, Qt fue rechazado por dos razones: Está orientado al desarrollo de interfaces gráficas basadas en la utilización de ventanas. Como se comentó en la sección anterior, la interfaz que se desarrollará no será basada en ventanas, sino que estará formada por varios menús a pantalla completa. Es una solución demasiado “grande”. Está pensado para realizar aplicaciones con interfaces visuales complejas y que se puedan ejecutar en diferentes plataformas. Para este proyecto interesa una solución más ajustada, para respetar las restricciones de espacio presentadas por los Set Top Boxes. DirectFB 2.4. DirectFB 2.4.1. Introducción 23 DirectFB [1] es una biblioteca ligera desarrollada teniendo en cuenta las necesidades especiales de los sistemas embebidos. Proporciona aceleración gráfica, control y abstracción de dispositivos de entrada, sistema de ventanas integrado y soporte de múltiples capas visuales sobre el dispositivo framebuffer de Linux. Es una capa de abstracción completa, que incluye la utilización de operaciones software para aquellos casos en los que la operación no esté soportada por el hardware subyacente. También proporciona un sistema de ventanas integrado, que permite la creación de ventanas translúcidas. Proporciona las siguientes operaciones gráficas aceleradas por hardware: Dibujo/relleno de rectángulos. Dibujo/relleno de triángulos. Dibujo de lı́neas. Copia de imágenes (blitting) tanto elástica como directa. Mezcla con un canal alpha (alpha blending). Mezcla con un valor alpha constante (alpha modulation). Nueve funciones de mezcla para el origen y el destino respectivamente. Modulación por color (colorizing). Copia basada en color (color keying) tanto para el origen como para el destino. DirectFB tiene su propia gestión de recursos de memoria de vı́deo, de forma que recursos como capas visuales, dispositivos de entrada o superficies pueden ser bloqueados para acceso exclusivo. Proporciona abstracciones para los diferentes objetos gráficos, tales como capas, superficies y ventanas. Esto permite la selección entre aplicaciones basadas en ventanas o aplicaciones pensadas para pantalla completa. 24 Capı́tulo 2. Contextualización El API de DirectFB está diseñado para facilitar la creación de plugins que implementen las siguientes partes: Aceleración gráfica: Las operaciones que no puedan ser realizadas por hardware (según la implementación existente para el hardware que se esté utilizando) serán realizadas por software. Soporta las tarjetas gráficas Matrox G200/G400/G450/G550, ATI128, Vodoo 3, NeoMagic, nVidia RIVA TNT, S3 Savage y Cyber Pro, aunque para algunas no está soportada la aceleración para todas las operaciones. Cualquier otra tarjeta puede ser utilizada sin aceleración hardware, ya sea utilizando un dispositivo framebuffer especı́fico o el framebuffer genérico VESA. Dispositivos de entrada: Permite abstraer el concepto de dispositivo de entrada, de forma que los programas no tengan que preocuparse del hardware utilizado, sólo deberán tener en cuenta los eventos generados. Actualmente, los dispositivos de entrada soportados son teclado, ratones serie y PS/2, joysticks, pantallas táctiles y dispositivos remotos por infrarrojos. Proveedor de imagen: Abstrae el concepto de imagen de forma que las aplicaciones no tengan que preocuparse del formato utilizado por la imagen que desean mostrar. Soporta los formatos JPEG, PNG, GIF y MPEG2 I-Frame. Proveedor de vı́deo: La misma idea, pero para archivos de vı́deo. Puede reproducir vı́deos en formato MPEG1/2, AVI, Macromedia Flash y OpenquickTime. También soporta Video4Linux. Proveedor de fuentes: Permite utilizar distintos tipos de fuente de forma homogénea. Soporta el formato propio de fuentes de DirectFB y fuentes TrueType. 2.4.2. Arquitectura DirectFB utiliza la interfaz proporcionada por el dispositivo framebuffer de Linux para acceder al hardware gráfico. Hay controladores especiales para los framebuffers de ciertos dispositivos integrados en el kernel de Linux, el resto de dispositivos funcionarán también utilizando el controlador para el framebuffer VESA, pero con ciertas limitaciones. Independientemente de que se utilice aceleración gráfica o no, DirectFB utilizará el dispositivo framebuffer para realizar las siguientes operaciones: Establecer el modo de vı́deo (resolución, profundidad de color y temporizaciones). DirectFB 25 Mapping de la memoria del framebuffer de la tarjeta. Cambios en el área visible (viewport) del framebuffer para soportar doble buffering. De esta forma se podrán realizar cambios en un área no visible y hacerla visible de forma sı́ncrona con el barrido del monitor para evitar efectos visuales desagradables. Si la tarjeta está soportada por DirectFB y el controlador para el chipset está presente en el kernel, DirectFB también usará el dispositivo framebuffer para las siguientes tareas: Mapping de memoria de los puertos de entrada/salida de las tarjetas. Desactivación de la aceleración interna del dispositivo framebuffer. Para ejecutar una operación gráfica especı́fica, el controlador de DirectFB hará un mapping de los puertos de entrada/salida de la tarjeta en memoria para transmitir la operación concreta a realizar. Como se dijo en la sección 2.3.2, la aceleración hardware se programa completamente desde el espacio de usuario. Ver la figura 2.6. Aplicacion DirectFB Espacio del Usuario DirectFB Controlador para el chipset Controlador para el dispositivo framebuffer framebuffer registros de temporizacion y modo de video Espacio del kernel motor de aceleracion Hardware grafico Solo se aplica cuando se utiliza la consola del framebuffer Desactivado mientras se ejecute una aplicacion DirectFB Figura 2.6: Acceso de una aplicación DirectFB al hardware gráfico Para acceder a los dispositivos de entrada, DirectFB utiliza las interfaces estándar que Linux proporciona para los distintos dispositivos. DirectFB no accede nunca al hardware de entrada directamente. 26 Capı́tulo 2. Contextualización 2.4.3. Términos importantes Blitting: Se refiere al proceso de copiar datos de una imagen. El caso más simple es copiar una imagen de una superficie a otra cuando ambas superficies tiene el mismo tamaño y el mismo formato de pixel. En este caso solamente tiene que ser copiada la memoria sin ningún tipo de procesado. Otros casos más avanzados son la copia elástica (stretch blitting) entre superficies de distinto tamaño, la copia con canal alpha o copia con cambio de formato de pixels. La mayorı́a de las tarjetas gráficas incluyen un blitter hardware capaz de realizar diversas operaciones de copia. Superficie: Es un zona de memoria reservada en la que se almacena la información sobre los pixels de una imagen en un formato especı́fico. Una superficie puede residir en la memoria de vı́deo y/o en la memoria del sistema. Es posible realizar operaciones de dibujo sobre una superficie, o copiar una superficie en otra, como se explicó anteriormente. Adicionalmente cada superficie puede utilizar un doble buffer de gráficos, de forma que las operaciones realizadas sobre ella sólo se vuelven válidas cuando se llama a una función para cambiar el buffer visible. En el modo de pantalla completa, la pantalla visible es representada por la “Superficie Primaria”. Esta superficie normalmente dispondrá de doble buffer para evitar efectos desagradables cuando se realicen operaciones sobre ella. Subsuperficie: Presenta el mismo interfaz que una superficie normal, pero no tiene memoria reservada para sı́ misma. Capa: Dependiendo del hardware gráfico, puede haber una o más capas visibles. La mayorı́a de las tarjetas gráficas de PC sólo disponen de una capa, pero la tarjeta de un Set Top Box puede soportar dos o más capas. Las capas ocupan diferentes posiciones en la memoria de vı́deo y suelen ser combinadas utilizando mezcla alpha, esto lo hace el hardware automáticamente. Ventana: Normalmente, los contenidos de una superficie de una capa son controlados por el sistema de ventanas integrado que muestra las ventanas pertenecientes a la capa sobre un fondo configurable. Cada ventana tiene su propia superficie que es utilizada por el sistema de ventanas para componer la imagen de ventanas superpuestas. Alternativamente, las aplicaciones pueden obtener acceso exclusivo a la capa, de esta forma la aplicación tiene acceso directo a la capa y a su contenido, no se muestran ventanas ni fondos. Esta será la forma en la que se desarrollará el software de este proyecto. DirectFB 2.4.4. 27 API El API de DirectFB está estructurado utilizando interfaces. Una interfaz es una estructura C que contiene punteros a funciones. Dependiendo de la implementación de dicha interfaz, estos punteros apuntarán a distintas funciones. Por ejemplo IDirectFBSurface puede representar la pantalla completa, los contenidos de una ventana o una subsuperficie. IDirectFB es la “Super Interfaz”. Es la única que puede ser creada por una función global (DirectFBCreate). El resto de interfaces serán creadas llamando a funciones de IDirectFB o a funciones de interfaces ya creadas. Se puede acceder a las interfaces existentes (por ejemplo los dispositivos de entrada) a través de IDirectFB. En la figura 2.7 se puede observar la relación entre las distintas interfaces de DirectFB. «super interface>> IDirectFB Obtener Crear Crear IDirectFBDisplayLayer IDirectFBSurface Obtener IDirectFBInputDevice Obtener Crear IDirectFBWindow Obtener Crear IDirectFBInputBuffer IDirectFBImageProvider IDirectFBVideoProvider IDirectFBFont Figura 2.7: Relación entre las interfaces de DirectFB Ciertas interfaces están implementadas como módulos que serán cargados en tiempo de ejecución en caso de que sea necesario. Las interfaces implementadas de esta forma son IDirectFBImageProvider, IDirectFBVideoProvider e IDirectFBFont. La aplicación cargará la implementación necesaria en cuando se construya la interfaz. Otras interfaces también implementadas como módulos serán cargadas durante la inicialización de la aplicación. Estas interfaces son IDirectFBInputDevice y GfxCard (interfaz interna inaccesible para las aplicaciones, que contiene el código encargado de utilizar la aceleración hardware en caso de que sea posible). De esta forma se podrán añadir nuevas implementaciones de controlador de aceleración, dispositivos de entrada y proveedores de medios sin necesidad de recompilar ni DirectFB ni la aplicación. Capı́tulo 3 Análisis Índice General 3.1. 3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . 29 3.2. Protocolo de comunicación . . . . . . . . . . . . . . . . 30 3.3. Set Top Box . . . . . . . . . . . . . . . . . . . . . . . . 31 3.4. Aplicación DFBCIn . . . . . . . . . . . . . . . . . . . . 32 3.4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 32 3.4.2. Módulo gráfico . . . . . . . . . . . . . . . . . . . . . . 32 3.4.3. Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.4.4. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . 35 3.5. Servidor XML (VXMLS) . . . . . . . . . . . . . . . . . 36 3.6. Ciclo de Desarrollo . . . . . . . . . . . . . . . . . . . . 37 3.6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 37 3.6.2. Prototipo . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.6.3. DFBCIn . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.6.4. VXMLS . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.6.5. Prueba del sistema conjunto . . . . . . . . . . . . . . . 40 Introducción El objetivo principal de este proyecto es el desarrollo de un sistema que permita al usuario final el acceso de forma controlada a los medios disponibles en el 29 30 Capı́tulo 3. Análisis servidor de VoD VoDKA. Por lo tanto, se necesita un dispositivo que actúe como mediador entre el usuario y el servidor VoD. Es deseable que dicha interacción pueda ser controlada de forma remota y centralizada, de forma que pueda adaptarse según las condiciones del entorno. Esto nos lleva a la necesidad de implementar otro dispositivo adicional que permita definir el protocolo de interacción que se presentará al usuario. Ası́ pues, este proyecto se analizará dividido en dos subproyectos: el desarrollo de un Set Top Box y el de un servidor que proporcione a dicho Set Top Box la información necesaria para controla la interacción con el usuario. Esta separación supone la necesidad de definir un protocolo de comunicación entre el Set Top Box y el servidor de “definiciones de interfaz”. Para establecer dicho protocolo, se han utilizado los estándares HTTP [35] y XML [36], lo que permitirá la utilización de herramientas ya existentes para el desarrollo del software necesario. 3.2. Protocolo de comunicación Para definir el protocolo de comunicación entre el Set Top Box y el servidor de “definiciones de interfaz” se necesita especificar: El sistema de comunicación: Se utilizará el protocolo HTTP sobre una conexión TCP convencional. El Set Top Box actuará como cliente, solicitando la información que necesite mediante peticiones HTTP al servidor. La estructura de la información: Se utilizará el estándar XML. El servidor devolverá la información requerida por el cliente enviando un documento XML en una respuesta HTTP. El estándar XML permite especificar la estructura que debe tener un documento de forma independiente de su contenido. Hay diferentes formas de expresar la estructura de un documento XML, en este proyecto se utilizarán DTDs [36] para definir la forma en la que los documentos deben expresar la información requerida. El servidor de “definiciones de interfaz” (servidor XML de aquı́ en adelante) necesitará expresar dos tipos de respuestas: la definición de la interfaz solicitada por el Set Top Box y los mensajes de confirmación o de error para el resto de Aplicación DFBCIn 31 operaciones que el Set Top Box pueda solicitarle (por ejemplo, iniciar la sesión). La estructura del documento de confirmación simplemente tiene que comprobar dos casos: éxito o fracaso. Los documentos utilizados para definir la interfaz son bastante más complejos. Deben poder expresar tanto el aspecto visual como la funcionalidad a realizar por la interfaz definida. Por lo tanto, debe diseñarse una estructura que sea fácilmente extensible, para no constreñir el aumento de funcionalidades del Set Top Box. 3.3. Set Top Box El desarrollo del Set Top Box debe tener como objetivo un sistema que sea capaz de realizar las siguientes tareas: Comunicarse con el servidor VXMLS para obtener la información necesaria sobre la interfaz a presentar al usuario. Esta interfaz debe lo suficientemente clara y sencilla, de forma que no suponga ninguna dificultad para cualquier tipo de usuario. Presentar dicha interfaz al usuario en un televisor convencional y permitirle interactuar con ella a través de un mando a distancia. Reproducir vı́deo recibido del servidor VoD VoDKA. Debe ser un sistema razonablemente económico. El software se desarrollará de forma que no sea dependiente de la plataforma, deberá ser posible ejecutarlo en cualquier Set Top Box que disponga de una salida para televisión y una tarjeta de red. Como sistema operativo se utilizará Linux. Para respetar las restricciones de espacio será necesario evitar el sistema X Windows, por lo que la programación gráfica se hará accediendo directamente al hardware mediante el dispositivo framebuffer de Linux (ver la sección 2.3). Para controlar la interacción con el usuario y la comunicación con el servidor XML se desarrollará una única aplicación llamada DFBCIn (DirectFB C Interface). La comunicación con el servidor VoD se ocultará al resto del sistema utilizando el proyecto desarrollado en [37] que permite manejar un vı́deo servido con el método de streaming como si se tratara de un fichero local. 32 Capı́tulo 3. Análisis 3.4. Aplicación DFBCIn 3.4.1. Introducción El objetivo de esta aplicación es el de controlar la interacción entre el usuario y los contenidos del servidor de streaming VoDKA. Se puede dividir el análisis de esta aplicación en tres módulos que deberán estar bien desacoplados: El módulo gráfico: Se encargará de controlar el aspecto gráfico de la interfaz mostrada al usuario. Como se verá en la sección 3.4.2, este módulo también deberá controlar los dispositivos de entrada. El módulo VXMLS : Se encargará de enviar las peticiones necesarias a VXMLS y de transformar los ficheros XML recibidos en una estructura de datos comprensible para el resto de la aplicación. Para ello deberá implementar un parser para interpretar los documentos recibidos. El motor de la aplicación: Se encargará de ejecutar las acciones solicitadas por el usuario. Este módulo accederá a los dos anteriores a través de un API definido y estable. 3.4.2. Módulo gráfico Para la programación gráfica se ha elegido la librerı́a DirectFB [1] (en la sección 2.4 se hace una pequeña introducción). El desarrollo se hará utilizando el lenguaje de programación de medio nivel C. De esta forma, se aligera notablemente la carga, tanto de ejecución como de espacio, pero la codificación se vuelve mucho más complicada que si se utilizara un lenguaje de más alto nivel y el sistema X Windows. Durante la fase de análisis se ha desarrollado una pequeña aplicación para validar la utilidad de la biblioteca DirectFB, de acuerdo con los objetivos del proyecto. En dicha aplicación se comprobó el comportamiento de DirectFB en una aplicación multi-hilo en la que se puede reproducir un vı́deo elegido mediante un menú gráfico. En esta aplicación se resumı́a la funcionalidad que es necesario implementar en la aplicación DFBCIn con la ayuda de esta biblioteca, obviando el resto de objetivos que se implementarán de forma independiente a la utilización de DirectFB. En la figura 3.1 se muestra el ciclo de interacción implementado en este prototipo. Aplicación DFBCIn 33 También se han realizado pequeñas aplicaciones de prueba para comprobar que DirectFB permite el acceso concurrente de varios hilos de programación a sus recursos, que es capaz de reproducir varios vı́deos al mismo tiempo (se comprobó que sólo puede reproducir varios vı́deos si sólo uno contiene audio) y su comportamiento ante fallos de la aplicación. Figura 3.1: Prototipo de interfaz con el usuario Además, para el desarrollo de la aplicación anterior se tomaron como referencia algunas aplicaciones ya existentes implementadas utilizando DirectFB, lo que ayudó a afrontar la codificación de DFBCIn con buen conocimiento del API y del estilo de codificación a seguir en la parte gráfica. La utilización de DirectFB proporciona algunas ventajas interesantes: Está diseñada siguiendo el paradigma de orientación a objetos, lo que facilita la traducción del diseño a la codificación. Proporciona numerosas abstracciones que desacoplan el código desarrollado de aspectos tales como la aceleración hardware o la decodificación de vı́deo. Permite el control de diversos dispositivos de entrada, entre ellos de puerto de infrarrojos. De esta forma, para el código desarrollado, no hay diferencia entre el mando a distancia y el teclado. El API es claro, sencillo y orientado a objetos. La documentación, aunque no excelente, es suficiente y está actualizada. 34 Capı́tulo 3. Análisis Pero también trae consigo una serie de contratiempos, la mayorı́a derivados de la utilización de un lenguaje de bajo nivel y con numerosos efectos colaterales como es C: Es necesario programar de forma explı́cita la reserva y liberación de memoria, ya que C no aporta un sistema de “recolección de basura”. C no es orientado a objetos, la traducción de los objetos del diseño es más complicada que si se utilizara un lenguaje moderno de más alto nivel. La combinación de los dos puntos anteriores hace que la compartición del mismo objeto entre distintos módulos de la aplicación tenga que ser controlada de forma explı́cita (es necesario asegurarse de que el objeto se libera solamente una vez cuando deje de ser necesario). El lenguaje C también obliga a hacer explı́cito el control de errores ya que no aporta ningún sistema de control de excepciones. La biblioteca DirectFB está actualmente en estado de desarrollo, por lo que será necesario tener un especial cuidado en el diseño para asegurar que los cambios sufridos por dicha biblioteca sólo afecten al módulo gráfico de DFBCIn y no se propaguen por el resto de la implementación. DirectFB controla tanto el hardware gráfico como los dispositivos de entrada. Esto hace imposible separar fı́sicamente los módulos de entrada de los de salida en el diseño del sistema. El módulo de entrada queda definitivamente ligado a la utilización o no de DirectFB en el de salida. Como es necesario independizar el módulo gráfico del resto de la aplicación, el diseño del sistema de entrada quedará vinculado al módulo gráfico. Un fallo en el proceso de liberación de recursos puede hacer que DirectFB deje el sistema inestable, siendo necesaria su recuperación de forma remota. 3.4.3. Parser Para interpretar los documentos XML recibidos del servidor VXMLS es necesario implementar un parser, para ello se ha utilizado la biblioteca expat [2] que proporciona una implementación de un parser XML genérico que se puede particularizar utilizando la idea del patrón Strategy [38]. Aplicación DFBCIn 35 VXMLS puede devolver dos tipos de documentos, los utilizados para especificar la interfaz con el usuario1 y los que certifican el éxito de operaciones sin resultado. En la figura 3.2 se puede ver cómo se pueden obtener distintos parsers utilizando expat. XML_Parser +XML_parse(): UserData #startElement(userData:UserData,tagName:String,attributes:Array) #EndElement(userData:UserData,tagName:String) #characterData(userData:UserData,text:String,length:Integer) MenuParser +parse(stream:CharacterStream): Menu -startElement(userData:UserData,tagName:String,attributes:Array) -EndElement(userData:UserData,tagName:String) -characterData(userData:UserData,text:String,length:Integer) VXMLSParser +parse(stream:CharacterStream): Boolean -startElement(userData:UserData,tagName:String,attributes:Array) -EndElement(userData:UserData,tagName:String) -characterData(userData:UserData,text:String,length:Integer) Figura 3.2: Creación de parsers con expat La aproximación a este diseño utilizando el lenguaje C presenta algunas complicaciones. La solución propuesta por expat proporciona una función que devuelve un objeto de tipo XML Parser en el que se deben instalar unos manejadores para los eventos startElement, endElement y characterData. Dado que la estructura de los documentos a analizar puede ser arbitrariamente profunda, será necesario utilizar una pila para ir almacenando la información a medida que el parser va llamando los manejadores proporcionados. Además, el número de etiquetas que pueden ser utilizadas en los documentos que definen la interfaz es bastante elevado (y aumentará a medida que aumente la funcionalidad del sistema), por lo que se deberá poner especial cuidado en la estructuración del código del parser para evitar la duplicación del mismo. También se debe tener en cuenta que el diseño debe quedar abierto de forma que se pueda añadir fácilmente el código necesario para interpretar las nuevas etiquetas que vayan surgiendo a lo largo del ciclo de vida del proyecto. 3.4.4. Conclusiones El diseño de la aplicación DFBCIn debe perseguir los siguientes objetivos: Aislar lo más posible la utilización de DirectFB para minimizar el impacto de los posibles cambios en dicha biblioteca. 1 La especificación de la interfaz se hará definiendo una sucesión de menús, como se verá en apartados posteriores. Por ello, el parser que se utilizará para este tipo de documentos devuelve objetos Menu 36 Capı́tulo 3. Análisis La comunicación con VXMLS también deberá ser desacoplada al máximo del resto de la aplicación para evitar que los cambios en VXMLS se propaguen por el código de DFBCIn. Permitir la extensión de la funcionalidad ofrecida con poco esfuerzo. A pesar de que el desarrollo se haga en C, el diseño será objetual. 3.5. Servidor XML (VXMLS) El objetivo de esta aplicación es atender las peticiones de las aplicaciones DFBCIn residentes en Set Top Boxes remotos, responderles con el correspondiente documento XML y mantener una base de datos con información, tanto de los medios disponibles en VoDKA como información referente a los usuarios. La funcionalidad ofrecida por el servidor será muy básica, simplemente la necesaria para comprobar el correcto funcionamiento de la aplicación DFBCIn y demostrar las posibilidades que ofrece el hecho de mantener la información sobre la interfaz con el usuario centralizada en un servidor remoto. El desarrollo del servidor se ha hecho utilizando el lenguaje funcional Erlang [3] y algunas herramientas proporcionadas por la plataforma de desarrollo Erlang/OTP [4]. Concretamente: el contenedor de aplicaciones HTTP Inets [39], el sistema gestor de bases de datos distribuido Mnesia [40] y el lenguaje de consulta Mnemosyne [41]. La aplicación VXMLS debe ser capaz de realizar las siguientes operaciones: Mantener sesiones para los diferentes usuarios conectados. Mantener información sobre los usuarios en la base de datos. Mantener información sobre los medios disponibles en VoDKA. Generar documentos XML de forma dinámica, dependiendo del contexto actual. Permitir a los usuarios configurar sus preferencias. Las posibilidades de personalización implementadas son la selección de idioma y la selección de perfil, de forma que cada Set Top Box puede mantener información sobre varios usuarios. Ciclo de Desarrollo 37 La definición de interfaz que debe proporcionar VXMLS permitirá acceder a las posibilidades de personalización anteriores y a la reproducción de vı́deos, que se mostraran clasificados como novedades, vistos y antiguos. Dentro de cada una de estas clasificaciones, los vı́deos estarán clasificados en géneros. El servidor también deberá encargarse de mover los vı́deos vistos por el usuario a la sección vistos. 3.6. Ciclo de Desarrollo 3.6.1. Introducción Una vez establecidos los requisitos que debe cumplir el sistema final, se debe elegir el ciclo de desarrollo que se seguirá para llevar a cabo este proyecto: 1. Desarrollo de un prototipo que demuestre la utilidad de DirectFB. El prototipo diseñado deberá dibujar menús, controlar la entrada del usuario y reproducir vı́deo. Estas son las operaciones que serán implementadas con la ayuda de DirectFB en la aplicación DFBCIn. También se ha codificado un parser mı́nimo para comprobar la utilidad de la biblioteca expat empleada en la codificación del parser XML. 2. Desarrollo de una primera versión de la aplicación DFBCIn que trabaje con ficheros XML estáticos en lugar de obtenerlos del servidor dinámico VXMLS. 3. Desarrollo de la aplicación VXMLS. 4. Desarrollo de un nuevo incremento de DFBCIn que se comunique con VXMLS para obtener la información sobre la interfaz con el usuario a utilizar. 5. Refinamiento de DFBCIn y de VXMLS en conjunto. 3.6.2. Prototipo Para comprobar que realmente se puede llevar a cabo la implementación gráfica utilizando DirectFB se desarrolló un prototipo en el que solamente se programó la parte gráfica de una posible aplicación para el Set Top Box. Este prototipo simplemente muestra una serie de menús (que no están especificados por ninguna fuente externa) que permiten la elección de una imagen de 38 Capı́tulo 3. Análisis fondo o la reproducción de un vı́deo. Para la programación de este prototipo se ha consultado el código fuente de algunas aplicaciones de ejemplo implementadas utilizando DirectFB. Una de ellas es el reproductor de vı́deo DFBSee (figura 3.3) y otra es DFBPoint (figura 3.4), que muestra una presentación de transparencias a partir de una especificación XML. Figura 3.3: Aplicación DFBSee En el código de la aplicación DFBSee se puede ver cómo programar un sistema que reproduzca vı́deo y permita al usuario realizar acciones tales como pararlo, avanzarlo o ver una barra de progreso. La aplicación DFBPoint resulta útil para hacerse una idea de cómo programar la presentación de menús. Implementa un parser especı́fico para los documentos XML que utiliza, por lo que esta parte no se tendrá en cuenta para la aplicación DFBCIn, en la que se utilizará la biblioteca expat para la implementación del parser. Tras el estudio de estas dos aplicaciones, se ha desarrollado el prototipo, lo que permite afrontar el diseño de la aplicación DFBCIn con los conocimientos necesarios para ocultar lo más posible la utilización de DirectFB a los módulos que no precisan el acceso al hardware gráfico. Ciclo de Desarrollo 39 Figura 3.4: Aplicación DFBPoint Para verificar la utilidad de la biblioteca expat se ha codificado un pequeño programa que muestra por pantalla los distintos eventos que detecta el parser genérico proporcionado por expat. La implementación del parser para DFBCIn deberá sustituir estos mensajes por las operaciones a realizar. 3.6.3. DFBCIn Para el desarrollo de la aplicación DFBCIn se ha seguido el paradigma de programación incremental. En primer lugar se desarrollará el núcleo de la aplicación y a partir del momento en que el núcleo haya sido validado con las pruebas correspondientes, se continuará con el desarrollo de nuevos incrementos que vayan aumentando la funcionalidad proporcionada por la aplicación. El paradigma de desarrollo incremental se adapta bien al tipo de software a desarrollar, ya que uno de los requisitos planteados para la aplicación DFBCIn es que se pueda extender la funcionalidad ofrecida a medida que vayan surgiendo nuevas ideas o necesidades. Seguir un desarrollo incremental obliga de forma explı́cita a que el diseño soporte la creación de nuevas versiones con nuevas caracterı́sticas a partir de las versiones anteriores. Uno de los principales problemas del desarrollo incremental es la definición 40 Capı́tulo 3. Análisis de lo que será el núcleo de la aplicación. Para el caso de DFBCIn, el primer incremento tendrá como objetivo un sistema que pueda interpretar documentos XML estáticos localizados en ficheros locales que definan una interfaz muy sencilla en la que lo único que se pueda hacer sea la navegación por los menús. En este incremento ya se contemplan varias de las funcionalidades importantes que se esperan de DFBCIn: la interpretación de documentos XML, la presentación gráfica de menús y el control de entrada por parte del usuario. Se dejarán para incrementos posteriores la comunicación con un sistema remoto para la obtención de los documentos XML y la reproducción de vı́deo. 3.6.4. VXMLS Puesto que el desarrollo de VXMLS se empezará cuando el desarrollo de DFBCIn ya esté suficientemente avanzado, a esas alturas se se tendrá una definición bastante estable de la estructura de los documentos XML a servir y de las posibilidades esperadas de la interfaz definida. Por lo tanto, el desarrollo del servidor VXMLS podrá hacerse en un único incremento con sus correspondientes etapas de análisis, diseño, codificación y prueba. 3.6.5. Prueba del sistema conjunto Una vez terminado el desarrollo de DFBCIn y de VXMLS, se realizaron las pruebas de ambos sistemas en conjunto (una vez hechos los cambios necesarios en DFBCIn para que se comunique correctamente con VXMLS), tras las cuales se ha desarrollado el último incremento de ambas aplicaciones hasta tener el sistema final funcionando correctamente. Capı́tulo 4 Diseño Índice General 4.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . 42 4.2. Definición de la interfaz y objetos del dominio . . . . 42 4.2.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 42 4.2.2. La clase Menu . . . . . . . . . . . . . . . . . . . . . . 43 4.2.3. La interfaz Action . . . . . . . . . . . . . . . . . . . . 45 4.3. Documentos XML . . . . . . . . . . . . . . . . . . . . . 48 4.3.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 48 4.3.2. Descripción de los menús . . . . . . . . . . . . . . . . 48 4.4. Protocolo de comunicación . . . . . . . . . . . . . . . . 54 4.5. Diseño de DFBCIn . . . . . . . . . . . . . . . . . . . . 55 4.5.1. División en subsistemas . . . . . . . . . . . . . . . . . 55 4.5.2. Interacción entre módulos . . . . . . . . . . . . . . . . 56 4.5.3. Objetos comunes . . . . . . . . . . . . . . . . . . . . . 58 4.5.4. Diseño del motor de la aplicación . . . . . . . . . . . . 59 4.5.5. Diseño del módulo VXMLS . . . . . . . . . . . . . . . 65 4.5.6. Diseño del módulo gráfico . . . . . . . . . . . . . . . . 71 4.6. Diseño de VXMLS . . . . . . . . . . . . . . . . . . . . . 75 4.6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 75 4.6.2. Objetos del dominio . . . . . . . . . . . . . . . . . . . 76 4.6.3. Modelo entidad-relación . . . . . . . . . . . . . . . . . 78 4.6.4. Diseño de la capa modelo . . . . . . . . . . . . . . . . 78 41 42 Capı́tulo 4. Diseño 4.6.5. Diseño de la vista y el controlador . . . . . . . . . . . 4.1. 80 Introducción Antes de proceder al diseño de las aplicaciones VXMLS y DFBCIn y del sistema que utilizarán para comunicarse, es necesario describir qué es lo que se entenderá por “descripción de interfaz”, es decir, hay que especificar qué es lo que VXMLS tiene que enviar a DFBCIn. Una vez establecida la definición de lo que se entenderá cómo “interfaz”, se debe hacer el diseño de los objetos que conformarán el dominio (los objetos comunes a ambas aplicaciones). Tras los dos pasos anteriores ya se tendrá hecho el diseño de la parte común a VXMLS y DFBCIn. Será necesario diseñar el protocolo de comunicación que ambas deberán cumplir. Una vez hecho esto se podrá comenzar el diseño de ambos sistemas por separado. 4.2. Definición de la interfaz y objetos del dominio 4.2.1. Introducción Como ya explicó en la sección 3.1, la aplicación VXMLS debe proporcionarle a DFBCIn una especificación de la interfaz a utilizar para comunicarse con el usuario. Antes de empezar el diseño de cualquier otra parte del sistema es necesario definir con claridad cómo se va a describir exactamente dicha interfaz con el usuario. Se pretende que la interfaz sea sencilla y que el usuario interactúe utilizando un mando a distancia. También es deseable que el número de botones necesario para acceder a la funcionalidad básica sea mı́nimo. Por lo tanto, la idea inicial de interfaz con el usuario será una sucesión de menús en los cuales el usuario deberá elegir entre las opciones disponibles. La interfaz será una generalización de la desarrollada para el prototipo implementado en la fase de análisis (ver la figura 3.1). Definición de la interfaz y objetos del dominio 4.2.2. 43 La clase Menu Ası́ pues, se entenderá como “descripción de interfaz” la descripción de la sucesión de menús que se irán mostrando al usuario. La clase Menu será la que defina los objetos que utilizará VXMLS para indicarle a DFBCIn la interfaz que deberá mostrar. Es decir, VXMLS generará objetos Menu y los transmitirá serializados (en forma de documentos XML) a la aplicación cliente DFBCIn. En la figura 4.1 se muestra una primera aproximación a la clase Menu. Menu Option 1..∗ + text: String − header: String + draw() + execute() Figura 4.1: Primera aproximación a la clase Menu Sin embargo, siguiendo este diseño, la información sobre las acciones a realizar por cada opción estarı́a codificadas en el método execute, lo que obligarı́a a especializar una nueva clase para cada opción de cada menú. Aplicando el patrón de diseño Command [38], se definirá una nueva interfaz Action, que representará una acción genérica a realizar cuando se selecciona una opción. En la figura 4.2 se puede ver la aproximación utilizada para definir los objetos Option. Menu Option 1..∗ + text: String − header: String + draw() for all actions do action.execute end + execute() 1..∗ <<interface>> Action + execute() NextMenuAction PreviousMenuAction − nextMenu: String − depth: Int + execute() + execute() Figura 4.2: Segunda aproximación a la clase Menu La interfaz Action proporciona la extensibilidad que se exigió en el análisis 44 Capı́tulo 4. Diseño referente a la funcionalidad ofrecida por el Set Top Box. En los diferentes subsistemas que componen el proyecto se deberá implementar este diseño preservando la posibilidad de añadir nuevas acciones manteniendo una interfaz común, de forma que estas nuevas acciones no afecten al resto del sistema. Además, la interfaz Action será utilizada para definir las acciones a realizar en otros casos, además de la ejecución de opciones seleccionadas por el usuario. El diseño definitivo de la clase Menu es el especificado en la figura 4.3 Menu + majorVersion: Int + minorVersion: Int − header: String − time: Int Option 1..∗ text: String + execute() + draw() for all actions do action.execute end 1..∗ <<interface>> initActions timeActions 0..∗ 0..* Action + execute() NextMenuAction PreviousMenuAction − nextMenu: String − depth: Int + execute() + execute() Figura 4.3: Diseño definitivo de la clase Menu Los atributos majorVersion y minorVersion se utilizarán para controlar las nuevas implementaciones de la clase Menu que VXMLS vaya siendo capaz de servir. En el caso de que DFBCIn reciba un menú de una versión mayor a la que sea capaz de soportar deberá actualizar su implementación de forma que pueda interpretar las nuevas caracterı́sticas de la clase Menu (idealmente, estas nuevas caracterı́sticas sólo deberı́an ser nuevas implementaciones de la interfaz Action). Las acciones initActions se ejecutarán cada vez que DFBCIn cargue un nuevo objeto de la clase Menu. El atributo time indicará (en caso de que sea necesario) el tiempo a esperar desde que se carga el menú hasta que se ejecuten las acciones timeActions. La espera se reiniciará cada vez que se reciba alguna entrada por parte del usuario. Definición de la interfaz y objetos del dominio 4.2.3. La interfaz Action 4.2.3.1. Acciones implementadas 45 El primer incremento de la aplicación DFBCIn solamente pretende conseguir una interfaz en la que el usuario pueda navegar por los menús, sin ninguna otra posibilidad. Para ello, inicialmente sólo se necesitará implementar una acción para pasar a un nuevo menú y otra para volver a menús anteriores (NextMenuAction y PreviousMenuAction respectivamente, inicialmente ChangeOptionAction estará codificada directamente como respuesta a la pulsación de las teclas arriba y abajo). Además de las dos acciones citadas anteriormente, el sistema final contiene las siguientes implementaciones de Action: PlayVideoAction: Inicia la reproducción de un vı́deo. StopVideoAction: Detiene la reproducción en curso. ResumeVideoAction: Continúa con la reproducción de vı́deo. SeekVideoAction: Salta a un momento especı́fico del vı́deo que se está reproduciendo. UnloadVideoAction: Termina la reproducción de vı́deo. HideMenuAction: Oculta el menú de forma que no sea visible en la pantalla. ReloadMenuAction: Vuelve a solicitar el menú actual a VXMLS. ShowMenuAction: Muestra el menú actual en la pantalla. ChangeOptionAction: Cambia la opción seleccionada en el menú actual. BindKeyAction: Enlaza una tecla a un grupo de acciones a realizar en caso de que el usuario la pulse. SetPropertyAction: Cambia el valor de un parámetro de configuración. ShowTextAction: Muestra texto en la pantalla. HideTextAction: Oculta el texto mostrado en la pantalla. LoadMp3Action: Inicia la reproducción de un fichero de audio mp3. PauseContinueMp3Action: Detiene o continua la reproducción de audio. KillMp3Action: Elimina el reproductor de mp3 para que permitir a otros subsistemas utilizar la tarjeta de sonido. 46 4.2.3.2. Capı́tulo 4. Diseño Acciones para el manejo de menús <<interface>> Action + execute() ReloadMenuAction HideMenuAction ShowMenuAction NextMenuAction PreviousMenuAction + execute() + execute() + execute() − nextMenu: String − arguments: Array − returnDepth: Int − reinit: Bool + execute() + execute() Figura 4.4: Acciones para el manejo de menús En la figura 4.4 está representado el diseño de las acciones a implementar para controlar la navegación por los menús de la interfaz. ReloadMenuAction, ShowMenuAction y HideMenuAction solamente deberán realizar acciones sobre el menú actual y no necesitan ningún tipo de atributos. NextMenuAction tiene como atributos el nombre del menú a cargar (DFBCIn solicitará un menú con ese nombre a VXMLS) y una lista de argumentos. La lista de argumentos se añadió al diseño durante la etapa de diseño de VXMLS, para permitir generalizar la generación de menús similares, por ejemplo, para generar un menú en el que las opciones sean una lista de pelı́culas, DFBCIn solicitará un menú lista de pelı́culas con los atributos género y filtro. La acción PreviousMenuAction tiene como atributos returnDepth, que indica el número de menús que se debe volver atrás y reinit, para indicar si se debe reinicializar el menú al que se retorna o se deben mantener sus propiedades actuales. 4.2.3.3. Acciones para el control de vı́deos En la figura 4.5 se puede ver el diseño de las acciones utilizadas para controlar la reproducción de vı́deo. Las acciones ResumeVideoAction, UnloadVideoAction y StopVideoAction no tienen ningún parámetro, simplemente deben actuar sobre la reproducción en curso. La acción SeekVideoAction tiene como atributos mode, que indica cómo se quiere posicionar el vı́deo (puede ser forward para avanzar, backward para retro- Definición de la interfaz y objetos del dominio <<interface>> Action 47 1.. ∗ endActions + execute() SeekVideoAction ResumeVideoAction StopVideoAction UnloadVideoAction PlayVideoAction − mode: String + execute() + execute() + execute() − video: String − time: Int + execute() + execute() Forward, Backward o Absolute Figura 4.5: Acciones para el control de vı́deos ceder o absolute para ir a un momento especı́fico de la reproducción), y time, que indica o bien el tiempo que se debe avanzar o retroceder, o bien el momento al que se debe saltar. La acción PlayVideoAction es una aplicación del patrón Composite [38]. Como atributo tiene el nombre del vı́deo a reproducir (video). Al finalizar la reproducción del vı́deo, se ejecutaran las acciones endActions. 4.2.3.4. Otras acciones En la figura 4.6 se describe el diseño del resto de acciones que se han implementado para el sistema final. <<interface>> 0 .. ∗ Action keyActions ACTIONS NULL EXECUTE + execute() KillMp3PlayerAction + execute() SetPropertyAction ChageOptionAction − name: String − absolute: Bool − type: String − value: Int − value: Int − key: String + execute() + execute() + execute() ShowTextAction PauseContinueMp3Action LoadMp3Action HideTextAction − lines: Array + execute() − path: String + execute() + execute() BindKeyAction + execute() Figura 4.6: Resto de acciones del sistema SetPropertyAction tiene como atributos el nombre de la propiedad a modificar (name) y el nuevo valor que debe tomar esa propiedad (value). 48 Capı́tulo 4. Diseño ChangeOptionAction tiene como atributos un entero y un valor booleano para indicar si el valor value se debe sumar a la opción actual o si es la acción que hay que seleccionar. ShowTextAction tiene como atributos un array de lı́neas de texto. LoadMp3Action tiene como atributo la ruta completa del fichero mp3 a reproducir. Las acciones HideTextAction, PauseContinueMp3Action y KillMp3Player no necesitan atributos para ser ejecutadas. 4.3. Documentos XML 4.3.1. Introducción Se necesitan dos tipos de documentos XML, uno de ellos se utilizará para confirmar aquellas operaciones para las que VXMLS no devuelve un objeto de la clase Menu y otro para interpretar las descripciones de los objetos Menu. La estructura de estos documentos que describen respuestas de VXMLS es muy simple, consisten en la etiqueta raı́z vxmls con, bien una etiqueta ok anidada para confirmar la operación, bien una etiqueta error con un mensaje de error anidado para indicar un fallo en la operación. La estructura de los documentos que describen un objeto de la clase Menu son bastante más complejos y se explicarán en la siguiente sección. En el apéndice A se pueden ver los DTDs que especifican la estructura de ambos documentos. 4.3.2. Descripción de los menús 4.3.2.1. Introducción La estructura de los documentos XML utilizada para la descripción de los menús debe atenerse al hecho de que el número de acciones irá aumentando con el paso del tiempo. Documentos XML 49 La interfaz Action será representada por la etiqueta action que no tiene argumentos. Anidadas dentro de esta etiqueta irán las etiquetas especı́ficas de cada acción a realizar. De esta forma se mantiene la idea del diseño de mostrar todas las acciones bajo una interfaz común. 4.3.2.2. Descripción del menú Teniendo en cuenta la clase Menu diseñada en el apartado 4.2, los documentos XML utilizados para definir un objeto perteneciente a dicha clase podrán contener las siguientes etiquetas (ver apéndice A). header: Contendrá el texto a mostrar como tı́tulo del menú. version: Versión de la definición de menú utilizada, DFBCIn deberá actualizarse cuando reciba un menú con una versión mayor a la que soporte. timer: Tiene un atributo time que indica el tiempo a esperar sin recibir eventos del usuario antes de ejecutar las acciones contenidas por esta etiqueta. initActions: Anidadas en esta etiqueta están las acciones a realizar en el momento en que se cargue el menú. option: Representa a la clase option, tendrá anidadas las etiquetas: • text Contiene el texto a mostrar al usuario para identificar la opción. • action Cada opción tendrá anidado un número de etiquetas de este tipo para definir las acciones a realizar cuando se seleccione dicha opción. Las etiquetas que describen cada implementación de la interfaz Action. Por ejemplo, las siguientes etiquetas XML describen un menú con un bloque de acciones a realizar en el momento en el que sea cargado, otro bloque de acciones que se ejecutarán en caso de que pasen diez segundos sin que se detecte ningún evento y dos opciones (el contenido de las etiquetas action se explica en la siguiente sección): <menu> <header>Menu de ejemplo</header> <version>0.0</version> <initActions> 50 Capı́tulo 4. Diseño <action>...</action> <action>...</action> </initActions> <timer seconds="10"> <action>...</action> </timer> <option> <text>Primera opción</text> <action>...</action> <action>...</action> </option> <option> <text>Segunda opción</text> <action>...</action> </option> </menu> 4.3.2.3. Descripción de las acciones Anidadas dentro de la etiqueta action se encontrarán las etiquetas que describen cada acción especı́fica. Cada etiqueta que represente a una acción deberá describir, ya sea mediante atributos, texto o nuevas etiquetas anidadas, un objeto perteneciente a la clase implementación de Action correspondiente (ver la sección 4.2.3). Las etiquetas que describen las distintas acciones son las siguientes: nextMenu: Representa una acción de tipo NextMenuAction, nextMenu contiene el valor del atributo con el mismo nombre del objeto que se está definiendo. Para especificar los argumentos que se deben pasar a VXMLS cuando se solicita el menú se utilizarán etiquetas menuArgument anidadas en el interior de la etiqueta nextMenu. Estas etiquetas nextArgument deben tener los siguientes atributos: • name: Nombre del argumento. • value: Valor del argumento. Por ejemplo, una acción para solicitar el menú movie list menu con los atributos filter=shown y genre=adventure se utilizará una etiqueta como esta: Documentos XML 51 <action> <nextMenu name="movie_list_menu"> <menuArgument name="genre" value="adventure"/> <menuArgument name="filter" value="shown"/> </nextMenu> </action> previousMenu: Describe una acción de tipo PreviousMenuAction. Contiene los atributos depth, que contiene el valor de atributo returnDepth del objeto definido, y reinit, que representa al atributo con el mismo nombre de la clase PreviousMenuAction. hideMenu: Representa una acción de tipo HideMenuAction. showMenu: Representa una acción de tipo ShowMenuAction. reloadMenu: Representa una acción de tipo ReloadMenuAction. changeOption: Describe una acción de tipo ChangeOptionAction. Para definir los atributos del objeto a crear, esta etiqueta contiene dos atributos: value, que representa al atributo value del objeto, y mode. El atributo mode puede ser relative con lo que el atributo absolute del objeto creado serı́a falso, o relative, con lo que el atributo absolute del objeto serı́a verdadero. playVideo: Se utiliza para describir acciones del tipo PlayVideoAction. Contiene un único atributo file que contiene la referencia al vı́deo a reproducir (atributo video de la clase PlayVideoAction). Anidadas dentro de esta etiqueta habrá otras etiquetas action, que representan las acciones a ejecutar cuando la reproducción de vı́deo alcance su fin. Por ejemplo, las siguientes etiquetas definen una acción que inicia la reproducción de un vı́deo de forma que cuando finalice se carga un nuevo menú se muestra por encima del vı́deo ya parado. <action> <playVideo file="video.avi"> <action> <stopVideo/> </action> <action> <nextMenu name="video_end_menu"> </nextMenu> 52 Capı́tulo 4. Diseño </action> <action> <showMenu/> </action> </playVideo> </action> stopVideo: Representa una acción de tipo StopVideoAction. resumeVideo: Representa una acción de tipo ResumeVideoAction. unloadVideo: Representa una acción de tipo UnloadVideoAction. seekVideo: Representa una acción de tipo SeekVideoAction. Para especificar los atributos del objeto a crear, esta etiqueta contiene dos atributos: mode, que define el valor de atributo con el mismo nombre del objeto a crear, y time, que define el valor del atributo time de dicho objeto. El atributo mode podrá tomar los valores forward, backward o absolute. bindKey: Describe una acción de tipo BindKeyAction. Como atributos únicamente tiene key que especifica la tecla que se quiere enlazar. Para definir a qué se quiere enlazar dicha tecla, una etiqueta bindKey puede contener una de estas dos etiquetas: • keyActions: Anidadas dentro de esta etiqueta irán un cierto número de etiquetas action que representan las acciones a ejecutar cuando el usuario pulse la tecla en cuestión. Por ejemplo, para hacer que una pulsación de la tecla UP cambie la opción seleccionada por la opción anterior: <action> <bindKey key="UP"> <keyActions> <action> <changeOption value="-1" mode="relative"/> </action> <action> <showMenu/> </action> </keyActions> </bindKey> </action> Protocolo de comunicación 53 • keyEvent: Tiene un atributo type que define el comportamiento que debe seguir DFBCIn cuando un usuario pulse la tecla vinculada. Los valores que puede tomar type son: ◦ EXECUTE: Indica que se de deben ejecutar las acciones vinculadas a la opción actualmente seleccionada. ◦ NULL: La pulsación de una tecla vinculada a un evento NULL no provoca ningún tipo de reacción en DFBCIn. Por ejemplo, para hacer que el botón SEL provoque la ejecución de las acciones de la opción seleccionada: <action> <bindKey key="SEL"> <keyEvent type="EXECUTE"/> </bindKey> </action> setProperty: Representa una acción de tipo SetPropertyAction. Para describir el objeto a crear, esta etiqueta utiliza los atributos name y value, cuyos nombres son idénticos a los de los atributos del objeto que se está definiendo. showText: Representa una acción de tipo ShowTextAction. Contiene un grupo de etiquetas textLine que describen las lı́neas de texto a mostrar. Por ejemplo, para describir una acción que muestra el resumen de una pelı́cula: <action> <showText> <textLine>Habiendo marchado en busca de</textLine> <textLine>fortuna y después de unos</textLine> <textLine>a~ nos ...</textLine> </showText> </action> hideText: Representa una acción de tipo HideTextAction. loadMp3: Representa una acción de tipo LoadMp3Action. Contiene el atributo path para describir el atributo con el mismo nombre del objeto definido. pauseContinueMp3: Representa una acción de tipo PauseContinueMp3Action. killMp3Player: Representa una acción de tipo KillMp3PlayerAction. 54 Capı́tulo 4. Diseño 4.4. Protocolo de comunicación La comunicación entre DFBCIn y VXMLS se hará siguiendo el estándar HTTP [35]. DFBCIn hará peticiones GET al servidor VXMLS. Este devolverá un documento XML encapsulado en una respuesta HTTP. Un mensaje GET permite solicitar un documento dada una URL, en esta petición se puede incluir el nombre del documento a solicitar y un número indeterminado de argumentos. Por lo tanto, para permitir la comunicación entre DFBCIn y VXMLS será necesario definir las URL a las que DFBCIn deberá referirse cuando necesite solicitar información de VXMLS, o solicitar que VXMLS realice alguna operación. Las operaciones que VXMLS debe permitir realizar son: Creación de una nueva sesión de usuario. Destrucción de la sesión de usuario. Petición de la descripción de un determinado menú. Cambio de un parámetro de personalización. Cada una de estas acciones será una URL a la que se le pasarán los argumentos necesarios. VXMLS responderá con un documento XML que DFBCIn podrá interpretar para obtener el resultado esperado. Suponiendo que la URL que apunta al servidor VXMLS sea http://vxmls, las peticiones HTTP para realizar las operaciones anteriores serán: Para solicitar una nueva sesión para el cliente settopbox que tiene como clave palabrasecreta, DFBCIn deberá hacer una petición GET a la URL: http://vxmls/login?client=settopbox&passwd=palabrasecreta Para destruir la sesión: http://vxmls/logout Para solicitar el menú main menu, suponiendo que no se necesiten argumentos adicionales para dicho menú, la URL a solicitar será la siguiente: http://vxmls/get_menu?menu=main_menu Diseño de DFBCIn 55 En caso de que la petición de menú necesite argumentos, por ejemplo el menú pelı́culas con el argumento filtro con valor nuevas, la URL será: http://vxmls/get_menu?menu=pelı́culas&filtro=nuevas Para cambiar el la propiedad language a es, VXMLS deberá proporcionar la URL: http://vxmls/set_property?name=language&value=es 4.5. Diseño de DFBCIn 4.5.1. División en subsistemas Durante el análisis, se establecieron unos aspectos que serı́a deseable que cumpliera el diseño de la aplicación DFBCIn (sección 3.4.4), para aislar los módulos conflictivos de los módulos estables, evitando que los problemas y riesgos derivados de las partes más complicadas del sistema se propaguen por toda la implementación. La aplicación DFBCIn se compone de tres módulos básicos: Módulo gráfico: Será el encargado de realizar las operaciones gráficas necesarias para comunicarse con el usuario. Como efecto colateral, debido a la utilización de DirectFB, también se encargará del control de la entrada. Esto es ası́ ya que es deseable que se puedan utilizar distintos módulos gráficos (en forma de plugins) sin necesidad de recompilar el resto de la aplicación, incluso módulos gráficos que no utilicen DirectFB. Puesto que la utilización de DirectFB condiciona la forma en la que se realiza el control de la entrada, la implementación de la entrada deberá estar en este módulo, aunque en el diseño lógico se considerará un submódulo. Un diseño en el que el módulo de entrada fuera independiente del módulo gráfico serı́a más correcto teóricamente, pero en la práctica siempre estarı́a fuertemente acoplado al módulo gráfico. Módulo de comunicación con VXMLS 1 : Se compondrá de un parser y una implementación del protocolo HTTP. Para el resto del sistema será una caja negra a la que se le pueden solicitar los menús (y el resto de operaciones que puede realizar VXMLS) indicados por las acciones que lo requieran. 1 de aquı́ en adelante módulo VXMLS, para abreviar 56 Capı́tulo 4. Diseño Motor de la aplicación: Será el encargado de coordinar la actuación de los dos módulos anteriores y de ejecutar los objetos de la clase Action. VXMLS Libreria grafica graphics.h Motor Modulo VXMLS Figura 4.7: Módulos de DFBCIn El módulo gráfico se implementará como una biblioteca dinámica, de forma que se puedan intercambiar distintos módulos que cumplan la interfaz esperada sin necesidad de recompilar el sistema. Los otros dos módulos formarán el programa que se enlazará dinámicamente con el módulo gráfico seleccionado. La figura 4.7 representa al sistema final, funcionando junto con el sistema VXMLS. 4.5.2. Interacción entre módulos En la figura 4.8 se describe un ejemplo de interacción entre los distintos módulos del sistema2 . Se observa como el módulo gráfico actúa como fachada (patrón Facade [38]), aislando la entrada y la salida del resto del sistema, y como el módulo VXMLS actúa como adaptador de VXMLS (patrón Adapter [38]), traduciendo las peticiones del motor para que sean comprensibles para VXMLS y las repuestas de VXMLS para que sean comprensibles para el resto de la aplicación. El motor controla la ejecución de ambos módulos. El diagrama muestra la secuencia que sigue el sistema en su inicio y un par de ejemplos de reacción ante distintos eventos. Los sucesos relevantes son los siguientes: 1. El motor inicia su ejecución y solicita al módulo VXMLS que se inicialice. 2. El módulo VXMLS pide una nueva sesión al servidor VXMLS, cuando éste le confirma la operación devuelve el control al motor. 2 Las funciones y mensajes son una simplificación de las interfaces entre los módulos. Diseño de DFBCIn 57 Modulo grafico Motor Modulo VXMLS VXMLS init() login ok init() getMenu(mainMenu) getMainMenu documento XML menu showMenu(menu) menu visual evento de usuario evento de entrada changeOption(newOption) menu visual evento de temporizacion setProperty(language, es) setLanguage(es) Figura 4.8: Interacción entre los módulos de DFBCIn 3. El motor inicializa el módulo gráfico. 4. El motor solicita al módulo VXMLS el objeto menu que representa el primer menú a mostrar al usuario. 5. El módulo VXMLS hace una petición HTTP a VXMLS solicitando la descripción del menú principal, recibe la respuesta de VXMLS y la transforma en el objeto Menu solicitado. 6. El motor le solicita al módulo gráfico que muestre el menú en la pantalla. 7. El usuario reacciona ante el menú y genera algún tipo de evento, por ejemplo pulsa una tecla que deberı́a cambiar la opción seleccionada. 8. El módulo gráfico detecta este evento y se lo comunica al motor. 9. El motor obtiene el conjunto de acciones relacionadas con el evento notificado por el módulo gráfico y las ejecuta. 10. La acción que deberı́a cambiar la opción provoca que el motor mande un mensaje al módulo gráfico para que muestre el cambio por pantalla. 11. El motor detecta un evento de temporización y ejecuta las acciones definidas para dicho evento. 12. Esta vez la acción ejecutada debe cambiar el lenguaje. Para ello, el motor solicita al módulo VXMLS que realice este cambio. 58 Capı́tulo 4. Diseño 13. El módulo VXMLS envı́a un mensaje HTTP a VXMLS solicitando el cambio de lenguaje a VXMLS, espera a la confirmación y comunica al motor el éxito o fracaso de la operación. Hay varios eventos que pueden provocar la ejecución de acciones por parte del motor, algunos de estos eventos son detectados por la biblioteca gráfica (los eventos provocados por la entrada del usuario o durante la reproducción de un vı́deo) y otros son controlados por el motor (los eventos de temporización y la inicialización de menús). Durante la ejecución de las acciones, el motor utilizará el módulo gráfico y el módulo VXMLS para comunicarse con los agentes externos al sistema (el usuario y VXMLS). En resumen, el flujo de información entre los tres módulos de DFBCIn y los dos agentes externos (usuario y VXMLS) es como sigue: El usuario se comunica con el módulo gráfico mediante los dispositivos de entrada necesarios (por ejemplo el mando a distancia) y recibe información del mismo a través del televisor. El módulo gráfico controla estos dispositivos. El módulo gráfico envı́a eventos al motor para que éste ejecute las acciones pertinentes. El motor se comunica con el usuario provocando cambios en la interfaz utilizando el API ofrecido por el módulo gráfico. El motor utilizará otro API para solicitar al módulo VXMLS que realice las operaciones que requieran comunicación con el servidor VXMLS. El módulo VXMLS enviará las peticiones HTTP necesarias y traducirá la respuesta transformándola en objetos comprensibles por el motor de la aplicación (normalmente instancias de la clase Menu). 4.5.3. Objetos comunes La clase Menu explicada en la sección 4.2.2 deberá ser implementada en DFBCIn para permitir la comunicación entre los tres módulos que la componen. Para almacenar información sobre el estado de los objetos Menu almacenados por DFBCIn, el diseño general de clase Menu deberá ser ligeramente extendido. La aplicación necesita saber para cada menú, cuáles son las teclas que están vinculadas a qué acciones. Para ello se añade una nueva clase Key que será referenciada por la clase Menu. Diseño de DFBCIn 59 Menu + majorVersion: Int + minorVersion: Int − header: String − time: Int Option 1..∗ + text: String for all actions do action.execute end + execute() + draw() 1..∗ initActions timeActions ∗ Key + key: String − event: String + execute() 0..∗ 0..* actions <<interface>> Action + execute() 0..* ACTIONS EXECUTE NULL Figura 4.9: Clase Menu en DFBCIn Para manejar información persistente se ha diseñado un pequeño sistema de propiedades que leerá y guardará la información de un fichero de texto. Este sistema permitirá a los distintos hilos de ejecución de la aplicación acceder concurrentemente al contenido del fichero y modificarlo en caso de que sea necesario. Los métodos exportados para el resto de la aplicación son tres: getPropertyValue: Devuelve el valor de una propiedad con un nombre dado. setPropertyValue: Cambia el valor de una determinada propiedad. saveProperties: Salva los datos almacenados en memoria en el disco. En los últimos incrementos, la mayorı́a de los parámetros de personalización pasan a ser almacenados por VXMLS, por lo que , finalmente, el sistema de propiedades solamente se utilizará para almacenar valores estáticos que no necesitan ser cambiados (el nombre y clave del Set Top Box, la dirección del servidor, etc). También se han diseñado dos clases para almacenar distintos tipos de información, la clase Stack (pila) y la clase Array. Ambas clases se pueden utilizar para guardar cualquier tipo de objeto3 . 4.5.4. Diseño del motor de la aplicación 4.5.4.1. Objetivos El objetivo de este módulo es el de controlar el funcionamiento del resto de módulos del sistema y el de realizar las acciones especificadas por los objetos 3 La implementación de estas clases en C presenta ciertos problemas a la hora de compartir objetos y de liberar memoria como se verá en el capı́tulo dedicado a la implementación de DFBCIn 60 Capı́tulo 4. Diseño Stack Array + empty(): Boolean + push(data: Object) + pop(): Object + seeTop(): Object + add(data: Object) + getData(position: Int): Object + elements(): Integer + flush() Figura 4.10: Clases para el almacenamiento de datos Action que deban ser ejecutados. Este módulo contiene la implementación de la parte más estable del sistema, ya que no está en contacto con ningún agente exterior. Básicamente, deberá implementar el ciclo de ejecución y las estructuras de datos para mantener la información necesaria para la ejecución de las acciones recibidas. Con respecto a los objetos del dominio, a este módulo le corresponde la implementación de los objetos pertenecientes a la clase Action. Será el módulo encargado de realizar las operaciones convenientes para ejecutar las acciones. 4.5.4.2. Estructura interna En la figura 4.11 se puede ver el diseño interno de este módulo. Básicamente, tiene cinco componentes: el objeto principal, un objeto encargado de controlar la interfaz con el usuario, un objeto que ayudará a éste controlando los eventos de entrada, un objeto que se encargará de reproducir archivos de audio y las implementaciones de la interfaz Action. Main: El método main será el método a invocar para iniciar la ejecución de la aplicación. Interface: Un objeto de esta clase se encargará de controlar la comunicación con la biblioteca gráfica, realizando las operaciones necesarias para mostrar la interfaz del usuario correctamente. InterfaceInput: Un objeto de esta clase se encargará de comunicarle a Interface los eventos de entrada de usuario que detecte con ayuda de la biblioteca gráfica. Interface e InterfaceInput son una aplicación simplificada del patrón Observer [38]. InterfaceInput avisará a Interface cuando detecte un evento de entrada y lo almacenará hasta que Interface pueda atenderlo. Mp3Player : Un objeto de esta clase se encargará de reproducir ficheros de audio. Diseño de DFBCIn 61 <<interface>> Action + execute() Mp3Player − path: String + load(path: String) + pauseContinue() Interface Modulo VXMLS − actualMenu: Menu Main + main() Recibe gupos de acciones de interface y para cada una de ellas ejecuta Action.execute + loadMenu(menu: Menu) + showMenu() + hideMenu() + unloadMenu() + loadVideo(video: String, endActions: array) + playVideo() + stopVideo() + resumeVideo() + unloadVideo() + waitForActions(): Array + notifyInputEvent() + notifyVideoEnd() + notifyNewFrame() Modulo Grafico interfaceInput.getInputKey() InterfaceInput − inputKey: Key + getInputKey(): Key − notify() interface.notifyInputEvent() Figura 4.11: Clases del motor Action: Las diferentes implementaciones de esta interfaz podrán utilizar tanto el módulo VXMLS como el módulo gráfico para realizar las operaciones necesarias para la ejecución del método execute. La mayorı́a de las operaciones gráficas deberán hacerse utilizando el objeto Interface. Interface, InterfaceInput y Mp3Player son clases Singleton [38], la aplicación instanciará un único objeto de cada una de estas tres clases. La interacción entre los distintos objetos que componen este módulo se ha ejemplificado en la figura 4.12. En ella se puede ver como Main notifica a Interface que está a la espera de nuevas acciones a ejecutar. Cuando InterfaceInput detecta un nuevo evento de entrada se lo hace saber a Interface y espera a que Interface puede consultar ese evento. Interface comprobará si hay un grupo de acciones relacionadas con el evento detectado y, de ser ası́, se las envı́a a Main para que las ejecute. Los métodos notifyNewFrame y notifyVideoEnd serán utilizados por el módulo gráfico para avisar a Interface de que hay un nuevo frame de vı́deo listo para ser mostrado o de que el final del vı́deo ha sido alcanzado. En este diagrama se han obviado las acciones de inicialización. También se debe tener en cuanta que no sólo los eventos de entrada pueden provocar la 62 Capı́tulo 4. Diseño Main Graphics create() create() InterfaceInput Interface waitForActions() notifyInputEvent() evento de entrada getInputEvent() Array InputKey Figura 4.12: Detección de eventos de entrada ejecución de acciones. Interface puede detectar otro tipo de eventos y devolver el grupo de acciones correspondiente al evento detectado. 4.5.4.3. Ciclo de ejecución de Main El ciclo de ejecución la aplicación (método main de la clase Main) se puede ver en la figura 4.13. Inicializacion Menu Principal Esperando acciones Ejecutando acciones Figura 4.13: Ciclo de ejecución 1. En primer lugar deberá inicializar todos los subsistemas necesarios, es decir, el módulo gráfico y el módulo VXMLS. 2. El segundo paso consiste en obtener el objeto que representa al menú principal y pasárselo al módulo gráfico para que lo muestre por pantalla. Diseño de DFBCIn 63 3. Tras estos dos pasos iniciales el motor deberá esperar a que el objeto Interface entregue un bloque de acciones a ejecutar. 4. Una vez obtenidas las acciones, el motor pasará a ejecutarlas (para cada objeto de la clase Action recibido, se invocará su método execute). Cuando termine la ejecución del bloque completo volverá al estado de espera por un nuevo bloque de acciones. Es importante señalar que no se contempla la finalización del programa como un comportamiento normal. La aplicación sólo terminará en caso de que se produzca una situación anormal. Para el desarrollo, se ha definido una tecla EXIT que provoca la finalización de la aplicación generando una notificación de error “artificialmente”. La aplicación definitiva sólo deberı́a terminar en caso de que se apague el Set Top Box, esto se considerará una situación excepcional y su control será idéntico al que se hace con los errores graves, que provocan la finalización de la aplicación. 4.5.4.4. Máquinas de estados de Interface e InterfaceInput En el diagrama de estados de la figura 4.14 se puede observar el proceso mediante el cual InterfaceInput notifica que ha detectado un nuevo evento de entrada. InterfaceInput bloquea la detección de eventos hasta que Interface recibe el evento detectado. Esto se podrı́a evitar con una cola de eventos, pero eso se supone que lo hará la biblioteca gráfica en caso de que sea necesario. waitForActions() notifyInputEvent()/ getInputKey() Reposo Sin Eventos nuevo evento/notify() getInputKey() notifyInputEvent()/ getInputKey() waitForActions() EventoAlmacenado Acciones listas Estados de InterfaceInput Esperando acciones Estados de Interface Figura 4.14: Estados de InterfaceInput e Interface En el caso de los estados de Interface, todas las transiciones provocadas por notifyInputEvent también podrán ser provocadas por notifyVideoEnd, pero para este caso, el módulo gráfico no cambia de estado tras la notificación de final de vı́deo (no se puede producir un final de vı́deo imprevisto antes de que Interface ejecute las acciones necesarias debidas al final notificado). 64 Capı́tulo 4. Diseño La máquina de estados de Interface es mucho más compleja que la mostrada en la figura 4.14, además los de estados referentes a las acciones a ejecutar, también debe mantener control sobre el estado del menú actual y sobre la reproducción de vı́deo. Además los estados mostrados en esta figura son una pequeña simplificación: puede ser que la tecla detectada por InterfaceInput no tenga asociada ninguna acción. Por todo ello, es más sencillo representar sus transiciones como tres máquinas de estados concurrentes, aunque el estado real sea una combinación de los tres estados en los que se halle cada una de las submáquinas de estados. En la figura 4.15 se puede ver con más detalle la evolución de Interface durante su ciclo de ejecución. notifyVideoEnd()/getVideoActions() inicializacion de menu temporizacion waitForActions() tecla vinculada tecla EXEC Reposo notifyVideoEnd()/getVideoActions() inicializacion de menu temporizacion notifyInputEvent()/ getInputKey() Sin Menu loadMenu(menu) unloadMenu() Menu Cargado tecla no vinculada Sin Video unload() load(video) Video Cargado waitForActions() play() Reposo−Comprobacion de tecla tecla vinculada tecla EXEC Acciones listas Esperando acciones notifyInputEvent()/ getInputKey() resumeVideo() stopVideo() gotoVideoPosition() Reproduciendo tecla no vinculada Esperando−Comprobacion de tecla Figura 4.15: Máquina de estados de Interface Como se puede ver, los estados referentes al vı́deo y a los menús son más simples de lo que cabı́a suponer. El control real lo llevará el módulo gráfico. Interface simplemente necesita saber cuándo se está reproduciendo vı́deo y cuándo no y cuándo tiene un menú cargado. El estado referente a si el menú es visible, si el vı́deo esta parado, etc. será controlado por la biblioteca gráfica. 4.5.4.5. Ejecución de acciones La ejecución de ciertas acciones obliga a mantener algún tipo de estado durante el ciclo de ejecución del motor de la aplicación. Diseño de DFBCIn 65 Las acciones NextMenuAction y PreviousMenuAction obligan a que se lleve un control de los menús anteriores al actual, esto se hará con una pila en la que se van almacenando los menús requeridos por NextMenuAction y de la que se van eliminando los menús descartados por PreviousMenuAction. La acción BindKeyAction necesita un mecanismo para almacenar las acciones vinculadas a las teclas para cada menú que se encuentre en la pila de menús. Para solucionar esto se ha ampliado la clase Menu como se explicó en la sección 4.5.3. El resto de variables de estado (en la implementación actual el estado de un usuario sólo contempla las pelı́culas vistas y el lenguaje) serán mantenidas por el servidor VXMLS. 4.5.4.6. Reproducción de audio La reproducción de audio no influye en el resto del sistema, el reproductor comenzará a reproducir un fichero cuando sea cargado utilizando el método load y detendrá la reproducción cuando se invoque su método pauseContinue. La siguiente llamada a pauseContinue provocará que se reanude la reproducción. Para comenzar la reproducción de vı́deo será necesario eliminar este objeto para que libere el dispositivo de audio, por lo tanto, load será un método de clase, que se encargará de instanciar el objeto correspondiente en caso de que aún no exista. 4.5.5. Diseño del módulo VXMLS 4.5.5.1. Objetivos Este módulo deberá actuar como adaptador entre el motor y el servidor VXMLS, ocultando al resto del sistema la comunicación con VXMLS. En los primeros incrementos del sistema, este módulo no obtendrá las descripciones de los menús de VXMLS sino que utilizará medios de almacenamiento estáticos (en las primeras fases ficheros locales y en fases más avanzadas se comunicará con un servidor HTTP estático). En el diagrama de secuencia de la figura 4.8 muestra cómo interactúa este módulo con VXMLS para todas las operaciones enumeradas excepto para la liberación de recursos. Esta última operación consiste simplemente en el envı́o de un mensaje logout a VXMLS para que destruya la sesión creada en la inicialización del sistema. 66 Capı́tulo 4. Diseño 4.5.5.2. Componentes del sistema La interfaz ofrecida por el módulo VXMLS al resto del sistema proporciona las siguientes operaciones. Inicialización: Esta operación deberá ser ejecutada una sola vez antes de realizar cualquier otra operación sobre este módulo. El módulo VXMLS solicitará una nueva sesión VXMLS y creará las estructuras de datos necesarias. Liberación de recursos: Provocará la liberación de la memoria ocupada por los datos internos de este módulo y la destrucción de la sesión creada para el usuario en VXMLS. Cambio de un valor de personalización: Los parámetros de personalización tienen un nombre y un valor, esta función permitirá solicitar a VXMLS que modifique el valor de un parámetro dado. Los únicos parámetros soportados en la implementación final son el usuario activo (cada Set Top Box tiene su cuenta en VXMLS y dentro de la misma puede mantener datos de varios usuarios) y el lenguaje a utilizar para el usuario activo. También se incluye dentro de este tipo de operación el hecho de marcar una pelı́cula como vista. Solicitud de una descripción de un menú. Devuelve un objeto de la clase Menu describiendo el menú solicitado. Este módulo contiene dos componentes principales: el parser encargado de interpretar los documentos XML que VXMLS envı́a como respuesta a las peticiones de menús y una implementación parcial del protocolo HTTP que permita la comunicación con VXMLS. La interacción entre estos componentes se puede ver en la figura 4.16. Motor Parser Modulo HTTP VXMLS getMenu() peticion de menu en un mensaje HTTP documento XML en un mensaje HTTP documento XML objeto menu setProperty() mensaje HTTP confimacion XML en un mensaje HTTP documento XML confirmacion Figura 4.16: Interacción entre los componentes del módulo VXMLS Diseño de DFBCIn 67 Las operaciones login y logout son similares a la operación setProperty. Internamente, el módulo VXMLS se organizará como se puede ver en la figura 4.17. Se compone de tres clases utilidad. VXMLSFacade: Fachada que proporcionará las operaciones especificadas por la interfaz al resto del sistema (patrón Facade [38]). Se encargará de componer la petición HTTP correcta, enviarla con ayuda de la clase HTTP e interpretar la respuesta de VXMLS utilizando los métodos de la clase Parser. HTTP : Controla la comunicación con el servidor HTTP. Se encargará de transmitir el mensaje GET para una URL de un servidor que esté escuchando HTTP en un determinado puerto y de devolver la respuesta del servidor como un flujo de datos. Parser : Interpreta los documentos XML obtenidos de VXMLS. <<utility class>> VXMLS facade + init + deinit() + getMenu(name:String, arguments:Array) + setProperty(name:String, value:String) <<utility class>> <<utility class>> HTTP Parser + httpGet(server:String, port:Int, url:String): Stream + escapedEnconding(string:String): String + parseMenu(menuDocument: Document):Menu + parseVXMLSResponse(response: Document): Boolean Figura 4.17: Clases del módulo VXMLS 4.5.5.3. Diseño del parser Es importante tener especial cuidado en el desarrollo de la clase Parser, deberá diseñarse de forma que sea fácil añadir código para interpretar las nuevas acciones que se vayan añadiendo al sistema. Como se vio en la sección 3.4.3, la biblioteca expat implementa un parser genérico en el que hay que definir ciertas funciones. Para los objetivos de este proyecto, las funciones que se deben implementar son las de apertura y cierre de etiquetas y la de sección de caracteres. Dichas funciones deben compartir algún objeto para almacenar información durante el proceso de interpretación del documento XML. 68 Capı́tulo 4. Diseño Para almacenar la información necesaria a lo largo del proceso de parsing se utilizará un objeto de la clase ParserData, que contiene una pila (ver la sección 4.5.3) y un objeto Menu que se irá construyendo a medida de que se disponga de la información necesaria. En la pila se almacenará objetos de la clase Context con la información necesaria para el tratamiento de las etiquetas abiertas hasta el momento. Normalmente, en caso de que sea necesario, se apilará un nuevo contexto durante la apertura de la etiqueta y se desapilará durante el cierre de la etiqueta. De esta forma, una etiqueta puede acceder a la información de todas sus etiquetas padre. El diseño de clases del parser puede verse en la figura 4.18. Expat ParserData MenuParser − parserData: ParserData + parse(document: Stream): Menu unconpleteMenu Menu contextStack Stack + pushContext(context: Context) + popContext(): Context + seeTopContext(): Context TagManager + startTag(tag: Tag, arguments: Array, parserData: ParserData) + endTag(tag: Tag, parserData: ParserData) + characterData(tag: Tag, data:String, paserData: ParserData) Context 0..∗ OptionContext + option: Option CharacterContext + text: String ActionContext + action: Action HeaderContext TextContext VersionContext BindKeyContext + key: Key + majorVersion: Int + minorVersion: Int Figura 4.18: Clases utilizadas para la construcción del parser para descripciones de menús La clase Context es abstracta para permitir la ampliación de los tipos de información almacenables en caso de que sea necesario. Por ejemplo, cuando se añadió la acción BindKey (ver la sección 4.2.3) fue necesario añadir una nueva estructura para almacenar información durante el proceso de parsing de las etiquetas que definen dicha acción. Con este diseño, simplemente hizo falta extender la clase Context y crear una nueva clase para la etiqueta especı́fica. Las funciones de apertura y cierre de la etiqueta action deberán ser capaces de manejar las acciones como clases abstractas que son. Para ello, en la apertura de una etiqueta action se apilará un nuevo objeto de la clase ActionContext cuyo atributo action será una acción vacı́a. Las funciones que controlan las etiquetas Diseño de DFBCIn 69 que pueden aparecer anidadas dentro de una etiqueta action deberán crear el objeto de una subclase Action concreta y colocarlo en el contexto apilado para la etiqueta action. El control de las acciones a realizar para la apertura y cierre de cada etiqueta lo hará la clase utilidad TagManager. La implementación de esta clase también deberá ser adaptada a medida que vayan apareciendo nuevas etiquetas a tratar. Aún ası́, la utilización de la pila de contextos permite que el código necesario para el tratamiento de las nuevas etiquetas que aparezcan para describir acciones no afecte al código ya existente: El código para manejar las etiquetas anidadas dentro de action solamente necesita tener en cuenta los datos que hay en la pila por encima del contexto colocado en la apertura de la etiqueta action. El código que maneja la etiqueta action espera que cuando se cierre esta etiqueta, el atributo action del contexto de la cima de la pila sea la acción completa, sin importar que implementación concreta de la interfaz Action sea. El código para manejar el resto de etiquetas no se ve afectado, ya que no hay datos referentes a las acciones en la pila. Normalmente, una etiqueta que pueda tener texto anidado colocará un contexto CharacterContext en la cima de la pila durante su apertura, La función characterData escribirá el texto en dicho contexto y durante el cierre de la etiqueta se procesará dicho texto. Esto se hace ası́ porque expat no asegura que el texto contenido por las etiquetas sea leı́do en un solo bloque, sino que podrá ser leı́do en varios bloques provocando varias llamadas a la función characterData. El parser utilizado para las repuestas de VXMLS es mucho más sencillo y puede ser desarrollado directamente, sin necesidad de una pila. 4.5.5.4. Diseño del módulo HTTP Este módulo será el encargado de enviar peticiones GET HTTP [35] a un servidor y devolver su respuesta a través de un descriptor de fichero, de forma que pueda ser interpretada por el parser. El diseño de este módulo no presenta ninguna dificultad digna de mención, el proceso a seguir ante una llamada a la función httpGet4 para enviar una petición a un puerto de un servidor determinado será: 4 La sintaxis exacta para esta función en C es int httpGet(const char *server, int port, const char *request, int fileDes). 70 Capı́tulo 4. Diseño 1. Establecer una conexión TCP con el servidor en el puerto especificado. 2. Componer la cabecera HTTP a enviar al servidor. 3. Enviar dicha cabecera y esperar por la respuesta. 4. Interpretar la cabecera HTTP de la respuesta, tomar medidas en caso de que sea necesario (por ejemplo, almacenar las cookies) y escribir el contenido de la respuesta en el descriptor de fichero. 5. Cerrar el descriptor de fichero en cuanto se detecte el final del mensaje (para poder utilizar un pipe con el parser ). La función escapedEncoding se utilizará para codificar las cadenas de texto que forman la URL solicitada de acuerdo con las especificaciones de [42]. 4.5.5.5. Funciones de la fachada VXMLSFacade VXMLSFacade oculta la estructura interna de este módulo al resto del sistema. Para implementar las funciones que ofrece su API utilizará los dos submódulos comentados anteriormente y el subsistema de propiedades (ver 4.5.3). init: Durante la inicialización, VXMLSFacade obtendrá del sistema de propiedades el nombre y clave del Set Top Box, ası́ como el servidor y puerto en el que está escuchando VXMLS. Con estos datos utilizará el módulo HTTP para enviar a VXMLS una petición de nueva sesión de usuario. Finalmente, utilizará el parser VXMLS para asegurarse de que VXMLS confirma la operación o para obtener el mensaje de error en caso contrario. deinit: Durante la destrucción del módulo VXMLS la fachada enviará un mensaje a VXMLS para que destruya la sesión VXMLS (obteniendo el servidor y puerto del sistema de propiedades y enviando el mensaje con ayuda del módulo HTTP como en el caso anterior). Interpretará la respuesta de VXMLS con el parser VXMLS. setProperty: Enviará un mensaje a VXMLS notificando el nuevo valor para la propiedad5 de forma similar a como se explicó en los dos casos anteriores. getMenu: De nuevo enviará un mensaje solicitando un menú con ayuda del sistema de propiedades y el módulo HTTP. Interpretará la respuesta del módulo HTTP con ayuda del parser de menús y devolverá el objeto Menu correspondiente al menú solicitado. 5 estas propiedades las almacena el servidor, las propiedades locales las maneja el sistema de propiedades local. Diseño de DFBCIn 4.5.6. Diseño del módulo gráfico 4.5.6.1. Objetivos 71 La función de este módulo será la de controlar tanto los interfaces de salida como los de entrada. La inclusión del control de la entrada en este módulo es un efecto colateral de la utilización de DirectFB, como se explicó en la sección 3.4.2. Asimismo como interfaz de salida no sólo se utilizará la pantalla, sino que también se controlará el dispositivo de audio durante la reproducción de vı́deo. Por estos dos motivos, este módulo podrı́a haberse llamado módulo de entrada/salida, pero el API que ofrece al resto del sistema solamente contiene operaciones para modificar los gráficos mostrados en pantalla y una función para obtener un evento de entrada. Este módulo será un componente intercambiable del sistema final, se podrán elegir diferentes implementaciones del mismo API sin necesidad de recompilar la aplicación. Se han desarrollado dos módulos gráficos distintos, uno de pruebas que utilizará la consola de texto para mostrar los menús y que mostrará mensajes en lugar de realizar operaciones tales como reproducir vı́deo y el módulo gráfico “de verdad” con todas las operaciones necesarias para permitir la ejecución correcta de las acciones implementadas en el sistema. En las siguientes secciones se describirá el diseño que se ha seguido para la implementación completa utilizando DirectFB, en la sección 4.5.6.4 se explicará brevemente qué objetos deberán cambiar para la implementación del módulo simplificado para las pruebas. El diseño de ambos módulos es idéntico, lo único que cambia es la implementación de los métodos. 4.5.6.2. Funciones del API El API que deberá implementar un componente que pretenda ser utilizado como módulo gráfico para DFBCIn deberá ofrecer las siguientes operaciones (el API formal se puede ver en el apéndice B): init y deinit: Se asegura que DFBCIn llamará una sola vez a la función init antes de llamar a cualquier otra función del API. También asegura que se llamará a la función deinit antes de terminar la ejecución y que no se llamará a ninguna otra función del API después de la llamada a deinit. loadMenu: Carga un objeto Menu en el módulo gráfico. 72 Capı́tulo 4. Diseño unloadMenu: Elimina el objeto Menu actual del módulo gráfico. setBox: Especifica si se debe dibujar una caja semitransparente rodeando al menú. loadVideo: De forma similar a loadMenu, establece el “vı́deo actual”. playVideo: Inicia la reproducción del vı́deo actual. stopVideo: Congela la reproducción de vı́deo. resumeVideo: Continúa con la reproducción de vı́deo. getVideoPosition: Devuelve la posición de vı́deo actual. gotoVideoPosition: Posiciona el vı́deo. blitVideo: Copia el frame actual en la pantalla. unloadVideo: Libera los recursos consumidos para la reproducción de vı́deo. setNewFrameCallback: Establece un método que será invocado por el módulo gráfico cada vez que haya un nuevo frame disponible. setVideoEndCallback: Establece el método a invocar por el módulo gráfico cuando se alcance el final del vı́deo. flipScreen: Este será el método que deberá ser llamado por el motor de la aplicación para hacer visibles los cambios de estado hechos utilizando el resto de métodos del API. Cuando se invoque esta acción, el módulo gráfico deberá dibujar bien el fondo de pantalla, bien el último frame de vı́deo disponible, y por encima el menú cargado en caso de que lo haya. testInput: Bloquea la ejecución hasta que haya un nuevo evento de entrada disponible. En caso de que ya se hubiera detectado un evento de entrada anterior a esta llamada, será devuelto ese evento sin bloquear la ejecución. Hay que tener en cuenta ciertas consideraciones adicionales a la hora de implementar un nuevo componente que respete este API: El objeto menú cargado con loadMenu pasa a estar compartido con el resto de la aplicación, por lo tanto, será necesario bloquearlo cuando se quiera acceder a su información. La operación loadMenu no hace el menú visible, es necesario llamar a flipScreen para ello; lo mismo ocurre con hideMenu. Diseño de DFBCIn 73 Entre las llamadas a loadVideo y unloadVideo, las llamadas a flipScreen provocarán que se dibuje el frame actual como fondo. 4.5.6.3. Subsistemas del módulo En el diseño de este módulo se debe hacer especial hincapié en aislar el código conflictivo lo más posible, ya que este módulo implementa dos partes especialmente problemáticas del sistema: el control de la entrada de usuario y la utilización del hardware gráfico. El diagrama de clases de la figura 4.19 muestra la división que se ha hecho para aislar el código inestable en diferentes objetos. Todos los metodos delegan en el objeto correspondiente <<utility class>> GraphicsFacade Hardware E/S VideoPlayer InterfaceDraw − video: VideoObject − menu: Menu − showBox: Bool + gotoPosition(pos: Int) + load(video: String) + play() + stop() + unload() + videoBlit() + getPosition(): Int + setNewFrameCallback(callback: function) + setEndCallback(callback: function) − newFrame() − end() + loadMenu(menu: Menu) + unloadMenu() + setBox(showBox: Bool) + flipBuffer() DirectFB EventCatcher + testInput(): Key Figura 4.19: Clases utilizadas para la construcción módulo gráfico Como siempre, la estructura interna del módulo se oculta tras una aplicación del patrón Facade [38]. InterfaceDraw contiene el código encargado de dibujar los menús y el fondo en la pantalla. VideoPlayer contiene el código encargado de manejar los objetos de vı́deo. También deberá realizar operaciones de dibujo en la pantalla. EventCatcher que transformará los eventos detectados en objetos del tipo Key. EventCatcher contiene el código dependiente del dispositivo de entrada, de esta forma InterfaceInput se mantiene independiente con respecto al sistema de control de entrada utilizado (ver la sección 4.5.4.2). 74 Capı́tulo 4. Diseño El diseño de Interface, (ver la sección 4.5.4.2) y VideoPlayer es una simplificación del patrón Observer [38], VideoPlayer deberá avisar a Interface cada vez que decodifique un nuevo frame de video o cuando el vı́deo que se está reproduciendo finalice. La máquina de estados de VideoPlayer se puede ver en la figura 4.20. Lo único destacable de este diagrama es que las operaciones de posicionamiento pueden provocar la finalización del vı́deo, ası́ como retroceder un vı́deo ya finalizado. Sin Video unload() unload() load(video) Video Cargado play() stop() Reproduciendo Parado resume() fin del video setPosition() setPosition() setPosition() Finalizado Figura 4.20: Máquina de estados de VideoPlayer La máquina de estados de InterfaceDraw debe contemplar los casos de tener o no un menú cargado y tener o no texto cargado. 4.5.6.4. Módulo gráfico basado en la consola de texto Para la ejecución de ciertas pruebas se ha desarrollado un módulo gráfico que utiliza la consola de texto para representar los menús. El resto de operaciones como la reproducción de vı́deo o el cambio de buffer visible se representarán mediante mensajes de texto. Asimismo, este otro módulo gráfico sirve para validar el hecho de que se puedan intercambiar distintas implementaciones de este componente sin necesidad de recompilar el motor y el módulo VXMLS. El diagrama de clases para este sistema es el mismo que el mostrado en la figura 4.19, con la diferencia de que ninguna clase utiliza DirectFB. Las modificaciones que se introducen en este módulo son: Diseño de VXMLS 75 InterfaceDraw : Mostrará el menú actual en la consola de texto. VideoPlayer : Todas los métodos (excepto los de notificación, que se dejan con implementación vacı́a) muestran un mensaje informando que se ha llamado a ese método, sin realizar ninguna otra operación. InterfaceInput: Los eventos de entrada se detectarán de forma rudimentaria utilizando funciones de la biblioteca stdio de C. En la figura 4.21 se puede ver el aspecto visual que presentan ambas implementaciones. Figura 4.21: Aspecto visual de las dos implementaciones del módulo gráfico 4.6. Diseño de VXMLS 4.6.1. Introducción El diseño de la aplicación VXMLS se ha hecho siguiendo el patrón Model/View/Controller [43]. Este patrón permite separar el aspecto visual (vista) de los datos que refleja (modelo). La capa controlador se encarga de interpretar los eventos generados por el usuario y actuar en consecuencia, normalmente provocando cambios en el modelo (que serán reflejados por la vista). Para el caso de esta aplicación, el usuario será una instancia de la aplicación DFBCIn, por lo que la entrada será más controlable. La vista se encargará de generar documentos XML de acuerdo con el modelo y las peticiones del usuario. El modelo deberá ofrecer una interfaz para acceder y modificar la información persistente. En este proyecto se ha utilizado la base de datos Mnesia [40] perteneciente a la plataforma Erlang/OTP. El hecho de utilizar una base de datos 76 Capı́tulo 4. Diseño interna a la plataforma de programación utilizada simplifica la implementación de la capa modelo, ya que las funciones de la interfaz del modelo se podrán implementar directamente sin necesidad de incluir una subcapa para el acceso a una base de datos externa. Aún ası́, el modelo debe ser diseñado de forma que oculte la utilización de Mnesia, de forma que se pueda sustituir por otra base datos utilizando una subcapa de adaptación y un controlador para la nueva base de datos. Vista Controlador Modelo DFBCIn Modelo VXMLS Mnesia Datos persistentes Figura 4.22: Patrón Model/View/Controller en el diseño de VXMLS La aplicación VXMLS desarrollada tiene una funcionalidad muy básica, pero este diseño es igualmente válido para una aplicación más compleja. 4.6.2. Objetos del dominio La información que necesitará VXMLS para su funcionamiento se estructura de la forma descrita en la figura 4.23. Para cada Set Top Box registrado se permite un cierto número de usuarios. Cada usuario tiene un lenguaje asociado y un conjunto de pelı́culas vistas. Las pelı́culas están asociadas a un único género y tienen cierta información que está almacenada en distintos idiomas. Los géneros tienen un nombre que también se almacena para los distintos idiomas. Para manejar las sesiones creadas para los diferentes Set Top Boxes, se utilizará un sistema de cookies, cada sesión se identificará con una única cookie que, a su vez, estará relacionada con un único Set Top Box. VXMLS también deberá manejar objetos de la clase Menu (ver figura 4.3). No será necesario implementar los métodos de la clase Menu ya que los objetos creados serán enviados a DFBCIn, que será el encargado de ejecutar los métodos necesarios. VXMLS simplemente se encargará de dar los valores correctos a los atributos de los objetos solicitados por DFBCIn, transformarlos en documentos Diseño de VXMLS 77 Set Top Box 1 n Usuario Lenguaje n n Cookie 1 Genero n Info Genero 1 n n 1 Pelicula n n Info Pelicula Figura 4.23: Objetos del dominio XML y enviarlos utilizando el protocolo HTTP. Para manejar la información persistente se utilizará una aplicación del patrón Value Object [44]. Este patrón permite encapsular varios datos relacionados en un único objeto, facilitando el intercambio de información entre las diferentes capas de la aplicación. Los objetos pertenecientes a la clase Menu explicada en la figura 4.3 serán definidos siguiendo el diseño mostrado en la figura 4.24. Con este diseño, VXMLS mantendrá encapsulada la información necesaria para enviar la información sobre los menús a DFBCIn. Timer Menu − majorVersion: Int − minorVersion: Into − header: String + getMinorVersion(): Int + getMajorVersion(): Int + getHeader(): String + getTimer(): Timer + getInitActions(): Array + getOptions(): Array + getTimer(): Timer + getTimer(): Timer HideMenu ReloadMenu HideText 0..1 − time: Int + getTime(): Int + getActions(): Array initActions Option 0..* − text: String 0..* 0..* 0..* 0..* <<interface>> Action + getText(): String + getActions(): Array 0..* endActions ChangeOption NextMenu PreviousMenu SeekVideo PlayVideo − value: Int − mode: String − name: String − argument: Array − depth: Int − reinit: Bool − time: Int − mode: String − file: String + getValue(): Int + getMode(): String + getName(): String + getString(): Array + getDepth(): Int + getReinit(): Bool + getTime(): Int + getMode(): String UnloadVideo ShowMenu SetProperty ShowText − name: String − value: String − lines: Array StopVideo + getFile(): String + getEndActions(): Array ResumeVideo + getLines(): Array + getName(): String + getValue(): String BindKey − key: String − event: String + getKey(): String + getEvent(): String + getActions(): Array Figura 4.24: Objetos valor utilizados para definir objetos Menu Los objetos valor necesarios para almacenar la información sobre los usuarios, 78 Capı́tulo 4. Diseño pelı́culas y lenguajes se puede ver en la figura 4.25. SetTopBox − id: String − password: String − currentUserId: String − cookieId: String + getPassword(): String + getId(): String + getCookie(): Cookie + setCookie(cookie: Cookie) + getActiveUserId(): String + setActiveUser(userId: String) Cookie − id: String − stpbxId: String + getId(): String + getStpbx(): Set Top Box 1..* User Language − name: String − languageId: String − id: String − name: String + getName(): String + getLanguageId(): String + setLanguage(languageId: String) + getId(): String + getName(): String Shown Movies Genre 0..* − id: String − name: String − languageId: String Movie − id: String − file: String − year: Int − genreId: String + getId(): String + getName(): String + getId(): String + getFile(): String + getInfo(lang: String): MovieInfo + getGenreId: String + getYear(): Int 1..* MovieInfo − title: String − synopsys: String + getTitle(): String + getSynopsys(): String TranslatedMovie + getMovie(): Movie + getMovieInfo(): MovieInfo Figura 4.25: Objetos valor utilizados por VXMLS La clase TranslatedMovie no representa a ningún objeto persistente, pero es útil para almacenar toda la información relativa a una pelı́cula en un determinado idioma. La clase Genre representa la información referente a un determinado género traducida a un idioma concreto. Esta clase representa a los objetos del dominio Genero y Info Genero definidos en la figura 4.23. Los objetos Genero no necesitan ser definidos ya que el único atributo que tienen es su propio identificador. 4.6.3. Modelo entidad-relación Para mantener los objetos persistentes en la base de datos se ha diseñado el modelo entidad-relación de la figura 4.26. Las tablas necesarias para implementar este modelo serán ocultadas al resto de la aplicación utilizando los objetos valor explicados en la sección anterior. 4.6.4. Diseño de la capa modelo La capa modelo presentará una fachada (patrón Session Facade [44]) al resto del sistema. Esta fachada proporciona una interfaz simple a los clientes, ocultando la interacciones complejas ente los objetos del dominio. De esta forma se evita Diseño de VXMLS 79 stpbx id cookie id Cookie 1:1 cookie id user name 0:1 user name 1:1 Set Top Box 1:n 1:1 password User 1:1 stpbx id stpbx id 1:1 0:n language id Shown Movie Language 0:n title movie id 0:n 0:n lang name movie id 1:1 user name genre id 0:n Movie Info 1:1 1:n Movie file 1:1 movie id language id synopsys new year language id 1:1 0:n Genre Info genre name 1:1 genre id 1:n Genre genre id Figura 4.26: Modelo entidad-relación exponer la estructura interna de la capa modelo al resto del sistema. El hecho de utilizar Mnesia [40] como base de datos elimina la necesidad de diseñar una capa de DAOs [44] para desacoplar los métodos de la fachada de la base de datos utilizada. Por lo tanto, el modelo de VXMLS se compondrá únicamente de una clase fachada que utilizará el API de Mnesia para acceder a los datos persistentes. Los métodos públicos que deberá ofrecer la fachada son: Obtener la contraseña de un determinado Set Top Box. Crear una nueva cookie y enlazarla a un Set Top Box. Eliminar una cookie (y desenlazarla del Set Top Box correspondiente). Obtener el identificador del Set Top Box enlazado a una cookie. Buscar todos los usuarios de un Set Top Box. Devolverá una lista de objetos User. Establecer el valor de una propiedad de personalización. 80 Capı́tulo 4. Diseño Obtener el lenguaje seleccionado por un determinado usuario. No devuelve el objeto Language en sı́, sino su identificador (que es el identificador ISO del lenguaje). Buscar todos los lenguajes soportados por el sistema. Devuelve una lista de objetos Language. Buscar todos los géneros. Devuelve una lista de objetos Genre. Buscar todas las pelı́culas de un cierto género que ya han sido vistas por un determinado usuario. Devuelve una lista de objetos TranslatedMovie. Buscar las pelı́culas de un género que no han sido vistas por un usuario dado. Podrá devolver una lista con las pelı́culas nuevas (atributo new de la clase Movie) o con las viejas según se desee. Como para la operación anterior, la lista devuelta contendrá objetos TranslatedMovie Obtener la información sobre una pelı́cula traducida a un cierto idioma. Devuelve un objeto TranslatedMovie. Marcar una pelı́cula como vista. Una alternativa a este diseño serı́a utilizar una fachada con estado (Stateful Session Facade), pero la implementación de este patrón en Erlang es más complicado que el de una fachada sin estado (Stateless Session Facade). No se ha desarrollado ninguna aplicación de administración para manipular los contenidos de la base de datos. En caso de hacerse, dicha aplicación deberı́a acceder a la misma base de datos que VXMLS pero a través de otra fachada que ofreciera operaciones de creación y eliminación de tablas, ası́ como operaciones para añadir información a las tablas existentes. Para hacer las pruebas del sistema, estas operaciones se han codificado en capa modelo de VXMLS, pero no estarán presentes en el API para el resto de la aplicación. La única operación disponible para realizar labores de administración será initDB, que creará las tablas necesarias e insertará los datos especificados en un fichero de configuración. Esta operación no podrá ejecutarse con el sistema en funcionamiento. 4.6.5. Diseño de la vista y el controlador La capa vista se compone de una única clase encargada de componer los documentos XML a enviar como respuesta ante las peticiones de los clientes DFBCIn. Diseño de VXMLS 81 El controlador deberá atender las peticiones del usuario, actuar sobre el modelo para obtener la información necesaria y realizar las modificaciones oportunas y, finalmente, utilizar las operaciones proporcionadas por la vista para enviar la respuesta al usuario. En la figura 4.27 se pueden ver las relaciones entre las distintas clases que conforman la vista y el controlador (todas las clases pertenecen al controlador excepto XMLUtil ). VXMLS Facade VXMLS Model + login(env: Array, args: Array): String + logout(env: Array, args: Array): String + getMenu(env: Array, args: Array): String + setProperty(env: Array, args: Array): String Logger PropertiesManager Menu Dispatcher + login(env: Array, args: Array): String + logout(env: Array, args: Array): String + setProperty(env: Array, args: Array): String + getMenu(env: Array, args: Array): String <<create>> <<interface>> MenuDocument Lib + validateUser(args: Array): Cookie + validateCookie(env: Array): String + httpDocument(header: Array, content:String): String + httpDocument(content: String): String + XMLDocument(clientId: String, user: User, languageId: String, arguments: Array): String XMLUtil + menu2xml(menu: Menu): String + errorReport(message: String): String + okMessage(): String ... MainMenu + XMLDocument(clientId: String, user: User, languageId: String, arguments: Array): String Map 1..* + getValue(key: String): String Language Server + getMessage(langId: String, messageId: String): String MenuComposer + compose(elements: Array): Menu + option(text: String, actions: Array): Option + timer(time: Int, data: Object): Timer + action(type: String, data: Object): Action elements contiene los distintos subobjetos que forman el menu tales como los objetos Option, un objeto Timer y los objetos Action con las acciones de inicializacion Figura 4.27: Clases de la vista y el controlador de VXMLS La clase VXMLSFacade es una clase utilidad que resulta de la aplicación del patrón Facade [38]. Será la interfaz de entrada al controlador que utilizará Inets [39] para solicitar que se realicen las operaciones correspondientes a una determinada URL (ver la sección 4.4). El resto de clases realizarán las siguientes funciones: Logger : Clase utilidad que se encargará de atender las URLs de petición de login/logout. Los argumentos que recibe son env con los campos de la cabecera HTTP de la petición hecha por el cliente y args con los argumentos pasados en la URL. En el caso de petición de login, deberá obtener los argumentos client y passwd de la URL y utilizar la clase Lib para comprobar que son correctos, en caso de que ası́ sea, utilizará el método httpDocument para enviar un mensaje confirmando la operación y establecer la cookie devuelta por validateUser. Para realizar la operación de 82 Capı́tulo 4. Diseño logout, utilizará la clase Lib para comprobar la validez de la sesión, el modelo para eliminar la cookie almacenada en la base de datos para identificar la sesión que se estaba manteniendo para el usuario y, finalmente, de nuevo la clase Lib para crear el mensaje HTTP de respuesta con un campo que anula la cookie almacenada por el cliente. Para obtener el contenido de los mensajes a devolver utilizará la clase XMLUtil. PropertiesManager : Al igual que la clase anterior, atenderá las peticiones a la URL utilizada para cambiar los valores de personalización. Extraerá los argumentos name y value y utilizará PropertiesManager para realizar los cambios necesarios. Para identificar el usuario utilizará Lib para validar la cookie que deberá encontrarse entre los campos de la cabecera HTTP descritos por env. MenuDispatcher : Como las clases anteriores, atenderá las peticiones a la URL utilizada para solicitar un menú, comprobando la sesión del usuario utilizando la cookie. Obtendrá el menú a generar del argumento menu de la URL, después instanciará un objeto perteneciente a la subclase de MenuDocument correspondiente e invocará el método xmlDocument para conseguir la descripción XML del menú solicitado. Esta descripción será transformada en un mensaje HTTP utilizando Lib. XMLDocument: Esta interfaz define los objetos que serán los encargados de crear los objetos Menu correspondientes a cada uno de los menús que pueden ser solicitados por DFBCIn. En la figura 4.27 sólo se muestra MainMenu, que será la clase que describe el objeto que genera el menú principal, pero el resto de implementaciones se relacionan de forma idéntica con el resto de clases del sistema. Básicamente, lo que harán estos objetos cuando se invoque el método xmlDocument será: 1. Solicitar a LanguageServer los mensajes necesarios, traducidos al lenguaje especificado por el argumento languageId. 2. Obtener la información necesaria utilizando el API expuesto por la capa modelo. 3. Crear un nuevo objeto Menu utilizando la clase MenuComposer. 4. Transformar el objeto Menu creado en un documento XML utilizando la clase XMLUtil. Lib: Clase utilidad que realizará ciertas operaciones misceláneas para la comprobación y creación de sesiones (validateUser y validateCookie) y para la creación de documentos HTTP (las dos variantes de httpDocument). Diseño de VXMLS 83 También tendrá operaciones para realizar otras operaciones poco importantes, como pueden ser las transformaciones entre distintos tipos utilizados por la aplicación que, han sido omitidas para simplificar el diagrama. XMLUtil : Clase utilidad para generar documentos XML. Como se vio en apartados anteriores, algunas implementaciones de la interfaz Action son una aplicación del patrón Composite [38]. Dadas las caracterı́sticas de los documentos XML es fácil transformar un compuesto en un documento utilizando una aproximación al patrón Visitor [38]. Por ejemplo, el pseudocódigo para transformar una acción playVideoAction en una etiqueta XML action con el contenido correspondiente serı́a: render(action: PlayVideoAction) { print("<action>") print(" <playVideo file=", action.file>) foreach(aux = action.endActions) { render(aux) } print(" </playVideo>") print("</action>") } XMLUtil “visita” cada componente del objeto Menu y lo transforma en una parte del documento XML que lo describe. No es una aplicación pura del patrón Visitor ya que la estructura interna de los objetos de la clase Menu debe ser conocida por el el visitante, pero es más fácil trasladar este diseño a una implementación en Erlang que el diseño propuesto en [38], en el que la clase visitada debe proporcionar un método que acepte la visita. La generación de los mensajes de error o de confirmación no presenta ningún problema ya que son documentos XML muy sencillos y con definiciones no recursivas. MenuComposer : Clase utilidad que proporciona las operaciones necesarias para componer objetos Menu. Es una aplicación del patrón Builder [38], las implementaciones de MenuDocument actúan como directores y MenuComposer como constructor. LanguageServer : Es una aplicación del patrón Singleton [38]. Controlará un conjunto de mapas en los que estarán almacenadas las traducciones de los diferentes mensajes visuales a los idiomas soportados por la aplicación. Las diferentes traducciones se cargarán de ficheros planos en los que se asociarán las claves identificadoras para cada mensaje con la cadena a mostrar para ese identificador. Capı́tulo 5 Implementación de DFBCIn Índice General 5.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . 86 5.2. Estándares de codificación . . . . . . . . . . . . . . . . 87 5.2.1. Nombres . . . . . . . . . . . . . . . . . . . . . . . . . . 87 5.2.2. Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . 89 5.3. Control de errores . . . . . . . . . . . . . . . . . . . . . 90 5.4. Control de concurrencia . . . . . . . . . . . . . . . . . 92 5.5. Tipos de datos de uso general . . . . . . . . . . . . . . 94 5.5.1. Pilas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.5.2. Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 5.6. La clase Menu . . . . . . . . . . . . . . . . . . . . . . . 96 5.7. La clase Option . . . . . . . . . . . . . . . . . . . . . . . 98 5.8. La clase Key . . . . . . . . . . . . . . . . . . . . . . . . 99 5.9. La interfaz Action . . . . . . . . . . . . . . . . . . . . . 100 5.9.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 100 5.9.2. Tipos de datos . . . . . . . . . . . . . . . . . . . . . . 100 5.9.3. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . 104 5.9.4. ¿Cómo instanciar una acción? . . . . . . . . . . . . . . 107 5.9.5. ¿Cómo añadir una nueva acción? . . . . . . . . . . . . 108 5.10. Implementación del motor de la aplicación . . . . . . 108 5.10.1. Main . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 5.10.2. Interface . . . . . . . . . . . . . . . . . . . . . . . . . 110 85 86 Capı́tulo 5. Implementación de DFBCIn 5.10.3. InterfaceInput . . . . . . . . . . . . . . . . . . . . . . 114 5.10.4. Mp3Player . . . . . . . . . . . . . . . . . . . . . . . . 116 5.11. Implementación del módulo VXMLS . . . . . . . . . . 118 5.11.1. Módulo HTTP . . . . . . . . . . . . . . . . . . . . . . 118 5.11.2. Parser VXMLS . . . . . . . . . . . . . . . . . . . . . . 120 5.11.3. Parser para los documentos menu . . . . . . . . . . . . 121 5.11.4. Implementación de la fachada VXMLS . . . . . . . . . 133 5.12. Implementación del Módulo gráfico . . . . . . . . . . 136 5.12.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . 136 5.12.2. Estructuración del código . . . . . . . . . . . . . . . . 137 5.12.3. La fachada graphics.c . . . . . . . . . . . . . . . . . 137 5.12.4. La inicialización de DirectFB . . . . . . . . . . . . . 138 5.12.5. Control de eventos de entrada . . . . . . . . . . . . . . 138 5.12.6. Impresión de menús y texto . . . . . . . . . . . . . . . 140 5.12.7. Reproducción de vı́deo . . . . . . . . . . . . . . . . . . 141 5.12.8. Módulo de pruebas . . . . . . . . . . . . . . . . . . . . 144 5.13. Sistema de propiedades . . . . . . . . . . . . . . . . . . 145 5.14. Etapas de codificación . . . . . . . . . . . . . . . . . . . 145 5.14.1. Desarrollo del núcleo de la aplicación . . . . . . . . . . 145 5.14.2. Reproducción básica de vı́deo . . . . . . . . . . . . . . 146 5.14.3. Pruebas en el Set Top Box . . . . . . . . . . . . . . . 147 5.14.4. Varias mejoras . . . . . . . . . . . . . . . . . . . . . . 147 5.14.5. Comunicación HTTP . . . . . . . . . . . . . . . . . . 148 5.14.6. Integración con VXMLS . . . . . . . . . . . . . . . . . 148 5.14.7. Integración con VoDKA . . . . . . . . . . . . . . . . . 149 5.14.8. Reproductor Mp3 y acciones de texto . . . . . . . . . 149 5.1. Introducción La implementación en C de un diseño orientado a objetos supone ciertas dificultades. C es un lenguaje imperativo de bajo nivel, la gestión de memoria, de errores y de concurrencia se debe hacer de forma explı́cita. Además, el concepto Estándares de codificación 87 de objeto debe ser simulado de alguna forma, combinando estructuras de datos con funciones. Como norma general, la implementación de un objeto consistirá en un módulo en el que se definirán las estructuras de datos necesarias para almacenar la información relevante para cada objeto instanciado y las funciones que operarán sobre dichas estructuras de datos. En la sección 5.2 se explica con más detalle la estructura de estos módulos. Como se ha dicho en la fase de análisis, el ciclo de vida de DFBCIn seguirá el paradigma incremental. En la primera iteración se desarrollará un sistema que aporte la funcionalidad más básica. Una vez hecho esto, se irán añadiendo posibilidades hasta obtener el sistema final. A lo largo de este capı́tulo se explica la implementación del sistema final, en la sección 5.14 se explican los pasos que se dieron hasta alcanzar esta implementación. 5.2. Estándares de codificación 5.2.1. Nombres Tanto los nombres como el contenido de los comentarios están escritos en inglés para facilitar la lectura del código a cualquier persona. Para conseguir un espacio de nombres suficientemente homogéneo y facilitar la compresión del código, se seguirán una serie de reglas a la hora de elegir los nombres de las funciones, tipos de datos y variables utilizadas. Los nombres compuestos estarán formados por las palabras concatenadas, marcando el principio de la cada palabra con una letra mayúscula: nombreCompuesto. Los nombres de los Tipos de datos comenzarán con mayúsculas: NombreDeTipoDeDato. Las variables y funciones comenzarán con minúsculas: nombreDeVariable y nombreDeFunción(). Las funciones de creación (reserva de memoria) para un determinado tipo de dato TipoDeDato se llamarán newTipoDeDato. 88 Capı́tulo 5. Implementación de DFBCIn Las funciones de liberación de memoria para el tipo de dato TipoDeDato se llamarán freeTipoDeDato. En caso que se lleve un control de referencias para una clase NombreDeClase cuyos objetos puedan ser compartidos y liberados por diferentes módulos de la aplicación, la memoria necesaria para el tipo de datos que representa los argumentos del objeto (ver sección 5.2.2) se manejará con las funciones newNombreDeClase, addNombreDeClaseReference y releaseNombreDeClase. addNombreDeClaseReference se utilizará para señalar que hay una nueva referencia al objeto y releaseNombreDeClase para indicar que una referencia ya no va a seguir siendo utilizada. Cuando la última referencia deje de ser utilizada se liberará la memoria reservada para los datos del objeto en cuestión. En algunos casos es necesario exportar funciones que van a ser utilizadas por macros que oculten ciertos aspectos, o que van a ser utilizadas solamente como callbacks, existiendo otra función o macro que será utilizada en la mayorı́a de los casos para cumplimentar el objetivo al que está orientado dicha función. En estos casos la función se llamará nombreDeFunción, y la macro o función de uso general que utilice dicha función será llamada nombreDeFunción. Los tipos de datos suelen ser punteros a estructuras. El nombre de la estructura es irrelevante para la aplicación, excepto en el momento en el que se necesita reservar la memoria para dicha estructura. El nombre de la estructura de datos a la que apunta un tipo PunteroAEstructura será PunteroAEstructura. las funciones exportadas por un módulo que implemente una clase utilidad (ver sección 5.2.2) Módulo se llamarán móduloFunción1, móduloFunción2, ... En caso de que un módulo Módulo necesite ser inicializado antes de ser utilizado, deberá definir las funciones móduloInit y móduloDeinit para las labores de inicialización y liberación del módulo. Las funciones que operan sobre un tipo de datos TipoDeDatos tendrán el nombre del tipo de datos como parte de su nombre compuesto, por ejemplo operaciónSobreTipoDeDatos. Los nombres de las macros se escribirán en mayúsculas, con barras bajas como separadoras entre las palabras: NOMBRE DE MACRO. Estándares de codificación 89 los nombres de los ficheros fuente estarán en minúsculas y utilizarán un guión como separador de palabras en caso de que sea necesario: nombre-defichero.c, nombre-de-fichero.h. 5.2.2. Objetos La implementación de una determinada clase del diseño se hará dependiendo de si la clase es una clase utilidad, una clase que define un objeto Singleton [38] o una clase que define objetos que van a ser compartidos por diferentes módulos del programa. Una clase utilidad, por ejemplo ClaseUtilidad, será implementada creando un módulo ClaseUtilidad formado por el fichero clase-utilidad.c y la cabecera clase-utilidad.h. Las funciones exportadas en clase-utilidad.h deberán tener nombres que comiencen por claseUtilidad, para facilitar la lectura del código (claseUtilidadNombre, serı́a equivalente a una llamada al método nombre de la clase ClaseUtilidad en un lenguaje orientado a objetos). Los objetos Singleton serán implementados de forma similar a una clase utilidad, pero normalmente será necesario que el código que se encargue de simular su comportamiento esté en un nuevo hilo de ejecución. En la sección 5.4 se explica como se hará el control de los módulos que generan nuevos hilos de ejecución. La implementación de los objetos que vayan a ser compartidos entre diferentes módulos de la aplicación dependerá de si es necesario llevar las referencias del objeto para poder liberar la memoria o no. En ambos casos, el objeto a compartir se codificará como un tipo de dato que será un puntero a una estructura en la que se almacenará la información necesaria sobre el objeto en cuestión (puede ser necesario que dicha estructura también contenga algún semáforo o similar para controlar el acceso concurrente a sus contenidos) y una serie de funciones que simulen los métodos definidos por la clase a la que pertenece el objeto que estamos tratando. A estas funciones será necesario pasarles la estructura que contiene los datos del objeto. Por ejemplo si se quiere ejecutar el método objetoCompartido.método, la lı́nea de código será métodoObjetoCompartido(objetoCompartido, ...). El constructor del objeto será la función newNombreDeClase explicada en la sección 5.2.1. En caso de que sea necesario llevar un control de las referencias existentes al 90 Capı́tulo 5. Implementación de DFBCIn objeto, para poder liberar su memoria, será necesario añadir un campo a la estructura de datos que contiene los atributos del objeto para contar dichas referencias. Para ello se definirá la función addNombreDeClaseReference que deberá ser llamada por cada nuevo módulo que vaya a mantener una referencia al objeto. La función releaseNombreDeClase liberará una referencia. La memoria reservada para mantener la información necesaria sobre el objeto será liberada cuando el contador de referencias llegue a cero. Las llamadas a releaseNombreDeClase deben ser sincronizadas para evitar problemas de concurrencia en caso de que varios hilos intenten ejecutar esta función simultáneamente, lo que podrı́a dejar la memoria sin liberar. No es necesario sincronizar addNombreDeClaseReference porque se supone que se está accediendo de forma segura al objeto que se quiere referenciar en el momento en el que se añade dicha referencia, es decir, no hay peligro de que se haga una llamada releaseNombreDeClase que vaya a liberar la memoria ocupada por el objeto durante la ejecución de addNombreDeClaseReference, puesto que la referencia debe ser añadida en un hilo que ya mantenga su propia referencia. 5.3. Control de errores El estado de error de la aplicación se controla con la función setAppError, con la que se puede establecer el error que se ha detectado y el fichero y la lı́nea en la que se ha producido. Como se explicó en la sección 5.2.1, un nombre que empieza por el caracter no debe ser utilizado directamente, esta función se exporta para que pueda ser utilizada por las macros que se definen para realizar el control de errores a lo largo de la aplicación. Los errores detectados se clasificarán según un tipo que está definido por la enumeración AppError. Se define una variable global de este tipo que será manipulada por las funciones exportadas por el módulo errors (estas funciones están declaradas en el fichero errors.h). Esta variable puede tomar uno de los siguientes valores : NO ERROR: No se ha detectado ningún error. MULTIPLE ERROR: Se ha llamado más de una vez a setAppError. NO MEMORY : No hay suficiente memoria. INVALID OPERATION : Se ha intentado realizar una operación no permitida. Control de errores 91 PARSER ERROR: Error durante el proceso de interpretación de un documento de texto. IMPLEMENTATION ERROR: Se ha alcanzado una sección del código que no deberı́a ser alcanzada. Se trata de un error en la implementación. VERSION ERROR: Se ha recibido un documento con una versión mayor a la que se puede manejar. EXTERNAL ERROR: Fallo en algo ajeno a la implementación (por ejemplo, fallo en una llamada al sistema). BAD FILE FORMAT : Un fichero no cumple la estructura que se esperaba. LOST PROPERTY : No se puede encontrar el valor de una propiedad obligatoria. DOWNLOAD ERROR: Fallo en la descarga de un fichero. VXMLS ERROR: Fallo en el servidor VXMLS. MPG123 ERROR: Fallo en el reproductor de mp3. Las funciones exportadas para permitir el control de errores durante la ejecución de la aplicación son: void setErrorMessage(const char *message): Se utiliza para establecer un mensaje explicativo que será escrito por printAppError junto con la descripción genérica para el estado de error en el que se haya la aplicación. AppError getAppError(void): Devuelve el estado de error actual. void unsetAppError(void): Establece NO ERROR como estado de error. void printAppError(void): Escribe un mensaje explicativo en el descriptor de error estándar. short existsError(void): Devuelve 0 en caso de que el estado de error sea NO ERROR y un número distinto de 0 en otro caso. Para la aplicación desarrollada, el comportamiento que se seguirá en caso de se produzca una situación excepcional será establecer el error correspondiente y finalizar la ejecución lo antes posible, haciendo una llamada a printAppError antes de terminar para notificar las causas del fallo. Para ello, en errors.h se define la macro DFBCIN FATAL, que provoca la finalización inminente de la aplicación en un estado de error dado. Es posible llamar a setErrorMessage antes para añadir información al mensaje mostrado en la finalización de la aplicación. 92 Capı́tulo 5. Implementación de DFBCIn #define DFBCIN_FATAL(error) { _setAppError(error, __LINE__, __FILE__); printAppError(); unsetAppError(); exit(EXIT_FAILURE); } \ \ \ \ \ \ También hay algunas funciones que pueden detectar errores durante su ejecución, pero que no pueden decidir si dicho error es lo suficientemente grave como para finalizar la ejecución o puede ser solventado de alguna otra forma. Estas funciones deberán devolver una variable de tipo AppError. En caso de que una llamada a una de estas funciones deba devolver un valor siempre correcto, la función que realiza la llamada puede utilizar la macro DFBCIN CHECK que terminará la ejecución en caso de que la función llamada devuelva un valor indicando un error. #define DFBCIN_CHECK(x...) { int err; err = (x); if (err != NO_ERROR) { _setAppError(err, __LINE__, __FILE__); printAppError(); unsetAppError(); exit(EXIT_FAILURE); } \ \ \ \ \ \ \ \ \ \ \ } 5.4. Control de concurrencia En la sección 5.2.2, se comentó que para codificar los objetos de algunas de las clases del diseño serı́a necesario añadir nuevos hilos de ejecución. La programación multi-hilo en un lenguaje como C debe hacerse cuidadosamente, ya que posibilita la aparición de problemas muy difı́ciles de depurar. La implementación de aquellos objetos que tengan un ciclo de ejecución propio se hará en dos ficheros, el fichero fuente y la cabecera con las funciones exportadas. Para explicar cómo se estructurará el código que implementa este tipo de objetos se utilizará como ejemplo una clase ficticia Singleton, que se codificará en Control de concurrencia 93 los ficheros singleton.c y singleton.h. En singleton.h se exportarán las funciones que serán accesibles para el resto de la aplicación: singletonMethod, singletonMethod2, etc. Entre estas funciones serán necesarias las funciones singletonInit y singletonDeinit para permitir la creación y la destrucción del objeto. En el fichero singleton.c se definirán las funciones y estructuras de datos locales. En este caso, será necesaria una estructura de datos en la que mantener los datos referentes al estado del objeto y los semáforos que se utilizarán para el control de concurrencia. Será necesario definir al menos: Un tipo de datos SingletonState que describa el estado interno del objeto que se está implementando. Una variable global (pero no accesible desde fuera de este módulo) singletonState de tipo SingletonState que contenga el estado interno del objeto. Un semáforo singletonMutex para controlar el acceso concurrente a singletonState y que posibilite el control de la ejecución del nuevo hilo utilizando una condición singletonCond. Una función singetonThread que contenga el código necesario para que el objeto creado se comporte como se espera, de acuerdo con las especificaciones hechas en el diseño. La función singletonInit deberá crear la variable singletonState y lanzar el nuevo hilo de ejecución, que estará implementado por la función singletonThread. La función singletonThread consistirá en un bucle que se ejecutará con el semáforo singletonMutex cerrado. El cuerpo del bucle realizará las acciones necesarias de acuerdo con el estado del objeto. En algún momento, la ejecución deberá ser detenida en la condición singletonCond. En este momento se liberará el semáforo para permitir a las funciones exportadas modificar el estado del objeto para variar su comportamiento. Las funciones exportadas en singleton.h no realizarán el trabajo solicitado por si mismas, sino que accederán a singletonState, siempre después de haber cerrado el semáforo singletonMutex, modificarán el estado para que el hilo de ejecución del objeto sepa las acciones que tiene que realizar y despertarán la ejecución del hilo singletonThread que deberı́a estar parado en la condición, para que compruebe de nuevo el estado y actúe en consecuencia. De esta forma, el 94 Capı́tulo 5. Implementación de DFBCIn ciclo de ejecución del objeto es concurrente con la ejecución del hilo principal del programa, pero los accesos a sus datos privados se mantienen sincronizados, ya que las funciones exportadas para la aplicación sólo pueden modificar del estado del objeto en un punto controlado. En caso de que el ciclo de ejecución del objeto no pueda ser detenido, se deberá codificar un punto en el que se libere el semáforo y se de paso a un nuevo proceso (utilizando las funciones del planificador) para permitir que las funciones exportadas puedan cambiar el estado interno del objeto: STATE_UNLOCK; sched_yield(); STATE_LOCK; 5.5. Tipos de datos de uso general 5.5.1. Pilas Las pilas serán utilizadas para almacenar la información necesaria durante el proceso de interpretación de los ficheros XML que describen los menús de la interfaz y para almacenar los objetos Menu que se van cargando a medida que el usuario profundiza en la estructura de menús definida. Las pilas no necesitarán ser compartidas entre distintos módulos del sistema, por lo que no será necesario implementar el mecanismo de referencias. Para implementar la clase Stack (que representará a una pila de contenido genérico) se definirá un tipo de dato abstracto Stack y una serie de funciones que operen sobre él. El tipo Stack está completamente encapsulado, el resto de módulos del sistema no tendrán acceso a su estructura interna. El API exportado para la manipulación de pilas se compone de las siguientes operaciones: Stack newStack(void): Crea una nueva pila. short isEmptyStack(Stack stack): Devuelve un número distinto de cero en caso de que la pila esté vacı́a. void pushStack(Stack stack, void *data): Añade un nuevo dato a la cima de la pila. Tipos de datos de uso general 95 void freeStack(Stack stack): Libera la memoria ocupada por la pila. La pila debe estar vacı́a para que se pueda realizar esta operación, en otro caso la aplicación terminará debido a un error INVALID OPERATION. void *popStack(Stack stack): Devuelve el dato de la cima de la pila y lo borra de la misma. void *seeStackTop(Stack stack): Devuelve el dato de la cima de la pila, pero no lo borra de la misma. 5.5.2. Arrays Los arrays se utilizarán para almacenar una cantidad indeterminada de datos de forma que puedan ser compartidos entre distintos módulos del sistema. En la aplicación, la utilización de los arrays consistirá en crearlos, añadir un cierto número de elementos y, una vez añadidos todos los datos necesarios, transmitirlos a los módulos que los necesiten para que realicen operaciones sobre los datos almacenados. No será necesario borrar datos ya existentes o añadir nuevos datos a un array que ya ha sido compartido. La implementación de los arrays utilizará el sistema de referencias que se describió en la sección 5.2.2. Para posibilitar la liberación de toda la memoria ocupada por un array (incluida la memoria que ocupan sus datos), en la función de creación del array se deberá especificar una función que sea capaz de liberar la memoria del tipo de datos que se vaya a almacenar en el array creado. Con estos dos mecanismos, un array con información puede ser compartido por varios módulos y puede ser fácilmente liberado cuando dichos módulos dejen de utilizarlo. Al igual que las pilas, los arrays se implementan utilizando un tipo abstracto de datos completamente encapsulado y un cierto número de funciones para operar sobre él. El API para manipular arrays es: Array newArray(FreeDataFunc1 freeFunc): Crea un nuevo array en el que se almacenará un cierto tipo de datos. freeFunc será una función capaz de liberar la memoria ocupada por una variable del tipo de datos almacenado. 1 el tipo FreeDataFunc se define como void (*FreeDataFunc) (void *data) 96 Capı́tulo 5. Implementación de DFBCIn void releaseArray(Array array): Se utiliza para señalar que se va a dejar de utilizar el array, eliminando una de las referencias al mismo. Cuando se libera un array de forma que ya no queden referencias al mismo se libera también la memoria ocupada por array, incluyendo la memoria ocupada por todos los elementos almacenados. void flushArray(Array array): Vacı́a el array liberando la memoria ocupada por todos los datos almacenados en el mismo. unsigned numArrayElements(Array array): Devuelve el número de elementos almacenados en el array. void addArray(Array array, void *data): Añade un nuevo elemento al final del array. void *getArrayData(Array array, unsigned nth): Devuelve el elemento almacenado en la posición nth del array. El primer elemento se almacena en la posición 0. void addArrayReference(Array array): Indica que hay un nuevo módulo utilizando el array añadiendo una nueva referencia al mismo. La implementación que se ha hecho de los arrays sólo comprueba la sincronización de las llamadas a releaseArray, para evitar que se produzcan dos llamadas concurrentes con el riesgo de que quede la memoria sin liberar. El resto de funciones no están sincronizadas. Esta implementación no contempla la posibilidad de que varios módulos intenten añadir datos de forma concurrente. 5.6. La clase Menu Los objetos de la case Menu (ver sección 4.5.3) serán compartidos por varios módulos, pero la creación y destrucción de estos objetos no necesita ser controlada con el sistema de referencias. Los objetos Menu sólo podrán ser creados por el módulo VXMLS, y serán almacenados en una pila global, de forma que siempre esté accesible el menú mas recientemente utilizado. La destrucción de estos objetos se hará a medida que se desapilen. De esto se encargarán algunas de las diferentes acciones (en el sentido de instancias de Action) implementadas. Por todo ello, el hecho de que los objetos de la clase Menu vayan a ser compartidos no complica su creación y destrucción, pero sı́ será necesario añadir algún sistema para evitar escrituras concurrentes. Por lo tanto, el tipo de datos que contendrá los atributos de los objetos de la clase Menu también deberá tener La clase Menu 97 asociado un semáforo que deberá ser bloqueado antes de acceder a sus contenidos. El tipo Menu definido no será un tipo completamente encapsulado como en el caso de los arrays o de las pilas. En este caso el resto de la aplicación deberá conocer la estructura interna del tipo Menu para poder modificar su contenido. Una variable de tipo Menu será un puntero a una estructura con los siguientes campos: short majorVersion, minorVersion: Indican la versión del esquema de menú utilizado, en caso de que DFBCIn reciba un menú con una versión mayor a la soportada deberá actualizarse2 . char header[]: Tı́tulo a mostrar para el menú. Array options: Sostiene la agregación de objetos Option. int time: Tiempo que debe pasar sin que se reciba un evento de entrada para que se disparen las acciones de temporización, si es -1 se supone que la espera es infinita (no hay acciones de temporización). Array timeActions: Conjunto de acciones a ejecutar en caso de que termine la temporización. Array initActions: Conjunto de acciones a ejecutar cuando se cargue el menú por primera vez. short currentPosition: Opción actualmente seleccionada. Key keys[]: Conjunto de objetos Key utilizados para almacenar la información sobre las acciones a realizar en caso de que se detecte un determinado evento de entrada (ver sección 5.8). char name[]: Nombre con el que fue solicitado el menú a VXMLS. Array arguments: Lista con los argumentos que se utilizaron para solicitar este menú a VXMLS. pthread mutex t mutex: Semáforo utilizado para la sincronización de los accesos concurrentes a los contenidos de esta estructura. Las funciones exportadas para manejar este tipo de datos son sólo las de creación, destrucción, bloqueo y desbloqueo, ya que el resto de operaciones se deberán hacer accediendo directamente a los contenidos de la estructura. 2 Esta caracterı́stica aún no está implementada, es una lı́nea de trabajo futura 98 Capı́tulo 5. Implementación de DFBCIn Menu newMenu(void): Devuelve una variable de tipo Menu ya inicializada (con memoria reservada tanto para la estructura de datos como para los arrays que contiene). void lockMenu(Menu menu): Bloquea menu para evitar el acceso concurrente. void unlockMenu(Menu menu): Desbloquea menu. void freeMenu(Menu menu): Libera la memoria ocupada por menu. Nótese que si a alguno de los arrays que contiene menu le fueron añadidas nuevas referencias éste no será liberado durante este proceso. De esta forma se pueden preservar acciones a ejecutar aunque se libere el menú que las contiene. 5.7. La clase Option Los objetos de la clase Option no serán compartidos directamente, sino que serán compartidos a través del menú que los contiene. Además, el acceso concurrente a sus contenidos siempre será a través del tipo Menu (de hecho, en el diseño se especificó que los objetos de la clase Option tienen una relación de composición con la clase Menu, ver la sección 4.5.3). Se supone que los accesos a una variable de tipo Menu siempre serán sincronizados, por lo que no será necesario tomar ninguna medida adicional para controlar los accesos a este tipo de objetos. La implementación de este tipo de objetos no presenta ningún tipo de complicación. Como en casos anteriores, se definirá un tipo de datos Option, no encapsulado, que será un puntero a una estructura en la que se almacenarán los atributos del objeto. Las funciones exportadas para manejar este tipo de datos serán únicamente la función de creación y la de destrucción. El método execute de la clase Option no puede ser implementado directamente. El subsistema Interface (ver sección 4.5.4.2) se encargará de recolectar las acciones a ejecutar y de enviárselas a Main para que las ejecute. La estructura de datos implementada tiene sólo dos campos : char text[]: Texto a mostrar para identificar la opción. Array actions: Grupo de acciones a ejecutar en caso de que el usuario seleccione esta opción. Y las funciones exportadas también son solamente dos: La clase Key 99 Option newOption(void): Devuelve una variable de tipo Option con memoria reservada. void freeOption(Option option): Libera la memoria ocupada por la variable option. Al igual que lo comentado para Menu, si las acciones fueron compartidas por otro módulo, estas no serán liberadas hasta que sean liberadas por este otro módulo. 5.8. La clase Key La clase Key se utilizará para almacenar la información sobre las acciones a realizar para cada evento de entrada detectado3 . Como se puede ver en 4.5.3 la pulsación de una tecla puede estar asociada a tres tipos de evento: ACTIONS, lo que provocará la ejecución de un grupo de objetos de la clase Action; EXECUTE, que provocará la ejecución del grupo de objetos Action asociados a la opción seleccionada; o NULL, que indica que la pulsación de la tecla debe ser ignorada. Para implementar este tipo de objetos se define un tipo de datos que apunta a una estructura con dos elementos: KeyBind keyBind: Indica el tipo de acción a realizar. El tipo KeyBind es una enumeración que puede tomar los valores KBIND NULL, KBIND EXECUTE, o KBIND ACTIONS que se corresponden con los valores NULL, EXECUTE y ACTIONS que puede tomar el atributo event de la clase Key. Array actions: Representa la agregación de acciones a ejecutar. Como ya se comentó para la clase Option el método execute no se implementa directamente. Interface se encargará de pasarle el grupo de acciones correspondiente a Main para que las ejecute en caso de que la tecla pulsada lo requiera: Si la tecla está asociada con un evento EXECUTE se provocará la ejecución de Option.execute, lo que equivale a pasarle a Main el array actions del elemento de tipo Option situado en la posición indicada por el campo currentPosition del menú actual en el array options de dicho menú. Si la tecla está asociada con un evento ACTIONS se le pasará a Main el array actions. Si la tecla está asociada a un evento de tipo NULL la llamada a execute no tiene ningún efecto. 3 Un evento de entrada se produce cuando el usuario pulsa una tecla del mando a distancia 100 Capı́tulo 5. Implementación de DFBCIn El atributo key de la clase Key no necesita codificarse explı́citamente, ya que los objetos que definen todas las teclas posibles están almacenados en el atributo keys del tipo Menu, la posición en la que está almacenada ya indica qué tecla es. En el fichero input-keys.h se define la enumeración InputKey que se utilizará para indexar los estos objetos. Por ejemplo: Si el usuario pulsa la tecla SEL, el objeto que represente dicha tecla se obtendrá accediendo a (menu->keys)[INPT SEL], suponiendo que menu sea la variable que representa el menú actual. 5.9. La interfaz Action 5.9.1. Introducción Los objetos pertenecientes a la case Action van a ser compartidos por diferentes módulos del sistema. Todas las acciones a ejecutar estarán en un principio en alguno de los arrays de algún menú que se haya descargado de VXMLS. Después de esto, se pueden mantener diferentes referencias al mismo array de acciones, por ejemplo, cuando una tecla pasa a estar vinculada con un array de acciones tras la ejecución de una acción BindKey el objeto Key que representa a la tecla vinculada pasa a referenciar el array de acciones que se encuentra entre los datos del objeto BindKey. En caso de que se detecte que el usuario ha pulsado esa tecla, el mismo array de acciones también será referenciado por Main para proceder a su ejecución. Sin embargo, no es necesario controlar las referencias a las acciones en sı́, ya que siempre se compartirán a través de un array, y el tipo Array ya implementa el control de concurrencia (ver sección 5.5.2), asegurando que ni él ni su contenido serán liberados hasta que todas las referencias hayan sido liberadas. 5.9.2. Tipos de datos 5.9.2.1. Implementación de la interfaz Action Para implementar esta interfaz de forma que sea fácil la creación de nuevas acciones que la respeten se ha definido un tipo Action que apunta a una estructura con dos campos: ActionType actionType: El tipo ActionType se utiliza para definir el tipo de acción que se está representando (es decir, a qué clase pertenece). La interfaz Action 101 ActionData actionData: El tipo ActionData representa un campo de datos genéricos desde el que se puede acceder a los diferentes atributos de cada tipo de acción implementado. El tipo ActionType será una enumeración en la que se podrán ir añadiendo los nombres de los nuevos tipos de acción que se vayan definiendo. Los tipos de acción que se han implementado para el sistema actual son: NEXT MENU ACTION: Acción para navegar a un nuevo menú PREVIOUS MENU ACTION: Permite volver a un menú anterior, descartando los menús que se hayan visitado después de ese. HIDE MENU ACTION: Oculta el menú actual. SHOW MENU ACTION: Muestra el menú actual. PLAY VIDEO ACTION: Inicia la reproducción de un vı́deo. STOP VIDEO ACTION: Detiene el de vı́deo que se está reproduciendo. RESUME VIDEO ACTION: Continúa con la reproducción de vı́deo anteriormente detenida. SEEK VIDEO ACTION: Salta a una determinada posición del vı́deo que se está reproduciendo. UNLOAD VIDEO ACTION: Termina la reproducción de vı́deo. CHANGE OPTION ACTION: Cambia la opción actual. BIND KEY ACTION: Cambia el evento asociado a una determinada tecla. SET PROPERTY ACTION: Cambia el valor de una propiedad de personalización. RELOAD MENU ACTION: Vuelve a solicitar el menú actual a VXMLS. SHOW TEXT ACTION: Muestra un texto por pantalla. HIDE TEXT ACTION: Oculta el texto que se estaba mostrando. LOAD MP3 ACTION: Carga un fichero de audio mp3 e inicia su reproducción. PAUSE CONTINUE MP3 ACTION: Detiene/continúa la reproducción de audio en curso. 102 Capı́tulo 5. Implementación de DFBCIn KILL MP3 PLAYER ACTION: Fuerza la eliminación del reproductor de mp3. Esto es necesario porque se necesita liberar el dispositivo /dev/dsp para que la reproducción de vı́deo pueda utilizar la tarjeta de sonido. Si se detiene la reproducción de audio con la acción PauseContinueMP3 el dispositivo /dev/dsp sigue estando bloqueado por el reproductor de mp3. El tipo ActionData será un puntero a una unión en la que se podrán definir campos para almacenar los datos necesarios para la ejecución de los diferentes tipos de acción. 5.9.2.2. Tipos de datos para las acciones concretas Para las acciones que se han implementado, hay nueve que necesitan información especı́fica que será almacenada en el campo actionData. Sabiendo qué acción se está tratando (está indicado por el campo actionType) se puede acceder a sus datos a través de la unión apuntada por ActionData. Estos campos pueden ser uno de los siguientes: NextMenuActionData nextMenuActionData: El tipo NextMenuActionData se utiliza para almacenar la información que se necesita para ejecutar una acción NextMenu. Apunta a una estructura con los campos: • char nextMenuName[]: Nombre del menú a solicitar a VXMLS. • Array arguments: Array con los argumentos con los que se va a solicitar el menú, ordenados de forma nombre 1, valor 1, nombre 2, valor 2, etc. PreviousMenuActionData previousMenuActionData: Las variables de tipo PreviousMenuActionData contienen la información necesaria para ejecutar una acción PreviousMenu. Apunta a una estructura con dos campos: • int returnDepth: Número de menús a retroceder. • short reinit: Si es 0 no se ejecutarán las acciones de inicialización del menú. PlayVideoActionData playVideoActionData: En este campo se almacena la información que se necesita para ejecutar una acción PlayVideo. El tipo PlayVideoActionData apunta a una estructura con dos campos: • char videoPath[]: Ruta completa del fichero de vı́deo. La interfaz Action 103 • Array endActions: Acciones a ejecutar cuando se alcance el final del vı́deo. ChangeOptionActionData changeOptionActionData: El tipo ChangeOptionActionData se utiliza para almacenar la información necesaria para la ejecución de las acciones de tipo ChangeOption. Apunta a una estructura con los campos: • int value: Número a sumar a la posición actual, o número de la opción a seleccionar, dependiendo de si absolute sea 0 o distinto de 0. • short absolute: Indica si value se debe interpretar como valor a sumar a la posición actual o como posición de destino. BindKeyActionData bindKeyActionData: En el tipo BindKeyActionData se almacena la información necesaria para la ejecución de las acciones BindKey. Apunta a una estructura con los campos: • InputKey inputKey: Identificador de la tecla que lanzará el evento definido por key. • Key key: Ver la sección 5.8. SeekVideoActionData seekVideoActionData: En este campo se almacenará la información necesaria para ejecutar las acciones SeekVideo. Como en los casos anteriores, SeekVideoActionData es un tipo que apunta a una estructura con dos campos: • SeekVideoType seekVideoType: SeekVideoType es una enumeración que puede tomar los valores: ◦ SEEK FORWARD: Se debe incrementar la posición actual en la reproducción del vı́deo. ◦ SEEK BACKWARD: Se debe decrementar la posición actual. ◦ SEEK ABSOLUTE: Se debe ir a la posición indicada. • double time: Posición a la que se debe ir o cantidad a aumentar o decrementar la posición actual. SetPropertyActionData setPropertyActionData: Aquı́ se almacena la información necesaria para ejecutar las acciones SetProperty. El tipo SetPropertyActionData apunta a una estructura con dos campos: 104 Capı́tulo 5. Implementación de DFBCIn • char name[]: Nombre de la propiedad. • char value[]: Nuevo valor para la propiedad. ShowTextActionData showTextActionData: En este campo se almacenará la información necesaria para ejecutar una acción ShowText. El tipo ShowTextActionData apunta a una estructura con un único campo lines que contiene un array de strings. LoadMp3ActionData loadMp3ActionData: En este campo se almacena la información que se utilizará para ejecutar las acciones de tipo LoadMp3. El tipo LoadMp3ActionData apunta a una estructura que contiene el campo path en el que se indica la ruta desde la que se debe cargar el fichero mp3 a reproducir. 5.9.3. Funciones El método execute se implementará utilizando una sentencia switch que llamará la función correspondiente de utilizando el campo actionType de la acción ejecutada. void executeAction(Action action, GlobalContext globalContext) { switch (action -> actionType) { case NEXT_MENU_ACTION: executeNextMenuAction(action -> actionData -> nextMenuActionData, globalContext); break; case PREVIOUS_MENU_ACTION: /* [...] continúa con casos similares */ El argumento globalContext lo pasa Main cuando llama a execute. Contiene información global sobre la ejecución de la aplicación. Además de las funciones necesarias para simular el método execute también son necesarias las funciones que posibiliten la creación y destrucción de este tipo de objetos. El API para el manejo de objetos de la clase Action proporciona, además de execute, las siguientes funciones: La interfaz Action 105 void freeAction(Action action): Libera la memoria ocupada por la variable action. void freeAction(void *data): Alias de la función anterior para utilizar en la creación de los arrays de acciones (ver sección 5.5.2). Action newAction(void): Devuelve una nueva acción con actionType como NO ACTION y actionData igual a NULL. Estos campos deberán ser rellenados explı́citamente con los datos correspondientes a la acción que se quiera representar. Al igual que la función execute, la función freeAction deberá implementar una sentencia switch para liberar la memoria ocupada por los datos especı́ficos de la acción. void freeAction(Action action) { if (action -> actionData != NULL) { freeActionData(action -> actionData, action -> actionType); } free(action); } static void freeActionData(ActionData actionData, ActionType actionType) { switch (actionType) { case // case // case // NO_ACTION: Falls through SHOW_MENU_ACTION: Falls through STOP_VIDEO_ACTION: Falls through /* * [...] * * Continúa con todas las acciones que no * necesitan datos adicionales */ 106 Capı́tulo 5. Implementación de DFBCIn break; case NEXT_MENU_ACTION: freeNextMenuActionData(actionData); break; case PREVIOUS_MENU_ACTION: freePreviousMenuActionData(actionData); break; /* * [...] * * Continúa con el resto de funciones de * liberación de ’actionData’s */ default: DFBCIN_FATAL(IMPLEMENTATION_ERROR); break; } } Para aquellas acciones que no necesiten datos especı́ficos no será necesario realizar ninguna acción adicional a la hora de liberar la memoria ocupada ya que el campo actionType será un puntero NULL. Para el resto de acciones se deberá añadir el código que se encargue de liberar los tipos de datos que utilizan para almacenar la información extra. Las funciones de creación y liberación para los tipos de datos definidos para almacenar la información necesaria para ejecutar las acciones también serán exportados por el API, ya que serán necesarios para completar las variables devueltas por newAction. newNextMenuActionData(const char *menuName) newPreviousMenuActionData(int depth, short reinit) newPlayVideoActionData(const char *videoPath) La interfaz Action 107 newChangeOptionActionData(int value, short absolute) newBindKeyActionData(InputKey inputKey, Key key) newSeekVideoActionData(SeekVideoType type, double time) newSetPropertyActionData(char *name, char *value) newShowTextActionData(void) newLoadMp3ActionData(const char *path) void freeNextMenuActionData(ActionData actionData) El resto de funciones de liberación son idénticas a la anterior con el nombre de la acción correspondiente. 5.9.4. ¿Cómo instanciar una acción? Por ejemplo, si se necesita una acción que al ejecutarla se encargue de solicitar el menú de nombre “mainMenu” con el atributo “test” con valor “yes” a VXMLS serı́a necesario el siguiente código4 : Action action; NextMenuActionData nextMenuActionData; action = newAction(); nextMenuActionData = newNextMenuActionData("mainMenu"); addArray(nextMenuActionData -> arguments, test); addArray(nextMenuActionData -> arguments, yes); action -> actionType = NEXT_MENU_ACTION; action -> actionData -> nextMenuActionData = nextMenuActionData; La llamada al método action.execute se implementa utilizando la función execute(action). Para liberar la memoria ocupada tanto por action como por nextMenuActionData sólo serı́a necesaria una llamada freeAction(action). 4 test y yes deberán ser strings con memoria reservada e inicializados como “test” y “yes” respectivamente 108 5.9.5. Capı́tulo 5. Implementación de DFBCIn ¿Cómo añadir una nueva acción? Los pasos que se deberán seguir para añadir un nuevo tipo de acción (lo que en diseño equivaldrı́a a una nueva implementación de la interfaz Action) son: 1. En caso de que sea necesario, crear un nuevo tipo de datos para almacenar información para ejecutar la acción y las funciones para crear y liberar variables de ese tipo de datos. Estas funciones deberán ser exportadas para el resto de la aplicación. 2. Añadir un campo del tipo anteriormente definido a la enumeración ActionData. 3. Añadir un identificador para la nueva acción a la enumeración ActionType. 4. Suponiendo NuevaAccion como nombre del nuevo tipo de acción que se está creando: implementar las funciones executeNuevaAccion con el código para ejecutar la acción. 5. Añadir los nuevos casos a los switch de executeAction y freeAction. 5.10. Implementación del motor de la aplicación 5.10.1. Main La implementación de la clase Main consistirá simplemente en la función main, que será la que se ejecute al iniciar la aplicación. Los pasos que debe seguir esta función serán: 1. Crear una variable de tipo GlobalContext que será pasada a la función execute a la hora de ejecutar las acciones para permitir que modifiquen el estado de la ejecución. En la implementación actual el tipo GlobalContext solamente contiene una pila en la que se irán almacenando los menús a medida que el usuario profundiza en la interfaz definida y una bandera para controlar los cambios en el sistema de propiedades para saber si debe ser salvado a disco. 2. Inicializar los subsistemas necesarios (lo que en diseño equivale a crear los objetos Singleton [38]). Para ello deberá ejecutar las funciones ***Init correspondientes: propertiesInit() Implementación del motor de la aplicación 109 vxmlsInit() graphicsInit(argc, argv) interfaceInit() interfaceInputInit() 3. Obtener el menú principal de VXMLS, añadirlo a la pila de menús e indicarle al módulo gráfico que lo muestre por pantalla. Para ello deberá utilizar el módulo VXMLS y el API del módulo gráfico. /* Load main menu */ menu = vxmlsGetMenu(ROOT_MENU, NULL); pushStack(globalContext -> menuStack, menu); /* Show main menu */ interfaceLoadMenu(menu, 1); interfaceShowMenu(); 4. Esperar a recibir bloques de acciones a ejecutar y ejecutarlos cuando estén disponibles. Normalmente, el primer bloque de acciones a ejecutar serán las acciones especificadas en el atributo initActions del menú principal. Después de la ejecución de cada bloque de acciones se comprueba si se ha modificado el sistema de propiedades y en caso de que sea ası́, se salva su contenido en el disco5 . while (1) { array = interfaceWaitForActions(); for (i = 0; i < numArrayElements(array); i++) { action = (Action) getArrayData(array, i); executeAction(action, globalContext); } /* Save the properties to disk if needed */ if (globalContext -> saveProperties) { propertiesSave(); globalContext -> saveProperties = 0; 5 El sistema de propiedades se dejó de utilizar para almacenar datos cambiantes en cuanto el control de este tipo de datos pasó a VXMLS, por lo que este paso es innecesario en la implementación actual 110 Capı́tulo 5. Implementación de DFBCIn } /* Release executed actions */ releaseArray(array); } 5.10.2. Interface 5.10.2.1. Introducción La implementación del objeto de la clase Interface se hará siguiendo los pasos explicados en la sección 5.4. La función interfaceInit creará un nuevo hilo de ejecución que se encargará de ejecutar la función interfaceThread que controlará la comunicación con el módulo gráfico y con InterfaceInput y detectará los arrays de acciones que deben ser ejecutados. 5.10.2.2. Banderas de estado Para definir el estado con el que controlar la ejecución del nuevo hilo de ejecución se utiliza un sistema de banderas. Las banderas definidas son: INTST MENU LOAD: Si está activa quiere decir que hay un menú cargado. INTST SHOW MENU: Las funciones exportadas activarán esta bandera para indicarle a interfaceThread que debe mostrar el menú cargado. INTST HIDE MENU: Esta bandera se activará para indicar que se debe ocultar el menú. INTST INPUT EVENT: Esta bandera será activada para señalar que hay un nuevo evento de entrada que atender. INTST ACTION READY: Si está activa indica que hay acciones listas para ser ejecutadas. INTST WAITING ACTION: Se activa para indicar que Main está esperando para recibir acciones a ejecutar. INTST NEW FRAME: La activará el sistema de reproducción de vı́deo para indicar que se ha decodificado un nuevo frame y que debe ser mostrado en la pantalla. Implementación del motor de la aplicación 111 INTST VIDEO LOAD: Se activará para indicar que se debe cargar un nuevo vı́deo. INTST VIDEO UNLOAD: Se deberá activar para indicar que se debe terminar la reproducción de vı́deo. INTST VIDEO PLAY: Con esta bandera se señala que se debe comenzar la reproducción del vı́deo cargado. INTST VIDEO STOP: Se utiliza para indicar que se debe detener la reproducción en curso. INTST VIDEO RESUME: Se activará cuando se desee continuar con la reproducción de vı́deo anteriormente detenida. INTST VIDEO PLAYBACK: Si está activa indica que se está reproduciendo un vı́deo (se mantiene activa aunque la reproducción esté temporalmente detenida). INTST MENU INIT: Se activará si se deben ejecutar las acciones de inicialización de un menú. INTST VIDEO END: Indica que se ha alcanzado el final del vı́deo. INTST EXIT: Se utiliza para terminar la aplicación, solamente es necesaria para las pruebas del sistema. INTST SHOW TEXT: Se activará para señalar que se debe mostrar un texto en la pantalla. INTST HIDE TEXT: Se activará para que interfaceThread oculte el texto anteriormente mostrado. Estas banderas no son accesibles para el resto de la aplicación, sólo serán manipuladas por las funciones exportadas para el resto de módulos y por las funciones internas de Interface. 5.10.2.3. Funciones exportadas La mayorı́a de las funciones exportadas al resto de la aplicación se limitarán a activar o desactivar algunas de las banderas del estado (siempre después de cerrar el semáforo) y despertar el hilo interfaceThread para que ejecute las operaciones pertinentes. Las funciones exportadas son: 112 Capı́tulo 5. Implementación de DFBCIn void interfaceInit(void): Simula la creación del objeto Singleton [38], para ello reserva la memoria necesaria para almacenar el estado (definido por la variable interfaceState) y crea el nuevo hilo interfaceThread. void interfaceDeinit(void): Destruye el objeto creado por la función anterior, para ello finaliza el hilo interfaceThread y libera la memoria ocupada por la variable de estado. void interfaceLoadMenu(Menu menu, short init): Se utiliza para cargar un nuevo menú en la interfaz. init indica si se deben ejecutar las acciones de inicialización. Esta función activa la bandera INTST MENU LOAD y, en caso de que init sea distinto de 0, también activa INTST MENU INIT. void interfaceUnloadMenu(void): Solicita la descarga del menú anteriormente cargado activando INTST MENU UNLOAD. void interfaceShowMenu(void): Activa INTST SHOW MENU. void interfaceHideMenu(void): Activa INTST HIDE MENU. void interfaceNotifyInputEvent(void): Esta función será utilizada por InterfaceInput para indicar que se ha detectado un nuevo evento de entrada que debe ser tratado. Activa la bandera INTST INPUT EVENT. void interfaceLoadVideo(char *videoFile, Array endActions): Activa INTST VIDEO LOAD, copia videoFile en un campo de la variable de estado y añade una referencia a endActions, para tener disponibles las acciones del final del vı́deo en caso de que este se produzca. void interfacePlayVideo(void): Activa INTST VIDEO PLAY. void interfaceStopVideo(void): Activa INTST VIDEO STOP. void interfaceResumeVideo(void): Activa INTST VIDEO RESUME. void interfaceUnloadVideo(void): Activa INTST VIDEO UNLOAD. void interfaceShowText(Array lines): Activa INTST SHOW TEXT y carga las lı́neas de texto en el módulo gráfico. void interfaceHideText(void): Activa INTST HIDE TEXT. Array interfaceWaitForActions(void): En caso de que no esté activa la bandera INTST ACTION READY, activa INTST WAITING ACTION y bloquea la ejecución en la condición actionCond durante el tiempo indicado por el Implementación del motor de la aplicación 113 campo time del menú cargado (durante tiempo infinito si este valor es -1). En caso de que finalice la temporización referencia el array timeActions del menú cargado en el array que se mantiene en el estado con las funciones listas para ser ejecutadas y activa INTST ACTION READY. Finalmente comprueba si ya está activa al bandera INTST ACTION READY y en caso contrario vuelve al bloqueo en la condición actionCond. 5.10.2.4. Ciclo de ejecución El ciclo de ejecución del hilo interfaceThread se mantiene dormido hasta que alguna de las funciones anteriores lo despierta. Tras esto comprueba las variables de estado y ejecuta las funciones que sean necesarias. En primer lugar, en caso de que no esté activa INTST ACTION READY comprueba si hay acciones listas para ejecutarse: 1. Si está activa la bandera INTST MENU INIT la desactiva y guarda el array initActions del menú cargado para devolverlo cuando se soliciten las acciones a ejecutar. 2. Si está activa la bandera INTST VIDEO END la desactiva y guarda el array de acciones endVideoActions para devolverlo como acciones a ejecutar. 3. Si está activa la bandera INTST INPUT EVENT recuperará la tecla pulsada utilizando la función getInputKey de InterfaceInput y comprobará el objeto Key correspondiente a dicha tecla en el menú cargado. Si la pulsación de esa tecla está asociada a la ejecución de un grupo de acciones guardará esas acciones para devolverlas como acciones a ejecutar, en caso de que la tecla esté vinculada a un evento EXECUTE, guardará el array de acciones de la opción señalada por el parámetro currentPosition del menú cargado. En caso de que la tecla esté asociada a un evento NULL, la entrada será ignorada (ni siquiera detiene la temporización). Si en el paso anterior se ha detectado un array de acciones a ejecutar se activa la bandera INTST ACTION READY. En caso de que esté activa la bandera INTST WAITING ACTION se despierta a los hilos que estén esperando en la condición ActionCond (en la función interfaceWaitForActions). Si está activa la bandera INTST VIDEO PLAY, se desactiva y se comienza la reproducción de vı́deo utilizando la biblioteca gráfica. También se activa la bandera INTST VIDEO PLAYBACK y se indica a la biblioteca gráfica que 114 Capı́tulo 5. Implementación de DFBCIn a partir de ese momento dibuje los menús dentro de un marco, ya que se superpondrán a la imagen de vı́deo. Si está activa la bandera INTST NEW FRAME se desactiva y se indica que debe ser redibujada la pantalla activando una variable local flip. Para controlar la reproducción del vı́deo se comprueba si esta activa alguna de las banderas INTST VIDEO STOP o INTST VIDEO RESUME en caso de que ası́ sea, se desactiva y se utiliza la biblioteca gráfica para detener o continuar la reproducción en curso. Si está activa la bandera INTST VIDEO LOAD se desactiva y se carga el fichero de vı́deo indicado en la variable de estado en el módulo gráfico. Si está activa la bandera INTST VIDEO UNLOAD se desactiva, se descarga el vı́deo del módulo gráfico y se indica al mismo que deje de dibujar el marco de los menús. También se libera el array endVideoActions anteriormente referenciado durante la ejecución de la función interfaceLoadVideo. Las banderas INTST SHOW MENU y INTST HIDE MENU provocarán que se cargue o descargue el menú en el módulo gráfico respectivamente en caso de que estén activadas. Ambas serán desactivadas una vez realizada la operación correspondiente. Para hacer que los cambios realizados sean visibles se debe redibujar la pantalla, por lo que también se activa la variable flip. Las banderas INTST SHOW TEXT y INTST HIDE TEXT provocarán un comportamiento similar a las dos anteriores, pero con lı́neas de texto en lugar del menú. Por último, en caso de que esté activa la variable flip, utilizará las funciones del API del módulo gráfico para dibujar en pantalla lo que sea necesario. Si está activa la bandera INTST VIDEO PLAYBACK llamará a la función graphicsBlitVideo para dibujar el frame actual como fondo, después llamará a la función graphicsFlipScreen que dibujará el resto de la interfaz gráfica (menú y/o texto) y hará los cambios visibles. Tras esto desactiva la variable flip. 5.10.3. InterfaceInput Al igual que Interface la implementación de InterfaceInput también se hará de acuerdo con lo explicado en la sección 5.4. Implementación del motor de la aplicación 115 La función intefaceInputInit reservará memoria para almacenar el estado y creará un nuevo hilo de ejecución interfaceThread que monitorizará la entrada y avisará a Interface cada vez que detecte que el usuario ha pulsado una tecla. El estado de este subsistema se compone de unas banderas y un espacio para almacenar un evento detectado. Las banderas de estado son: INPTST EVENT DETECTED: Se activará cuando se detecte un evento de entrada. INPTST EVENT STORED: Se mantendrá activa mientras se tenga almacenado un evento anteriormente detectado a la espera de que sea consultado por Interface. El ciclo de ejecución del hilo que controla este objeto sigue los siguientes pasos: 1. Si no está activa la bandera INPTST EVENT STORED hace una llamada a la función graphicsTestInput del modulo gráfico. Esta función bloqueará la ejecución hasta que se detecte un evento de entrada. Una vez obtenido el evento de entrada, comprueba si éste es válido y, en caso de que ası́ sea, activa INPTST EVENT DETECTED y INPTST EVENT STORED. 2. Si está activa la bandera INPTST EVENT STORED la desactiva y llama a la función interfaceNotifyInputEvent. 3. Mientras esté activa la bandera INPTST EVENT STORED detiene su ejecución. Este módulo exporta la función getInputKey que devuelve el evento almacenado y desactiva la bandera INPTST EVENT STORED. Esta función debe bloquear el semáforo antes de acceder al estado, por lo que sólo podrá completar su ejecución en caso de que el hilo interfaceInputThread esté dormido. Tras devolver el evento detectado despierta el hilo para que continúe su ejecución. Para destruir el objeto creado, se exporta la función interfaceInputDeinit, que detiene la ejecución del hilo creado por la función de inicialización y libera la memoria ocupada por la variable de estado. 116 Capı́tulo 5. Implementación de DFBCIn 5.10.4. Mp3Player 5.10.4.1. Introducción Esta clase también es una aplicación del patrón Singleton [38], por lo que se implementará de acuerdo con lo explicado en 5.4. Para la reproducción de los ficheros o URLs se utilizará el programa mpg123. Este programa define un protocolo de comunicación utilizando la entrada y salida estándar, lo que permite controlarlo desde DFBCIn utilizando un pipe y redirección de ficheros. 5.10.4.2. Banderas de estado Las banderas utilizadas para el control del hilo de ejecución de este objeto son: PLST LOAD: Se activa para indicar que se debe iniciar la reproducción de un cierto fichero o URL. PLST PAUSE CONTINUE: Se activa para indicar que se debe detener la reproducción en curso o continuar con una reproducción anteriormente detenida. PLST QUIT: Se activa para indicar que se debe terminar la ejecución del programa mpg123. PLST PAUSED: Se activará cuando la reproducción esté detenida. 5.10.4.3. Funciones exportadas La función mp3Init se encargará de crear el objeto. Para ello ejecuta las siguientes operaciones: 1. Crea dos pipes, uno para enviar comandos a mpg123 y otro para leer información del mismo. 2. Crear un nuevo proceso del sistema operativo con una llamada fork. La entrada estándar de este nuevo proceso se redirige al extremo de lectura del pipe de comandos y la salida estándar se redirige al extremo de escritura del pipe de información. Tras esto se ejecuta el programa mpg123 con una llamada exec. Implementación del módulo VXMLS 117 3. Crear un nuevo hilo de ejecución que monitorice la ejecución de mpg123 utilizando los pipes anteriormente creados. Antes de crear este nuevo hilo, se asegura que mpg123 está funcionando correctamente leyendo el mensaje de confirmación del pipe de información. La función mp3Load llamará a mp3Init en caso de que el objeto no haya sido creado (es decir, que no exista el hilo de control), tras esto bloquea el estado, activa la bandera PLST LOAD y, en caso de que esté activa la bandera PLST PAUSED, despierta el hilo de control. La función mp3PauseContinue obtiene el bloqueo del estado, activa la bandera PLST PAUSE CONTINUE y, en caso de que esté activa la bandera PLST PAUSED, despierta el hilo de control. La función mp3Deinit bloquea el estado, activa la bandera PLST QUIT, despierta el hilo de control en caso de que esté activa la bandera PLST PAUSED, libera el estado, espera a que finalice el hilo y después espera a que termine la ejecución de mpg123. 5.10.4.4. Ciclo de ejecución El hilo de control sigue los siguientes pasos en su ciclo de ejecución: 1. Si está activa la bandera PLST PAUSED bloquea su ejecución en una condición, a la espera de ser despertado por alguna de las funciones exportadas. 2. Si no está activa la bandera PLST PAUSED, lee una lı́nea del pipe de información para asegurarse de que la reproducción no ha llegado a su fin, en cuyo caso volverá a empezarla desde el principio. Tras esto libera el estado, cede la CPU para permitir la ejecución de las funciones exportadas y vuelve a bloquear el estado. 3. Si está activa la bandera PLST LOAD, la desactiva y escribe el comando para cargar la URL en el pipe de comandos y lee la confirmación del pipe de información. 4. Si está activa la bandera PLST PAUSE CONTINUE la desactiva y escribe el comando para detener o reanudar la reproducción. Tras esto lee la información devuelta y activa o desactiva la bandera PLST PAUSED según sea necesario. 5. Si está activa la bandera PLST QUIT escribe el comando para terminar la ejecución de mpg123 y termina termina el hilo, en caso contrario vuelve al paso 1. 118 Capı́tulo 5. Implementación de DFBCIn 5.11. Implementación del módulo VXMLS 5.11.1. Módulo HTTP 5.11.1.1. Introducción La comunicación con VXMLS se hará mediante peticiones GET [35]. Para poder realizar estas peticiones se utilizará la clase utilidad HTTP que proporciona las funciones httpGet y escapedEncoding que permiten enviar peticiones GET genéricas al puerto de un servidor. Lógicamente, no se ha realizado una implementación completa de un cliente HTTP, sino que sólo se pretende que sea posible realizar peticiones a VXMLS. Aún ası́, el servidor y el puerto se pasan como parámetros para respetar la modularidad del sistema. En [42] se definen la sintaxis que deben respetar las URLs (una URL es un tipo especial de URI). La función escapedEncoding transformará cadenas de texto utilizando la codificación hex hex, para evitar que los caracteres reservados que se puedan encontrar en esas cadenas varı́en la semántica de la URL. Por ejemplo, si se quiere pasar un argumento un-&-es-un-caracter-reservado deberá ser previamente codificado para evitar que el caracter & sea interpretado como separador de argumentos: strcpy(argumento, "un-&-es-caracter-reservado"); escapedEncoding(argumento); puts(argumento); > un-%26-es-caracter-reservado 5.11.1.2. Peticiones GET La función httpGet se encargará de hacer una petición GET HTTP sobre una URL de un cierto servidor que esté escuchando en un puerto determinado. Para ello se implementarán las partes necesarias del protocolo HTTP. Para mantener las sesiones de los diferentes usuarios se utilizará un sistema de cookies [45]. Este sistema se implementará de forma sencilla; sólo se necesita poder almacenar una única cookie en memoria, se supone que la sesión sólo será mantenida mientras que el cliente esté funcionando. Envı́o de peticiones GET HTTP 1.1, contemplando la posibilidad de enviar el campo Cookie en caso de que sea necesario. Interpretación de ciertos campos de la cabecera de la respuesta del servidor: Implementación del módulo VXMLS 119 • Connection: En caso de que contenga el valor close, se puede asumir que el servidor cerrará la conexión en cuanto termine de enviar el mensaje. • Content-Length: Este campo se utilizará para indicar cuántos bytes se deben leer para obtener la respuesta completa. El servidor HTTP implementado por Inets [39] devuelve el campo Connection con valor close en lugar de devolver la longitud de la respuesta. • Set-Cookie: Este campo indica que se debe almacenar una determinada cookie. La cookie almacenada se enviará en todas las siguientes peticiones en el campo Cookie de la cabecera GET. Set-Cookie también puede ser utilizado para ordenar al cliente la destrucción de una cookie previamente almacenada. • VXMLS-Error : Este campo se ha definido para que VXMLS pueda advertir a DFBCIn de que el contenido que le envı́a puede no ser el esperado, ya que se ha producido un error. 5.11.1.3. Envı́o de peticiones GET Para enviar peticiones GET se utiliza la función int httpGet(char *server, int port, char *request, int fileDes) que ejecuta los siguientes pasos para enviar la petición request al puerto port del servidor server HTTP y escribir la respuesta en el descriptor de fichero fileDes. 1. Abre un socket y envı́a la petición. De esto se encarga una función interna sendRequest mediante los pasos: a) Crear un socket orientado a conexión. b) Obtener la dirección de server. c) Conectar con el servidor utilizando el socket. d ) Crear la cabecera GET a enviar con la URL request y el campo Cookie en caso de que sea necesario. e) Enviar la petición 2. Leer la cabecera de la respuesta para obtener el tamaño del contenido (o asegurarse de que se ha devuelto el campo Connection con valor close) y guardar una nueva cookie o destruir la cookie almacenada en caso de que sea necesario. Si se detecta el campo VXMLS-Error se escribirá un aviso en el error estándar para clarificar las causas del fallo. 120 Capı́tulo 5. Implementación de DFBCIn 3. Leer el resto del mensaje y escribirlo en el fileDes hasta que se alcance el final, ya sea el marcado por el campo Content-Length o el marcado por el cierre de la conexión por parte del servidor (en caso de que el anterior campo no hubiera sido devuelto). 5.11.2. Parser VXMLS La estructura de los documentos XML devueltos por VXMLS para las operaciones que deben ser confirmadas es muy sencilla (ver el apéndice A), lo que facilita la elaboración del parser para interpretarlos. El API exportado por el parser VXMLS exporta dos funciones al resto módulos: parseVxmlsResponse para interpretar las repuestas de VXMLS y la función getVxmlsErrorMessage que devuelve un mensaje explicativo en el caso de que se haya producido algún error. Para almacenar el estado interno del parser durante el proceso de parsing se define una estructura con los campos: AppError error: Para indicar qué error se produjo en caso de que se produjera alguno. char errorMessage[]: Mensaje devuelto por VXMLS explicando el error. short inError: Se utiliza para indicar que se está interpretando el contenido de la etiqueta error. short abort: Se activará en caso de que se detecte un error que obligue a abortar el proceso de parsing. En el parser creado se instalarán los siguientes manejadores (ver sección 3.4.3): startElement: Se instala para manejar la apertura de las etiquetas. • Si está activo el campo abort no hace nada. • Si la etiqueta que se está abriendo es la etiqueta ok pone el valor NO ERROR en el campo error del estado del parser para indicar que VXMLS confirmó la operación. • Si la etiqueta que se está abriendo es la etiqueta error pone el valor VXMLS ERROR en el campo error del estado del parser para indicar que VXMLS ha fallado al realizar la operación y activa inError. Implementación del módulo VXMLS 121 • En otro caso, si la etiqueta abierta no es vxmls (etiqueta raı́z del documento esperado) se pone el valor PARSER ERROR en el campo error para indicar que la respuesta de VXMLS es incomprensible y se activa abort. endElement: Se instala para manejar el cierre de las etiquetas. • Si está activo el campo abort no hace nada. • Si la etiqueta que se está cerrando es la etiqueta error desactiva el campo inError del estado del parser y copia el mensaje de error errorMessage en una variable global para que pueda ser devuelto por la función getVxmlsErrorMessage. characterData: Se instala para manejar las secciones de caracteres. Su objetivo es copiar la sección de datos de la etiqueta error en el campo errorMessage del estado del parser. Lo único que deberá hacer esta función será concatenar los datos que recibe como argumentos en el campo errorMessage en caso de que el campo inError esté activo. La función AppError ParseVxmlsResponse(int fileDes) creará un nuevo parser utilizando el API de expat, le instalará los manejadores anteriormente definidos y lo utilizará para parsear los contenidos del fichero descrito por fileDes. Devuelve un código de error, en caso de que todo haya ido bien, el código devuelto es NO ERROR. 5.11.3. Parser para los documentos menu 5.11.3.1. Introducción La estructura de un documento XML utilizado para describir un determinado menú de la interfaz pude ser arbitrariamente profunda (ver el apéndice A) debido a que algunos tipos de acción pueden contener descripciones de otras acciones en su interior. Además de esto, el numero de acciones descritas es susceptible de ser aumentado con frecuencia, por lo que se deberá implementar el parser de forma que el hecho de añadir nuevas acciones no afecte al código existente. Para resolver el problema de la profundidad arbitraria se utilizará una pila en el estado del parser. Las funciones utilizadas por éste podrán utilizar la pila para almacenar la información necesaria entre la apertura y el cierre de las etiquetas. En esta pila se almacenará un tipo de datos llamado Context que apunta a una 122 Capı́tulo 5. Implementación de DFBCIn estructura con dos campos, un campo tag en el que se indicará a qué etiqueta pertenece el contexto y un campo data en el que se almacenará la información especı́fica (será de tipo puntero a void, las funciones especı́ficas de cada etiqueta deberán hacer la transformación de tipo necesaria). El caso de la extensibilidad se resuelve estructurando el código de forma que las funciones que controlan la interpretación de la parte “estable” del documento y las funciones que controlan la parte cambiante estén lo más desacopladas posible6 . 5.11.3.2. Organización del código El código se distribuye de la siguiente forma, para permitir añadir las funciones necesarias para parsear nuevas acciones de forma sencilla: La cabecera parser.h en la que se define la única función que va a ser exportada para el resto de la aplicación: parseMenu. La cabecera parser-types.h en la que se definen los tipos que van a ser utilizados tanto en la parte estable como en la parte que define las acciones. Estos tipos son: • Tag: Enumeración utilizada para definir cada una de las posibles etiquetas que se pueden encontrar de acuerdo con el DTD, por ejemplo, la etiqueta <option ...> se corresponderá con una variable de tipo Tag con valor OPTION. • Context: Contiene la información relativa a una etiqueta abierta, como se explicó al principio de esta sección. • ParserData: Es el tipo que tendrá la variable que representa el estado del parser durante el proceso de interpretación. Apunta a una estructura con dos campos, en uno estará la pila de contextos y en otro el objeto de la clase Menu que se irá construyendo a medida que se interpreta la información del documento. • BindKeyContextData: Este tipo es necesario para almacenar la información que se necesitará durante el proceso de interpretación del contenido de la etiqueta bindKey. Para el manejo del tipo Context se declaran tres funciones: 6 Como parte cambiante se entiende lo que va dentro de las etiquetas action, sin incluir éstas Implementación del módulo VXMLS 123 • Context newContext(Tag tag, void *data): Crea una variable de tipo Context para la etiqueta representada por tag y con los datos apuntados por data. • void freeContext(Context context): Libera el espacio que ocupa el contexto context pero no el que ocupan sus datos. • void freeContextAndData(Context context): Libera el espacio reservado para context y los datos que referencia. En el fichero parser.c se define la función parseMenu y la parte estable de las funciones que componen el parser, ası́ como las funciones getTag, que transforma un string con el nombre de una etiqueta en una variable de tipo Tag; freeContext y freeContextAndData; y getAttrValue que será utilizada por las funciones que manejan la apertura de las etiquetas para obtener los valores de los argumentos XML de esas etiquetas, por ejemplo, para la apertura <ejemplo nombre="valor"> la llamada getAttrValue(attrs, "nombre"), devolverá "valor" (attrs es un conjunto de pares nombre-valor que proporciona la implementación genérica de parser hecha por expat). Por último, en los ficheros tag-functions.h y tag-functions.c se implementan las funciones que controlarán la apertura y el cierre de las acciones especı́ficas. 5.11.3.3. Proceso de parsing ¿Cómo conseguir una independencia lógica aceptable si todas las funciones trabajan sobre la misma pila y el mismo objeto Menu? El propio proceso de parsing facilita la solución de este problema. Como se explicó en la sección 4.5.5.3, las etiquetas menos profundas no se ven afectadas por las labores de las más profundas ya que estas trabajarán en la pila por encima de sus datos. Por lo tanto, el proceso de parsing de la parte estable de los documentos puede hacerse sin tener en cuenta los pasos que deberán dar las funciones que interpreten las acciones especı́ficas. Un aspecto que debe tenerse en cuenta es que las funciones que trabajan con acciones especı́ficas pueden necesitar utilizar etiquetas action, que son parte de lo que se considera parte estable de los documentos. Aún ası́ el tratamiento de las etiquetas action puede hacerse de forma genérica con un pequeño “truco” que se explicará un poco más adelante en la sección 5.11.3.4. Dentro de parser.c, las funciones que manejarán los eventos de apertura, cierre y datos de las etiquetas serán: 124 Capı́tulo 5. Implementación de DFBCIn manageStartElement: Recibe como argumentos: • ParserData parserData: La variable de estado del parser. • const XML Char **attrs: Array de strings con los nombres y los valores de los atributos con los que fue abierta la etiqueta. Se puede utilizar la función getAttrValue como se explicó anteriormente para obtener el valor de un determinado argumento de forma sencilla. • Tag tag: Identifica la etiqueta que se está interpretando. manageEndElement: Recibe los argumentos: • ParserData parserData: La variable de estado. • Tag tag: Identifica la etiqueta que se está cerrando. manageCharacterData: Recibe los argumentos: • ParserData parserData: La variable de estado del parser. • const XML Char **text: Fragmento del contenido PCDATA de la etiqueta. • int length: Longitud del fragmento de datos text. • tag Tag: Etiqueta que se está tratando. Dentro de cada una de estas funciones habrá un bloque switch que ejecutará el código correspondiente en función del valor de tag. Para los valores MENU, VERSION, TIMER, INIT ACTIONS, OPTION, TEXT, HEADER y ACTION, el código estará dentro de este mismo fichero; estas son las etiquetas que no cambiarán. El resto de etiquetas (las que definen los diferentes tipos de acción) provocarán una llamada a alguna de las funciones declaradas en el fichero tag-functions.h. Estas tres funciones no son las que se instalan directamente como manejadores en el parser creado con expat [2], las funciones que espera expat tienen unos argumentos ligeramente diferentes. Los manejadores instalados actuarán como adaptadores entre expat y las tres funciones anteriormente explicadas. startElement: Llama a manageStartElement. Recibe los siguientes argumentos del parser creado con expat: • void *userData: Contiene la variable de estado instalada en el momento de crear el parser. Será convertida al tipo ParserData y se pasará en el argumento parserData de manageStartElement. Implementación del módulo VXMLS 125 • const XML Char *name: Nombre de la etiqueta. Se obtendrá el valor Tag correspondiente usando la función getTag y se pasará en el argumento tag • const XML Char **attributes: Este valor se pasa tal cual. endElement: Llama a manageEndElement, su comportamiento es idéntico al de startElement, pero no recibe el argumento attributes. characterData: Llama a manageCharacterData. Recibe los siguientes argumentos: • void *userData: Se convertirá al tipo ParserData y se pasará en el atributo parserData. • const XML Char *text: Este atributo se pasa tal cual. • int length: Al igual que text, se pasa tal cual. Como se puede ver, characterData no recibe el argumento name, por lo que en principio no se sabe a qué etiqueta pertenecen los datos. Además, expat no garantiza que se haga una única llamada a characterData para cada bloque de datos, sino que puede que los datos de una misma etiqueta se lean con más de una llamada a esta función. La solución a ambos problemas: cuando para una etiqueta se necesite leer su contenido, en la apertura de la misma se apilará un contexto con espacio reservado en el que se irán concatenando los fragmentos de información leı́da. manageCharacterData obtendrá el contexto de la cima de la pila y comprobará el valor del atributo tag del mismo. Este valor se le pasará a la función manageCharacterData. Es decir, si para una determinada etiqueta es necesario leer su contenido, es obligatorio que después de que se ejecute manageCharacterData para dicha etiqueta en la cima de la pila se encuentre un contexto con el valor tag correspondiente a la etiqueta que se está tratando. 5.11.3.4. Interpretación de la parte estable de los documentos Los pasos que se dan para interpretar las etiquetas que forman la parte estable de los documentos son: Para la etiqueta menu no es necesario hacer nada, ni en la apertura ni en el cierre de la misma. La etiqueta version contiene datos de caracteres en su interior, por lo tanto, en su apertura se apilará un nuevo contexto con espacio para que manageCharacterData copie el contenido de la etiqueta (appMalloc devuelve un puntero a un bloque de memoria reservada): 126 Capı́tulo 5. Implementación de DFBCIn string = (char *) appMalloc(10); string[0] = ’\0’; context = newContext(VERSION, string); pushStack(parserData -> stack, context); La función manageCharacterData concatenará los datos que vaya leyendo en el espacio de datos del contexto que se encuentre en la cima de la pila: context = (Context) seeStackTop (parserData -> stack); string = (char *) context -> data; strncat(string, text, length); Por último, en el cierre de la etiqueta se desapila el contexto de la cima de la pila, cuya sección de datos contendrá la cadena de caracteres completa leı́da del interior de la etiqueta, interpreta esta cadena para obtener los números de versión, los copia en el menú incompleto contenido en el estado del parser (estas dos operaciones las realiza parseVersion) y comprueba que la versión está soportada. Por último libera la memoria ocupada por context y por los datos que contiene. context = (Context) popStack(parserData -> stack); if (context -> tag != VERSION) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } parseVersion(context -> data, &(parserData -> menu -> majorVersion), &(parserData -> menu -> minorVersion)); if(unsupportedVersion(paserData -> menu)) { DFBCIN_FATAL(VERSION_ERROR); } freeContextAndData(context); La interpretación de la etiqueta header se implementa de forma similar a la de la etiqueta version, pero en el cierre de la misma la única operación que hay que realizar es desapilar el contexto correspondiente y copiar su contenido en el campo header del menú que se está construyendo: Implementación del módulo VXMLS 127 context = (Context) popStack(parserData -> stack); if (context -> tag != HEADER || context -> data == NULL) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } strcpy(parserData -> menu -> header, (char *) context -> data); freeContextAndData(context); Para implementar la interpretación de action hay que tener en cuenta que, aunque action se haya considerado parte de la estructura estable de los documentos, las etiquetas que definan nuevas acciones pueden incluir etiquetas action en su interior. Se puede suponer que las acciones siempre van a ser leı́das en grupos, ya que, como se vio en apartados anteriores, las acciones se ejecutan en bloques. Por lo tanto, las acciones siempre van a ser almacenadas dentro de arrays. Aprovechando esta situación, se definirá una etiqueta especial ARRAY TAG, que no representa a ninguna etiqueta que pueda estar presente en los documentos, sino que representará un contexto especial cuyo campo de datos contiene un array en el que se almacenarán las acciones que se vayan interpretando. De esta forma, si una etiqueta necesita recolectar las acciones que hay en su interior, deberá dejar un contexto ARRAY TAG en la cima de la pila tras su apertura y en su cierre podrá desapilarlo y recoger las acciones que se hayan interpretado. La interpretación de una etiqueta action consistirá, por tanto, en: • En su apertura, se crea una acción vacı́a y se pone en un contexto en la cima de la pila a la espera de que las etiquetas anidadas dentro de action la definan. De esta forma, en la implementación de una acción especı́fica lo que se deberá hacer es asegurar que, en el cierre de la etiqueta que define la acción, se deje en la cima de la pila este mismo contexto con la acción ya completa. action = newAction(); context = newContext(ACTION, action); pushStack(parserData -> stack, context); 128 Capı́tulo 5. Implementación de DFBCIn • En el cierre de action, se desapilará el contexto ACTION que ya deberı́a tener la acción completa y se añadirá al array del contexto ARRAY TAG que deberı́a encontrarse en la cima de la pila. context = (Context) popStack(parserData -> stack); if (context -> tag != ACTION) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } action = (Action) context -> data; freeContext(context); /* Add the complete action to the receiver array */ context = (Context) seeStackTop(parserData -> stack); if (context -> tag != ARRAY_TAG) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } array = (Array) context -> data; addArray(array, action); La implementación del control de la etiqueta initActions sirve como primer ejemplo de aplicación del sistema descrito en el punto anterior para permitir la recogida de acciones sin afectar al código existente. La apertura de initActions consistirá simplemente en apilar un contexto ARRAY TAG con el array initActions del menú en construcción en el campo de datos. context = newContext(ARRAY_TAG, parserData -> menu -> initActions); pushStack(parserData -> stack, context); En el cierre de la acción sólo sera necesario desapilar en contexto ARRAY TAG y liberar la memoria ocupada por éste, pero no la ocupada por sus datos, ya que estos son el array de acciones que ya está referenciado por el menú. context = (Context) popStack(parserData -> stack); if (context -> tag != ARRAY_TAG) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); Implementación del módulo VXMLS 129 } freeContext(context); La implementación del código para timer actions es muy similar a la de initActions, con la diferencia de que para timer es necesario leer el atributo time en la apertura de la etiqueta y que esta vez el array de acciones, lógicamente, será timeActions. context = newContext(ARRAY_TAG, parserData -> menu -> timeActions); if ((value = getAttrValue(attributes, "seconds")) == NULL) { PARSER_FATAL; } pushStack(parserData -> stack, context); parserData -> menu -> time = atoi(value); El código que cierra la etiqueta es el mismo que para initActions (ejecutan el mismo bloque case). La apertura de la etiqueta option supone la creación de un objeto Option vacı́o que se apilará dentro de un contexto OPTION, sobre el cual se apilará un contexto ARRAY TAG para recolectar las acciones que se definen dentro de la opción. option = newOption(); context = newContext(OPTION, option); pushStack(parserData -> stack, context); context = newContext(ARRAY_TAG, option -> actions); pushStack(parserData -> stack, context); En el cierre de la etiqueta, se desapilarán ambos contextos y se añadirá la opción, ya completa, al array options del menú en construcción. Los contextos serán liberados, pero no sus contenidos. context = (Context) popStack(parserData -> stack); if (context -> tag != ARRAY_TAG) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } 130 Capı́tulo 5. Implementación de DFBCIn freeContext(context); context = (Context) popStack(parserData -> stack); if (context -> tag != OPTION) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } option = (Option) context -> data; addArray(parserData -> menu -> options, option); freeContext(context); Por último, para implementar el control de los eventos relacionados con la etiqueta text vuelve a ser necesario apilar un contexto con espacio para almacenar la información de caracteres que será leı́da por la función manageCharacterData de la misma forma que para las etiquetas version y header. En el cierre de la etiqueta se desapilará el contexto TEXT apilado en la apertura y se copiará su contenido en el campo text de la opción que se está construyendo. Como tras el contexto OPTION fue apilado un contexto ARRAY TAG es necesario desapilar previamente éste para poder acceder a la opción. Una vez modificada la opción, el contexto ARRAY TAG volverá a ser colocado en su sitio. context = (Context) popStack(parserData -> stack); if (context -> tag != TEXT || context -> data == NULL) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } strcpy(auxString, (char *) context -> data); freeContextAndData(context); auxContext = (Context) popStack(parserData -> stack); context = (Context) seeStackTop(parserData -> stack); if (context -> tag != OPTION || context -> data == NULL) { DFBCIN_FATAL(IMPLEMENTATION_ERROR); } option = (Option) context -> data; strcpy(option -> text, auxString); pushStack(parserData -> stack, auxContext); Implementación del módulo VXMLS 5.11.3.5. 131 Interpretación de las acciones Las etiquetas utilizadas para definir las acciones que pueden ser realizadas por DFBCIn se definen en los ficheros tag-functions.c y tag-functions.h. Para implementarlas se puede suponer, como se explicó para la etiqueta action, que en el momento de abrir una etiqueta para una determinada acción, en la cima de la pila habrá un contexto ACTION con una acción vacı́a. Las funciones que controlan la interpretación de este tipo de etiquetas deberán dejar en la cima de la pila la acción completa, una vez cerrada la etiqueta que se está interpretando. Por supuesto, en caso de que la acción esté definida por una estructura de etiquetas anidadas, se pueden apilar nuevos contextos sobre el contexto ACTION. Por ejemplo, para interpretar la descripción de una acción BindKey se definen las siguientes funciones: startBindKeyTag: Utilizará una variable de tipo BindKeyContextData para almacenar la información sobre la acción y la almacenará en un nuevo contexto que almacenará en la pila del parser. Leerá el atributo key de la etiqueta y lo escribirá en la variable almacenada en el contexto. startEventTag: Lee el argumento type de la etiqueta, crea un objeto Key (ver la sección 5.8) de acuerdo con el mismo (puede ser EXECUTE o NULL) y lo almacena en el campo correspondiente en los datos del contexto de la cima de la pila (es el contexto que se apiló en la función startBindKeyTag). startKeyActionsTag: En este caso se van a leer las acciones que están asociadas a la tecla, por lo que se crea un objeto Key y se apila un nuevo contexto ARRAY TAG con el array de acciones de dicho objeto en su sección de datos para recolectar las acciones. endKeyActionsTag: En el cierre de la etiqueta keyActions simplemente es necesario desapilar el contexto ARRAY TAG apilado en la apertura de esta etiqueta y liberarlo. endBindKeyTag: Desapilará el contexto de la cima de la pila y obtendrá de él los datos almacenados durante la interpretación de esta etiqueta (almacenados en una variable de tipo BindKeyContextData). Con esta información ya se pueden rellenar los campos de la acción que se encontrará en el contexto de la cima de la pila. context = (Context) seeStackTop(parserData -> stack); if (context -> tag != ACTION) { 132 Capı́tulo 5. Implementación de DFBCIn DFBCIN_FATAL(IMPLEMENTATION_ERROR); } actionData = newBindKeyActionData(bindKeyContextData -> inputKey, bindKeyContextData -> key); action = (Action) context -> data; action -> actionType = BIND_KEY_ACTION; action -> actionData = actionData; 5.11.3.6. ¿Cómo añadir una nueva acción? Para añadir el código necesario para interpretar una nueva acción se deberán seguir los siguientes pasos: 1. Añadir a la enumeración Tag los identificadores de las nuevas etiquetas que se vayan a utilizar para describir la acción. 2. Añadir a la función getTag las comparaciones necesarias para que pueda reconocer las nuevas etiquetas. 3. En caso de que sea necesario, definir el tipo de datos que se utilizará para almacenar la información en los bloques Context almacenados en la pila. Añadir los case que hagan falta en la función freeContextAndData para que sea capaz de liberar los contextos para las nuevas etiquetas. 4. Añadir a manageStartElement el código para manejar la apertura de las nuevas etiquetas definidas. Como se explicó anteriormente, para evitar tener que modificar en exceso parser.c (y también para evitar que crezca demasiado), el control de las etiquetas utilizadas para definir acciones se hace con las funciones de tag-functions.c, por lo tanto, para una etiqueta nuevaEtiqueta se creará una función startNuevaEtiquetaTag en tag-functions.c y se añadirá un case al switch de manageStartElement para que la llame cuando se abra dicha etiqueta. 5. Añadir a manageEndElement el código para manejar el cierre de las etiquetas definidas. Si es necesario realizar alguna acción, se procederá de forma similar al paso anterior creando una función endNuevaEtiquetaTag, en caso contrario, se añadirá un case vacı́o. Esta función aborta con un error IMPLEMENTATION ERROR en caso de detectar una etiqueta desconocida para evitar errores al añadir nuevas acciones. Implementación del módulo VXMLS 133 6. Opcionalmente, añadir el código necesario (creando una nueva función en tag-functions.c como en el caso anterior) para manejar el contenido de caracteres de alguna de las etiquetas creadas, pero normalmente valdrá con el código ya utilizado para text, header y version, que simplemente copia el contenido en el contexto de la cima de la pila. 5.11.4. Implementación de la fachada VXMLS 5.11.4.1. Introducción En el fichero vxmls.c se implementan las funciones que se encargarán de realizar las peticiones a VXMLS utilizando las funciones del módulo HTTP y de interpretar sus respuestas utilizando alguno de los parsers. Durante la inicialización de este módulo se leerá del sistema de propiedades el nombre y puerto del servidor en el que está funcionando VXMLS, ası́ como el nombre y clave que deberá utilizar DFBCIn para solicitar una nueva sesión de éste. Estos valores se guardarán ya codificados utilizando la función characterEncoding (ver la sección 5.11.1). Después de esto ejecutará la función vxmlsLogin para crear la sesión. El principal problema se encuentra a la hora de pasar la respuesta de VXMLS a los parsers. Los parsers leen de un descriptor de fichero y, como se vio en la sección 5.11.1, las respuestas HTTP se escriben en un descriptor de ficheros. La solución adoptada es unir esos dos descriptores de fichero mediante un pipe. Para ello se debe ejecutar la llamada httpGet en un hilo distinto al que ejecuta el parser. 5.11.4.2. Nuevo hilo para las peticiones Se definirá una función httpProxy que creará un pipe y un nuevo hilo de ejecución en el que utilizará la función httpGet para enviar la petición a VXMLS y escribir en uno de los extremos del pipe. El otro extremo será devuelto como valor de retorno para permitir ejecutar un parser que lea de él. static int httpProxy(const char *request) { ProxyData int pthread_t proxyData pipeFd[2]; thread; if (pipe(pipeFd) == -1) { = NULL; 134 Capı́tulo 5. Implementación de DFBCIn DFBCIN_FATAL(EXTERNAL_ERROR); } proxyData = newProxyData(request, pipeFd[1]); pthread_create(&thread, NULL, httpProxyThread, proxyData); return pipeFd[0]; } proxyData se utiliza para pasarle al hilo creado la cadena con la petición HTTP request y el descriptor de fichero en el que escribir la respuesta. La función httpProxyThread utilizará la función httpGet para enviar una petición GET al servidor VXMLS (utilizando el nombre y puerto obtenidos durante la inicialización de éste módulo). En caso de que el servidor responda con un código distinto de 200 se simulará que VXMLS ha devuelto un mensaje de error escribiendo en el pipe un mensaje de acuerdo con el DTD que especifica las respuestas de VXMLS utilizando la función writeError. static void writeError(const char *message, int fileDes) { char errorMessage[MAX_VXMLS_ERROR_MESSAGE]; sprintf(errorMessage, "<?xml version=’1.0’ encoding=’iso-8859-1’ standalone=’no’?>" "<!DOCTYPE vxmls SYSTEM \"/dtds/vxmls.dtd\">" "<vxmls><error>%s</error></vxmls>", message); write(fileDes, errorMessage, strlen(errorMessage)); } Una vez terminada la escritura de la respuesta, se cierra el descriptor de fichero y termina el hilo de ejecución. 5.11.4.3. Funciones exportadas Con ayuda de la función httpProxy la implementación de las funciones del API ofrecido por esta fachada es tan sencilla como: 1. Crear la cadena con la URL a enviar en la petición GET (en la sección 4.4 se describen las URLs a las que se debe solicitar cada posible operación): Implementación del módulo VXMLS 135 Para vxmlsLogin, siendo vxmlsClient el nombre que debe utilizar DFBCIn para crear la sesión y vxmlsPasswd la clave que debe utilizar para identificarse: sprintf(request, VXMLS_PREFIX "login?client=%s&passwd=%s", vxmlsClient, vxmlsPasswd); Para vxmlsLogout, simplemente VXMLS PREFIX "logout". Para vxmlsSetPorperty, suponiendo escapedName y escapedValue el nombre y el nuevo valor de la propiedad ya codificados con la función escapedEncoding: sprintf(request, VXMLS_PREFIX "set_property?name=%s&value=%s", escapedName, escapedValue); Para vxmlsGetMenu es necesario un bucle para añadir todos los argumentos. strcpy(escapedMenuName, menuName); escapedEncoding(escapedMenuName); /* Get the request */ sprintf(request, VXMLS_PREFIX "get_menu?menu=%s", escapedMenuName); if (arguments != NULL) { /* Append the parametters */ for (i = 0; i < numArrayElements(arguments); i++) { name = (char *) getArrayData(arguments, i++); value = (char *) getArrayData(arguments, i); /* Encode strings */ strcpy(escapedName, name); escapedEncoding(escapedName); strcpy(escapedValue, value); escapedEncoding(value); /* Append to the request */ sprintf(request, "%s&%s=%s", request, escapedName, 136 Capı́tulo 5. Implementación de DFBCIn escapedValue); } } 2. Hacer una llamada a httpProxy con la petición generada: fileDes = httpProxy(request); 3. Utilizar el parser adecuado para interpretar la respuesta de VXMLS: Para vxmlsGetMenu se utilizará la función parseMenu y se copiarán los valores con los que fue obtenido el menú: menu = parseMenu(fileDes); close(fileDes); strcpy(menu -> name, menuName); menu -> arguments = arguments; addArrayReference(arguments); return menu; El resto de funciones utilizarán parseVxmlsResponse y comprobarán que el resultado devuelto por VXMLS es mensaje de confirmación. En caso de fallo en la comunicación también se obtendrá un mensaje de error, como se explicó antes, el hilo que se encarga de obtener la respuesta escribe un mensaje de error por sı́ mismo si no consigue contactar correctamente con VXMLS. if (parseVxmlsResponse(fileDes) != NO_ERROR) { setErrorMessage(getVxmlsErrorMessage()); DFBCIN_FATAL(VXMLS_ERROR); } close(fileDes); 5.12. Implementación del Módulo gráfico 5.12.1. Introducción El módulo gráfico se implementará como una biblioteca dinámica para permitir que se puedan intercambiar distintos módulos de este tipo sin necesidad de recompilar la aplicación. Implementación del Módulo gráfico 137 El API que debe implementar la biblioteca para poder ser utilizada como módulo gráfico se puede ver en el apéndice B. En las siguientes secciones se explicarán los pasos que se han seguido en la implementación del módulo gráfico con la ayuda DirectFB. También se ha hecho otra implementación para algunas pruebas que utiliza la consola de texto en lugar del hardware gráfico. 5.12.2. Estructuración del código La implementación de la fachada es idéntica para los dos módulos gráficos implementados, por lo que el código se organizará en tres directorios, un directorio Common en el que se almacenará el fichero graphics.c y las cabeceras interface-draw.h, video.h, graphics-init.h y event-catcher.h que definen las diferentes clases utilidad que componen el módulo gráfico. Las cabeceras anteriores serán implementadas con el correspondiente fichero .c en los directorios DFB para el módulo gráfico completo y Character para el módulo gráfico de pruebas. 5.12.3. La fachada graphics.c En el fichero graphics.c se implementan todas las funciones definidas por el API del módulo gráfico. Excepto las funciones de creación y destrucción, el resto delegan directamente en la clase correspondiente: InterfaceDraw para las operaciones de dibujo, EventCatcher para el método testInput y Video para las operaciones de control de vı́deo. La función graphicsInit llamará a la funciones de inicialización de los subsistemas que componen este módulo. void graphicsInit(int argc, char **argv) { specificInit(argc, argv); interfaceDrawInit(); videoInit(); } specificInit se define en el fichero graphicsInit.c de cada uno de los dos módulos gráficos implementados, para permitir realizar operaciones especı́ficas de inicialización. graphicsDeinit llama a las funciones de destrucción de los subsistemas inicializados por graphicsInit. 138 Capı́tulo 5. Implementación de DFBCIn void graphicsDeinit(void) { interfaceDrawDeinit(); videoDeinit(); specificDeinit(); } specificDeinit tiene el mismo cometido que specificInit: permitir operaciones especı́ficas de destrucción. 5.12.4. La inicialización de DirectFB Para inicializar el módulo gráfico que utiliza DirectFB, specificInit deberá: 1. Inicializar DirectFB llamando a la función DirectFBInit. 2. Crear la interfaz primaria llamando a DirectFBCreate. 3. Utilizando la interfaz primaria, crear la superficie primaria y el buffer de eventos de entrada. La función specificDeinit deberá liberar los recursos creados durante la inicialización por specificInit. 5.12.5. Control de eventos de entrada En el fichero event-catcher.c se implementa la función testInput que deberá detener la ejecución hasta que se detecte que el usuario ha pulsado una de las teclas soportadas (todas las del mando a distancia y alguna del teclado para facilitar las pruebas). El control de entrada utilizando DirectFB se puede realizar mediante dos métodos: con o sin buffer de eventos. Para la este proyecto se utilizará el control de entrada utilizando buffer de eventos, de esta forma no es necesario implementar mecanismos adicionales para recordar los eventos que aún no fueron atendidos. Para esta aplicación sólo interesan los eventos de entrada (en un buffer de eventos genérico de DirectFB también se almacenan otro tipo de eventos, por ejemplo eventos relacionados con la gestión de ventanas), estos eventos se pueden manejar utilizando un tipo especial de buffer de eventos creado con la función CreateInputEventBuffer de la interfaz principal. Implementación del Módulo gráfico 139 Un buffer de eventos de entrada de DirectFB puede detectar varios tipos de eventos: Eventos de teclas. Eventos de ejes. Eventos de botones. El buffer de eventos de entrada utilizado sólo deberá preocuparse de los eventos de teclas, que pueden ser o bien eventos de pulsación de tecla DIET KEYPRESS, o bien eventos de liberación de tecla DIET KEYRELEASE. Para controlar el mando a distancia se utilizará la herramienta LIRC [46]. Esta herramienta está soportada directamente por DirectFB, de forma que los eventos detectados por LIRC generarán eventos de pulsación y liberación de teclas en las aplicaciones que utilizan DirectFB. Para ello, en el fichero de configuración de LIRC se deben nombrar los códigos reconocidos para cada tecla del mando a distancia con un nombre equivalente a alguna de las teclas definidas en el fichero directfb-keynames.h sin el prefijo DIKS . Por ejemplo, para la tecla “play” del mando a distancia, se añade la lı́nea PLAY 0x000000000032CCB3 en el fichero lircd.conf para asociar la tecla PLAY al código generado con la pulsación de “play” en el mando a distancia. En las aplicaciones DirectFB se detectarán eventos DIKS PLAY para esa tecla. Un problema que presenta el uso del mando a distancia es el hecho de que éste no genera un único evento DIET KEYPRESS cuando se pulsa la tecla y un evento DIET KEYRELEASE cuando se suelta la tecla, sino que está generando eventos como si se pulsara la tecla intermitentemente. Esto provoca que el control de la interfaz se haga incómodo, ya que ante una pulsación de una tecla puede comportarse como si se pulsara varias veces la misma tecla. Para solucionar este problema, se descartarán los eventos que se repitan en un determinado intervalo de tiempo. Utilizando el buffer de eventos proporcionado por DirectFB, la implementación de la función testInput es sencilla: 1. Llamar a la función waitForEvent del buffer de eventos para detener la ejecución hasta que haya un evento disponible. 2. Obtener el evento detectado llamando a la función getEvent del buffer de eventos. 140 Capı́tulo 5. Implementación de DFBCIn 3. Comprobar si el evento detectado es relevante para la aplicación: a) Comprobar que se trata de la pulsación de una tecla. b) Comprobar que la tecla pulsada es alguna de las soportadas por la aplicación. c) Obtener el valor InputKey utilizado por la aplicación para identificar a esa tecla. Para esto se utilizará una tabla en la que se relacionan los eventos DFBInputEvent con las teclas InputKey. 4. Filtrar el caso de que un mismo evento se repita en un pequeño intervalo de tiempo. 5. En caso de que se haya obtenido un evento válido se devuelve, en otro caso se vuelve al paso uno. 5.12.6. Impresión de menús y texto DirectFB (ver la sección 2.4) define un tipo de interfaz IDirectFBSurface que representa una superficie en la que realizar operaciones gráficas tales como copiar el contenido de otras superficies, dibujar rectángulos o lı́neas, imprimir texto, etc. La superficie mostrada en pantalla es la conocida como superficie primaria. Las superficies pueden dotarse de un sistema de doble buffer, las operaciones ejecutadas sólo se harán visibles cuando se ejecute la operación Flip de la superficie, que vuelve visible el buffer invisible (back buffer ) e invisible el buffer visible (front buffer ). Esto es útil en la superficie primaria, porque de esta forma se puede sincronizar la operación Flip con el refresco del monitor evitando los efectos desagradables que surgen si se cambia el contenido de la memoria de vı́deo sin tener en cuenta este factor. InterfaceDraw proporciona operaciones para cargar y descargar un menú, cargar y descargar una serie de lı́neas de texto y modificar la forma en la que se dibujan los menús (si se muestra o no un marco rodeándolos). La función interfaceDrawFlipBuffer dibujará los elementos cargados en caso de que los haya. En la inicialización de este módulo se cargarán las fuentes e imágenes que se van a utilizar para generar la interfaz con el usuario. DirectFB define la interfaz IDirectFBFont para representar una fuente, las imágenes se almacenan en superficies. Para cargar los elementos utilizará la interfaz IDirectFBImageProvider para Implementación del Módulo gráfico 141 leer los ficheros de imagen y la función CreateFont de la interfaz primaria para crear las fuentes dado un tamaño y un fichero True Type. También se creará una variable de estado en la que almacenar el menú y el texto cargados y una bandera para indicar si se debe o no dibujar un marco rodeando al menú. La función de destrucción deberá liberar todos los recursos anteriormente creados por la función de inicialización. La función drawMenu bloqueará el menú cargado para asegurar que no se modificará la opción actualmente seleccionada durante el proceso de representarlo gráficamente. Para dibujar el menú se escribirá el campo header a modo de tı́tulo, las opciones se dibujarán en cinco lı́neas. En caso de que haya más de cinco opciones, se dibujará la opción actualmente seleccionada en la lı́nea del centro, las opción siguiente y la anterior en la cuarta y segunda lı́nea y unas flechas arriba y abajo en la primera y quinta lı́nea. Dibujar una opción supone escribir su campo text. La opción actualmente seleccionada se dibujará en color amarillo y el resto en color azul. La función drawText simplemente escribirá las lı́neas de texto cargadas. Para centralizar el control sobre la superficie primaria, será InterfaceDraw la única encargada de ejecutar la operación Flip sobre la misma. Para ello se implementa la función interfaceDrawFlipBuffer, que ejecuta las siguientes operaciones: 1. En caso de que su argumento showBackground sea distinto de 0 copiará la superficie con la imagen de fondo en el buffer invisible. 2. Si hay un menú cargado, dibujará dicho menú en el buffer invisible. 3. Si hay texto cargado, imprimirá dicho texto en el buffer invisible. 4. Por último ejecutará la operación Flip sobre la superficie primaria durante el próximo barrido vertical: DFBCHECK(primary -> Flip(primary, NULL, DSFLIP_WAITFORSYNC)); 5.12.7. Reproducción de vı́deo DirectFB es capaz de reproducir distintos formatos de vı́deo utilizando su sistema de proveedores (ver la sección 2.4). Los vı́deos se cargan creando una interfaz IDirectFBVideoProvider, utilizando la función CreateVideoProvider de la 142 Capı́tulo 5. Implementación de DFBCIn interfaz principal. DirectFB detecta el formato de vı́deo y carga la implementación necesaria de proveedor de vı́deo. En las pruebas se comparó la reproducción de vı́deos en formato MPEG con la reproducción de vı́deos en formato AVI, obteniéndose mejores resultados en el segundo caso. El proveedor de vı́deo utilizado para reproducir ficheros AVI usa la biblioteca avifile [47]. La versión proporcionada con DirectFB estaba implementada sobre una versión antigua de avifile y presentaba ciertos problemas que serán comentados posteriormente, pero que fueron solucionados, en parte, adaptando la implementación del proveedor para que utilizará una versión más moderna de avifile. El procedimiento que se sigue para la carga y reproducción de un vı́deo es el siguiente: Con una llamada a la función videoLoad se carga un fichero de vı́deo de forma que quede listo para ser reproducido. El proceso de carga de un vı́deo es el siguiente: 1. Crear el proveedor de vı́deo sobre el fichero de vı́deo a reproducir. 2. Obtener la información sobre la resolución del vı́deo a reproducir y utilizarla para crear una superficie en la que reproducirlo. 3. Crear un rectángulo del máximo tamaño posible (respetando la relación ancho/largo del vı́deo) en el que se va a copiar cada frame en la superficie primaria. La función videoPlay iniciará la reproducción del vı́deo en la superficie creada por videoLoad. Para ello utilizará la función PlayTo del proveedor de vı́deo creado por videoLoad: videoProvider -> PlayTo(videoProvider, videoSurface, NULL, videoCallback, NULL); El proveedor llamará a la función videoCallback cada vez que esté disponible un nuevo frame. Esta función será utilizada para llamar a la función instalada por videoSetNewFrameCallback. La función videoStop detendrá la reproducción de vı́deo utilizando la función Stop del proveedor de vı́deo. Implementación del Módulo gráfico 143 La función videoBlit copia el frame actual en el buffer no visible de la superficie primaria (ver la sección 5.12.6). Esta función será llamada por Interface cada vez que se notifique que hay un nuevo frame. La función videoUnload elimina el proveedor de vı́deo y libera la superficie utilizada para reproducir el vı́deo. Las funciones para obtener la posición actual y para mover la reproducción a una determinada posición utilizan las funciones que proporciona el proveedor de vı́deo: GetPos y SeekTo. 5.12.7.1. Detección del final de la reproducción La interfaz IDirectFBVideoProvider no tiene ninguna función que permita detectar el final del vı́deo directamente, sin embargo, en la definición de la acción VideoPlay, se especifica un array de acciones que se deberán ejecutar al terminar el vı́deo. Por lo tanto, es necesario detectar el final del vı́deo de alguna forma y ejecutar la función instalada por videoSetVideoEndCallback. Para ello, es necesario crear un nuevo hilo de ejecución que monitorice el estado del proveedor de vı́deo durante la reproducción. La función de inicialización creará dicho hilo (observerThread) y una variable de estado para comunicarse con él de forma similar a la explicada para Interface e InterfaceInput en las secciones 5.10.2 y 5.10.3. La primera opción que se barajó fue la de comprobar cada cierto tiempo que el valor devuelto por la función GetLength y la función GetPos del proveedor de vı́deo no era el mismo, pero se comprobó que la función GetPos llamada al final del vı́deo no siempre devolvı́a la última posición. La solución que se adoptó definitivamente fue comprobar cada cierto tiempo que el valor devuelto por GetPos habı́a cambiado, de no ser ası́, se puede asumir que el vı́deo está detenido en el final. Esta solución obliga a disponer de una bandera VST PLAY que se active sólo cuando el vı́deo se está reproduciendo para evitar que el hilo detecte el final del vı́deo cuando lo que realmente está sucediendo es que el vı́deo está detenido debido a una llamada a videoStop. También se necesita otra bandera VST SEEK que se activará en caso de que se ejecute la función videoGotoVideoPosition, para advertir que no se tenga en cuenta la próxima comparación ya que podrı́a darse la casualidad de que se ejecutara sobre la misma posición. El ciclo de ejecución del hilo observerThread será como sigue: 144 Capı́tulo 5. Implementación de DFBCIn 1. Esperar N segundos. 2. Bloquear la variable de estado. 3. Si está activa la bandera VST SEEK desactivarla y guardar la posición actual en el campo lastFrame del estado. 4. Si está activa la bandera VST PLAY: Si el valor devuelto por GetPos es igual a lastFrame entonces se desactiva VST PLAY y se llama a la función instalada para avisar del final del vı́deo por videoSetVideoEndCallback. En otro caso, poner el valor devuelto por GetPos en lastFrame. 5. desbloquear la variable de estado y volver a 1. Para que este hilo funcione correctamente es necesario modificar algunas de las funciones de video.h (las operaciones sobre la variable de estado se harán siempre bloqueando el semáforo primero): videoPlay debe activar la bandera VST PLAY. videoStop debe desactivar la bandera VST PLAY. videoGotoVideoPosition debe activar la bandera VST SEEK en caso de que se salte a una posición anterior. 5.12.8. Módulo de pruebas La implementación del módulo gráfico de pruebas consiste simplemente en los ficheros: event-catcher.c: Se hace un control de la entrada utilizando las funciones de la biblioteca stdio. video.h: Lo único que hacen todas las operaciones es escribir un mensaje en la consola indicando que fueron llamadas. graphics-init.c: Las implementaciones de las funciones specificInit y specificDeinit son vacı́as. interface-draw.c: Tiene un comportamiento similar al de su homólogo en la implementación “seria”. Las funciones interfaceDrawBlitBackground y intefaceDrawSetBox sólo muestran mensajes para indicar que fueron ejecutadas, los menús se dibujan con cadenas de texto como, por ejemplo: Etapas de codificación 145 --------------"Menu Principal" --------------*(0) Axustes (1) Pelı́culas Las lı́neas de texto, simplemente, se muestran en la consola. 5.13. Sistema de propiedades El sistema de propiedades lee el contenido de un fichero local y lo almacena en un array que contiene datos del tipo Property. El tipo Property apunta a una estructura con dos campos: name: Nombre de la propiedad. value: Valor de la propiedad. Las funciones getProperty y setProperty se implementan asegurando la sincronización mediante la implementación de un sistema de lectores-escritores. Varios hilos pueden estar leyendo una misma propiedad concurrentemente, pero un hilo puede modificar una propiedad sólo en caso de que no haya ningún otro hilo accediendo a la misma. Esto fue utilizado durante algunas etapas del desarrollo, pero en el sistema final el sistema de propiedades sólo se utiliza para almacenar datos estáticos, las propiedades cambiantes serán controladas por VXMLS. 5.14. Etapas de codificación El desarrollo final se consiguió después de pasar por los siguientes incrementos. 5.14.1. Desarrollo del núcleo de la aplicación En este primer incremento se pretende obtener un sistema con la funcionalidad mı́nima. Para ello se desarrollan los siguientes módulos: Las acciones NextMenu y PreviousMenu, para permitir la navegación por el sistema de menús. 146 Capı́tulo 5. Implementación de DFBCIn El parser de menús. En este incremento el parser será capaz de interpretar las etiquetas header, version, option, action, previousMenu y nextMenu. El control de entrada sólo reconoce la pulsación de las teclas arriba, abajo y SEL. Por ahora no se utiliza el mando a distancia. Como aún no se dispone de las acciones BindKey ni ChangeOption, Interface se encargará de cambiar la opción seleccionada cuando se detecte la pulsación de las teclas arriba o abajo y de mandar ejecutar las acciones de la opción seleccionada en cuanto se detecte la pulsación de ENTER. En este incremento InterfaceDraw sólo será capaz de mostrar menús. No hay módulo VXMLS, la descripción de los menús se lee de ficheros locales, que se pasan directamente al parser. Tras esto se obtiene una aplicación que muestra una serie de menús gráficos descritos por ficheros XML, permitiendo navegar por ellos a través de las opciones mostradas, utilizando las teclas arriba, abajo y SEL. 5.14.2. Reproducción básica de vı́deo En este iteración se añadirán las acciones que permitan iniciar la reproducción de un vı́deo. Implementación de las acciones PlayVideo, StopVideo, ResumeVideo y UnloadVideo. Por ahora la acción PlayVideo no contempla las acciones de finalización. Modificación del parser para que puedea interpretar las etiquetas que definen estas nuevas acciones. Implemetación de Video en el módulo gráfico. Por ahora no es necesario detectar la finalización del vı́deo. En esta iteración se obtiene una aplicación que ya cumple con los requisitos mı́nimos que se exigen en la especificación. Muestra una interfaz definida por ficheros XML y es capaz de reproducir ficheros de vı́deo. Etapas de codificación 5.14.3. 147 Pruebas en el Set Top Box Hasta este paso se hizo el desarrollo en un PC. En esta iteración se harán las pruebas en el Set Top Box y se desarrollará el control del mando a distancia. Pruebas de rendimiento el el Set Top Box, en este paso se realizaron las optimizaciones explicadas en el apartado 7.3.1. Instalación de LIRC y comprobación de su funcionamiento con la aplicación. Por ahora, las únicas teclas utilizables siguen siendo arriba, abajo y ENTER. Tras esto ya se tiene el sistema completo, funcionando de forma básica en la televisión y con el mando a distancia como sistema de entrada. 5.14.4. Varias mejoras En esta iteración se terminará de desarrollar el sistema de entrada, se incorporará la temporización, las acciones de personalización y se completará la implementación del sistema de vı́deo. Para completar el sistema de entrada: Implementación completa de testInput para que sea capaz de detectar la pulsación de todas las teclas del mando a distancia. Implementación de la acción BindKey para permitir la asociación de teclas con eventos. Implementación del mecanismo de inicialización de menús, de forma que cuando se cargue un menú en Interface se ejecuten las acciones del campo initActions de dicho menú. Este campo se hace imprescindible, ya que ahora por defecto las teclas están vinculadas al evento NULL. Implementación de la acción ChangeOption, ya que ahora ya se puede asociar a una tecla. Modificación del parser para que sea capaz de interpretar la descripción de estas nuevas caracterı́sticas. Para implementar la temporización se debe modificar el método waitForActions de Interface para que devuelva las acciones del campo timeActions del menú actual en caso de que finalice la temporización. También se deberá modificar el parser para que pueda interpretar el campo timer de los documentos XML. 148 Capı́tulo 5. Implementación de DFBCIn Para implementar la posibilidad de personalización se crea una nueva acción SetProperty, que, por ahora, posibilitará definir y modificar los valores para ciertas propiedades, como por ejemplo definir el lenguaje a utilizar. Esta acción trabajará sobre el sistema de propiedades local descrito en 5.13. En el sistema de vı́deo se añade el control de finalización, para lo que es necesario modificar también la acción PlayVideo y el parser para que interprete el contenido de la etiqueta loadVideo como el conjunto de acciones a ejecutar en caso de que finalice la reproducción. También se añade una nueva acción SeekVideo para permitir reposicionar la reproducción. De nuevo, es necesario modificar el parser para que pueda interpretar las etiquetas que definen esta acción. 5.14.5. Comunicación HTTP En esta iteración se implementará el sistema de comunicación HTTP, pero aún no se utilizará con VXMLS, ya que no está desarrollado todavı́a, sino que obtendrá los ficheros de un servidor HTTP estático. Una vez finalizada esta iteración ya se tendrá un sistema capaz de mostrar una interfaz definida remotamente y de reproducir vı́deo, interactuando con el usuario a través de una televisión y un mando a distancia. En este punto se detiene temporalmente el desarrollo de DFBCIn y se comienza a implementar VXMLS. 5.14.6. Integración con VXMLS En este incremento se completa el desarrollo el módulo VXMLS para que acceda a las URLs del mismo para solicitar los menús o cambiar las propiedades de personalización. Para esto es necesario modificar también las acciones ChangeProperty y NextMenu. Al utilizar VXMLS como servidor se implementan dos nuevas caracterı́sticas: los parámetros para solicitar menús y la selección de lenguaje. La selección de lenguaje consiste simplemente en modificar la propiedad de personalización language utilizando una acción SetProperty, pero para poder definir correctamente la interfaz, es necesario implementar una nueva acción Re- Etapas de codificación 149 loadMenu para permitir volver a solicitar el menú principal en el nuevo idioma. Para facilitar la generación de menús, VXMLS acepta parámetros cuando se le solicita un determinado menú. Para implementar esta caracterı́stica es necesario modificar la acción NextMenu. Será también necesario modificar el parser para que pueda interpretar la descripción de la acción ReloadMenu y los parámetros que ahora se incluyen en las descripciones de las acciones NextMenu. 5.14.7. Integración con VoDKA El sistema de vı́deo utilizado hasta ahora es capaz de reproducir vı́deos desde un fichero local, pero el Set Top Box desarrollado debe reproducir vı́deo obtenido de un servidor de streaming. Es necesario, por tanto, utilizar un mecanismo que permita a DFBCIn acceder a este tipo de medios. En [37] se desarrolla un sistema de ficheros virtual que permite a los reproductores un acceso sencillo a los medios obtenidos mediante la descarga de streaming. Utilizando este sistema no es necesario realizar ninguna modificación a la aplicación DFBCIn, simplemente se debe montar un sistema de ficheros virtual y acceder a los medios disponibles de la misma forma que se accedı́a a los ficheros locales. 5.14.8. Reproductor Mp3 y acciones de texto Finalmente se añaden las acciones LoadMp3, PauseContinueMp3, KillMp3Player, LoadText y UnloadText. Para ello es necesario implementar la clase Mp3Player y llevar a cabo pequeñas modificaciones en la librerı́a gráfica. Como en los casos anteriores, se deberá modificar el parser de menús para que pueda interpretar las nuevas etiquetas y el servidor VXMLS para que pueda generarlas. Capı́tulo 6 Implementación de VXMLS Índice General 6.1. Introducción . . . . . . . . . . . . . . . . . . . . . . . . 152 6.1.1. Generalidades sobre Erlang/OTP . . . . . . . . . . . . 152 6.1.2. ¿Cómo funciona Inets? . . . . . . . . . . . . . . . . . . 152 6.1.3. ¿Cómo funciona Mnesia? . . . . . . . . . . . . . . . . 153 6.1.4. Servidores genéricos . . . . . . . . . . . . . . . . . . . 154 6.1.5. Estructura de la aplicación VXMLS . . . . . . . . . . 154 6.1.6. Tipos de datos . . . . . . . . . . . . . . . . . . . . . . 155 6.1.7. Control de errores . . . . . . . . . . . . . . . . . . . . 156 6.2. Capa Modelo . . . . . . . . . . . . . . . . . . . . . . . . 157 6.2.1. Tipos de datos . . . . . . . . . . . . . . . . . . . . . . 157 6.2.2. Administración de la base de datos . . . . . . . . . . . 159 6.2.3. Funciones de la fachada . . . . . . . . . . . . . . . . . 159 6.3. Capa Vista . . . . . . . . . . . . . . . . . . . . . . . . . 165 6.4. Capa Controlador . . . . . . . . . . . . . . . . . . . . . 167 6.4.1. Generación de la respuesta HTTP . . . . . . . . . . . 167 6.4.2. Traducción de mensajes . . . . . . . . . . . . . . . . . 167 6.4.3. Fachada . . . . . . . . . . . . . . . . . . . . . . . . . . 168 6.4.4. Creación y destrucción de sesiones . . . . . . . . . . . 168 6.4.5. Propiedades de personalización . . . . . . . . . . . . . 169 6.4.6. Generación de menús . . . . . . . . . . . . . . . . . . 169 6.4.7. Composición de objetos Menu 151 . . . . . . . . . . . . . 171 152 Capı́tulo 6. Implementación de VXMLS 6.4.8. Menús definidos . . . . . . . . . . . . . . . . . . . . . 172 6.4.9. Ejemplos de vxmls get menu . . . . . . . . . . . . . . 173 6.5. Extensibilidad . . . . . . . . . . . . . . . . . . . . . . . 175 6.1. Introducción 6.1.1. Generalidades sobre Erlang/OTP El modelo de programación de Erlang se basa en la creación de procesos que se ejecutan concurrentemente y que se comunican entre ellos mediante paso de mensajes. El código se estructura en módulos, cada uno de los cuales implementa una serie de funciones. Un módulo puede exportar funciones que serán accesibles al resto, las funciones no exportadas son locales y no afectan para nada a los demás módulos. Un proceso Erlang es una unidad de computación aislada, que coexiste concurrentemente con otros procesos en el sistema. Un proceso se crea utilizando la BIF spawn a la que se le pasa el nombre de un módulo, una función de dicho módulo y una lista de argumentos. El proceso creado finalizará cuando termine la ejecución de la función. Estos procesos se ejecutan “dentro” de un nodo Erlang, cada nodo Erlang ocupa un único proceso del sistema operativo. Erlang/OTP [4] también se define el concepto de aplicación. Una aplicación es un paquete de recursos, tales como nombres registrados, módulos y procesos. La aplicación VXMLS, utilizará, a su vez, algunas aplicaciones que forman parte del entorno Erlang/OTP: Inets [39] que es un contenedor de servidores para Internet, Mnesia [40] que es un sistema gestor de bases de datos distribuidas y Mnemosyne [41] que es una interfaz de consulta para Mnesia. 6.1.2. ¿Cómo funciona Inets? Inets incluye una implementación de un servidor web compatible con el protocolo HTTP 1.1 [35]. Para comunicarse con aplicaciones externas el servidor implementa CGI [48] y ESI (Erlang Scripting Interface). ESI proporciona una interfaz eficiente para acceder a funciones Erlang, se recomienda la utilización de ESI sobre la utilización de CGI por razones de eficiencia, ya que CGI necesita que el servidor cree un nuevo proceso de sistema Introducción 153 operativo. ESI imita a CGI pero sin el sobrecoste de crear un nuevo proceso, una URL puede llamar a una función Erlang de acuerdo con la siguiente sintaxis: http://your.server.org/***/Mod[:/]Func(?QueryString|/PathInfo) El nodo Erlang en el que se ejecuta Inets deberá tener acceso a un módulo Mod con una función Func con dos argumentos. Si un cliente accede a la URL anterior se ejecutará Func(Env,Input), donde Env contiene información sobre el cliente e Input la cadena QueryString o PathInfo de acuerdo con la especificación [48], la función deberá devolver una cadena de texto siguiendo esta misma especificación. Para implementar VXMLS se utilizará el sistema ESI, las funciones utilizadas para recibir las llamadas de Inets serán las de la fachada VXMLSFacade (ver la sección 4.6.5). En el fichero de configuración de Inets se debe especificar que URLs se utilizan de interfaz con ESI y a qué módulos pueden acceder. Para VXMLS, estas funciones se definirán en el módulo vxmls, y únicamente se utilizará una URL para acceder éstas. Suponiendo que se estén ejecutando unas pruebas con el servidor en el puerto 8080 de la máquina local: http://localhost:8080/vxmls/Mod/Func?QueryString Donde Mod debe ser vxmls y Func podrá ser alguna de las siguientes: login, logout, get menu o set property. En la sección 4.4 se explican las peticiones que se pueden enviar a VXMLS. 6.1.3. ¿Cómo funciona Mnesia? El acceso a una base de datos Mnesia desde una aplicación Erlang es sencillo, una vez arrancada la aplicación sobre un nodo, se pueden utilizar las funciones exportadas por el módulo mnesia para manipular las tablas. Para realizar consultas complejas se debe utilizar Mnemosyne que permite escribir consultas embebidas en el código Erlang utilizando una construcción llamada query list comprehensions. La sintaxis es la siguiente: query [ <patrón> || <cuerpo> ] end Por ejemplo: 154 Capı́tulo 6. Implementación de VXMLS query [ Empleado || Empleado <- table(empleado), Empleado.departmento = ventas end 6.1.4. ] Servidores genéricos Erlang/OTP define el concepto de comportamiento (behaviour ) como una formalización de patrones de diseño que pueden ser utilizados para programar algunos problemas genéricos. Un módulo que utilice un comportamiento deberá exportar ciertas funciones que que serán llamadas por el sistema mientras el proceso se ejecuta. En Erlang/OTP, una aplicación cliente-servidor puede ser programada utilizando el comportamiento gen server, este comportamiento será utilizado para implementar dos servidores en la aplicación VXMLS, el servidor vxmls que representa a la aplicación en sı́ (cuando termina la ejecución del proceso vxmls termina la ejecución de la aplicación) y el servidor vxmls lang, que mantendrá tablas con los posibles mensajes a mostrar traducidos a los diferentes idiomas soportados. Un servidor genérico se crea con la función start del módulo gen server que crea un proceso con el nombre del servidor y lo mantiene en ejecutándose hasta que se elimina utilizando alguno de los mecanismos establecidos para detener el servidor. 6.1.5. Estructura de la aplicación VXMLS El código de la aplicación VXMLS se estructura en los siguientes módulos: vxmls: Contiene las funciones para inicializar y detener la aplicación (start y stop) y las funciones que atenderán las peticiones de Inets. vxmls lib: En este módulo se agrupan funciones de diversa ı́ndole. vxmls xml : Este módulo exporta funciones para la generación de documentos XML. vxmls menu: Este módulo exporta funciones para la generación de objetos Menu. vxmls db: Exporta las funciones para acceder a la capa modelo de VXMLS y una función para inicializar la base de datos. Introducción 155 vxmls lang: Exporta funciones para obtener mensajes traducidos a diversos idiomas. vxmls conf : Exporta funciones para acceder al fichero de configuración de VXMLS. vxmls properties: Exporta las funciones necesarias para atender a la petición set property. vxmls login: Exporta las funciones necesarias para atender a las peticiones login y logout. vxmls menu dispatcher : Exporta las funciones necesarias para atender a la petición get menu. Además de estos, cada menú es generado por un módulo, esto permite añadir nuevos menús sin necesidad de modificar ni recompilar el código existente. Para generar un menú llamado, por ejemplo, foo menu, será necesario crear un nuevo módulo vxmls foo menu. Como norma general, todos los módulos creados con este objetivo tendrán un nombre de forma vxmls *** menu. 6.1.6. Tipos de datos Para representar los objetos necesarios en Erlang se siguieron distintas aproximaciones: Los objetos valor [44] se representan definiendo un registro con los campos necesarios. Para ocultar la estructura interna del tipo se definirán las funciones necesarias para acceder a sus campos en un único módulo. Por ejemplo: la clase Menu 1 se implementa definiendo un registro menu y el módulo vxmls menu con las funciones para manipularlo. Las clases utilidad se representan con un módulo que exporte las funciones requeridas. Los objetos Singleton se representan utilizando el comportamiento Erlang gen server. 1 en la forma en que se utiliza en VXMLS, ver sección 4.6.2 156 Capı́tulo 6. Implementación de VXMLS 6.1.7. Control de errores La filosofı́a de control de errores de Erlang consiste en asumir siempre que los argumentos recibidos por una función son correctos, es decir, no hacer control de errores de forma explı́cita. Si en algún momento una función llega a una situación inesperada, el proceso que ejecuta esta función debe morir. Se pueden establecer jerarquı́as de procesos en las que procesos supervisores monitoricen la ejecución de procesos trabajadores, cuando un trabajador muere debido a un error, un supervisor detecta el fallo y actúa en consecuencia para corregir el error. En caso de que se vaya a utilizar una función susceptible de provocar un error, pero que no necesariamente debe provocar la muerte del proceso, el código se puede ejecutar dentro de un bloque catch que devolverá la tupla {’EXIT’, Razon} en caso de que el código que contiene alcance algún punto en el que el proceso finalizarı́a en circunstancias normales. En la aplicación desarrollada los procesos pueden morir debido a diversas causas, identificadas por los siguientes valores de Razon: {open, Atom, File}: Error al abrir un fichero. Atom identifica la causa del error y File es el nombre del fichero. {format, Reason, File}: Un fichero leı́do no tiene el formato esperado. Reason contiene una descripción del error y File es el nombre del fichero. {not found, Key}: No se ha encontrado un valor almacenado para la clave Key. bad request: Se ha recibido una petición HTTP no válida. incorrect password: La clave del Set Top Box no es correcta. cookie not valid: La cookie asociada al Set Top Box no es válida. not logged in: El Set Top Box no tiene asociada una sesión. no home argument: La aplicación se ha arrancado sin el argumento home, necesario para acceder a algunos ficheros. {unknown item, {Name, Value}}: Se ha proporcionado un valor desconocido o una propiedad desconocida. Name indica el nombre de la propiedad y Value el valor. Capa Modelo 157 {database error, Reason}: Error alguna operación realizada sobre la base de datos. Reason puede tomar alguno de los siguientes valores: • {not found, Key}: No se encontró el registro con la clave Key. • {reference constraint, Table, Key}: La clave Key utilizada como referencia a un registro de la tabla Table no está presente en ésta. • {already exists, Key}: Ya existe un objeto con clave Key. • user not found: El usuario no se ha encontrado. Cualquier otro error será considerado desconocido a la hora de notificarlo. 6.2. Capa Modelo 6.2.1. Tipos de datos Las funciones utilizadas para insertar datos en Mnesia utilizan registros para representar las tuplas de las tablas. Esto implica que en una tabla de la base de datos se puede almacenar cualquier tipo de dato Erlang. Normalmente sólo se almacenarán cadenas de texto o átomos, pero en algunos casos también se utilizarán tuplas para representar claves múltiples En la mayorı́a de los casos un objeto del dominio se relacionará con una tabla de la base de datos, excepto la tabla shown movie que no representa ningún objeto, sino la relación N a N entre user y movie y el objeto TranslatedMovie, que no representa ninguna tabla de la base de datos. En el fichero vxmls tables.hrl se definen los registros que se utilizarán para almacenar los datos sobre los diferentes objetos del dominio y las tablas de la base de datos: set top box: Contiene los campos necesarios para identificar a un Set Top Box cliente: • id: Cadena de texto que identifica unı́vocamente al cliente. • passwd: Contiene la clave encriptada. • cookie: Referencia a la cookie que identifica la sesión creada para este Set Top Box. • user: Referencia al usuario activo para este Set Top Box cookie: Representa a una cookie utilizada para identificar una sesión. 158 Capı́tulo 6. Implementación de VXMLS • string: La cadena de texto que define la cookie. • id: Referencia al Set Top Box para el que esta cookie define una sesión. language: Representa un idioma. • id: Identificador ISO del idioma. • name: Nombre del idioma. movie: Representa una pelı́cula. • id: Átomo utilizado para identificar a la pelı́cula. • file: Fichero que contiene el vı́deo. • genre id: Referencia al género de la pelı́cula. • year: Año en el que fue publicada. • new: Si es true indica que la pelı́cula es un estreno. movie info: Contiene datos textuales sobre una determinada pelı́cula que deben ser traducidos a los diferentes idiomas soportados. • id: Clave compuesta por una referencia a la pelı́cula y otra al idioma. • synopsys: Lista de cadenas de texto con un resumen de la pelı́cula. • title: Tı́tulo de la pelı́cula. genre: Representa un género. • id: Clave compuesta por un átomo que identifica al género y una referencia a un idioma. • name: Nombre traducido del género. user: Información sobre un usuario de un determinado Set Top Box. • id: Clave compuesta por el nombre del usuario y una referencia al Set Top Box. • lang id: Referencia al idioma seleccionado por el usuario. shown movie: Representa la tabla utilizada para almacenar la relación entre un usuario y las pelı́culas que ha visto. • id: Clave compuesta por una referencia al usuario y una referencia al Set Top Box. • movie id: Referencia a la pelı́cula. Capa Modelo 159 translated movie: Agrupa información sobre una determinada pelı́cula traducida a un cierto idioma. No representa a ninguna tabla de la base de datos. • id: El átomo identificador de la pelı́cula. • file: El fichero en el que se encuentra el vı́deo. • genre id: Referencia al género de la pelı́cula. • year: Año en el que fue publicada. • synopsys: Resumen. • title: Tı́tulo. 6.2.2. Administración de la base de datos Como no se ha desarrollado ninguna herramienta de administración, en el módulo vxmls db se exporta una función init que creará todas las tablas necesarias e introducirá los datos indicados por un fichero de configuración. El fichero de configuración es un fichero de texto en el que se escriben términos Erlang separados por puntos. El formato utilizado para esta aplicación es el siguiente: {NombreDeTabla1, ListaDeTuplas1}. {NombreDeTabla2, ListaDeTuplas2}. ... Utilizando la función read del módulo vxmls conf se obtiene una lista con todas las tuplas que definen el contenido de las diferentes tablas. Tras esto se almacenarán esas tuplas en las tablas recién creadas. 6.2.3. Funciones de la fachada 6.2.3.1. Acceso a datos Las funciones que permiten el acceso a los datos del modelo están implementadas en el módulo vxmls db. La mayorı́a de ellas utilizarán el API de Mnesia para manipular los datos persistentes almacenados en la base de datos, pero también se exportan las funciones que permiten el acceso a los datos almacenados en los objetos valor que devuelven algunas operaciones. De esta forma se aisla la representación interna de dichos objetos valor del resto de módulos. Las funciones exportadas se pueden clasificar en cuatro categorı́as: 160 Capı́tulo 6. Implementación de VXMLS Funciones de acceso a datos: Devuelven datos almacenados en la base de datos. Funciones de edición datos: Modifican el valor de un campo de alguna tabla de la base de datos. Funciones de inserción de datos: Añaden nuevas tuplas a la base de datos. Funciones de acceso a los campos de un objeto valor : Devuelven el valor de un atributo de una variable de alguno de los tipos definidos internamente para agrupar información relacionada. Una función que se escapa de esta clasificación es la función set property, que se comporta de forma diferente según la propiedad a modificar: user: Cambia el usuario referenciado en un Set Top Box. language: Cambia el idioma referenciado en un usuario. shown movie: Añade un nuevo registro a la tabla shown movie en caso de que dicho registro aún no exista. 6.2.3.2. Transacciones El procedimiento general para ejecutar una transacción en Mnesia es el siguiente: 1. Crear una función de nivel superior que ejecute las operaciones que conformen la transacción. Por ejemplo, para obtener el registro correspondiente al usuario activo en un Set Top Box dado el identificador del Set Top Box : F = fun () -> [SetTopBox] = mnesia:read({set_top_box, SetTopBoxId}), [User] = mnesia:read({user, {SetTopBoxId, SetTopBox#set_top_box.user}}), User end 2. Ejecutar la función transaction del módulo mnesia que toma como argumento una función de nivel superior y la ejecuta de forma atómica dentro de una transacción. En caso de que se produzca algún error aborta la transacción y ejecuta un rollback. Esta función puede devolver: Capa Modelo 161 {atomic, Result}: En caso de éxito Mnesia devuelve en Result el resultado devuelto por la función ejecutada por transaction. {aborted, Reason}: En caso de error Mnesia devuelve esta tupla. Reason representa la razón del error. Para ejecutar las transacciones sobre Mnesia de acuerdo con la filosofı́a adoptada para el control de errores en VXMLS se define mnesia transaction, que toma como argumento la función de nivel superior que define la transacción y comprueba que el resultado es correcto, en caso de que transaction devuelva {aborted, Reason} el proceso termina con el error {database error, Reason}, si devuelve {atomic, Result} devuelve Result. Una transacción puede ser abortada explı́citamente ejecutando la función abort del módulo mnesia a la que se le pasa un valor que será la razón devuelta por transaction. 6.2.3.3. Funciones de acceso a datos Este tipo de funciones buscarán un dato en una tabla siguiendo un determinado criterio. Las búsquedas en las tablas de Mnesia se pueden hacer de varias formas: Si se conoce la clave primaria del objeto que se quiere buscar se debe utilizar la función read del módulo mnesia, que devuelve una lista con los registros almacenados cuya clave coincida con la clave especificada. Las funciones implementadas de esta forma son: • get active user: Recibe como argumento el identificador de un Set Top Box, obtiene el registro que define dicho Set Top Box y después busca el registro que define el usuario cuya clave está referenciada en el campo user del Set Top Box obtenido. Aborta con la razón user not found si el Set Top Box no tiene un usuario asociado. • get password: Recibe como argumento el identificador de un Set Top Box, lo busca y devuelve el campo passwd del mismo. • get id from cookie: Recibe la cadena que identifica a una cookie, busca el registro que la representa y devuelve el campo id del mismo. Si se quieren buscar los registros de una tabla que cumplan un determinado patrón se usará la función match del módulo Mnesia que devuelve una lista con los registros que cumplen ese patrón. Este tipo de búsqueda lo realizan las funciones: 162 Capı́tulo 6. Implementación de VXMLS • get languages: Devuelve todos los registros almacenados en la tabla language. • get genres: Recibe como argumento un identificador de idioma y devuelve todos los registros que definen los géneros en ese idioma (los registros genre cuyo campo language id coincide con el argumento de la función). Si se necesitan los registros de una tabla que cumplan una determinada condición se puede utilizar la función select del módulo Mnesia. Esta función no fue necesaria para implementar ninguna de las búsquedas utilizadas por VXMLS. Para búsquedas más complejas se usarán consultas Mnemosyne (ver la sección 6.1.3), por ejemplo, para realizar uniones entre tablas. Las funciones implementadas con este método son las siguientes: • get shown movies: Recibe como argumentos un identificador de idioma, un registro de un determinado usuario y un identificador de género. Debe devolver una lista de registros de tipo translated movie con información traducida al idioma correcto sobre las pelı́culas del género especificado que no han sido vistas por el usuario. Para ello debe ejecutar una unión entre las tablas movie, movie info y shown movies y seleccionar de entre estas que se ajusten al idioma y género especificados. La consulta ejecutada es la siguiente: Q = query [{Movie, MovieInfo} || Movie <- table(movie), MovieInfo <- table(movie_info), ShownMovie <- table(shown_movie), Movie.genre_id = GenreId, MovieInfo.id = {Movie.id, LanguageId}, ShownMovie.id = User#user.id, ShownMovie.movie_id = Movie.id] end Las tuplas obtenidas serán procesadas para transformarlas en registros translated movie. • get not shown movies: Recibe los mismos argumentos que la anterior más uno nuevo que indica si se deben seleccionar las novedades o el resto de pelı́culas. En este caso de deben devolver las pelı́culas que no están referenciadas en shown movies para el usuario especificado. Como la función anterior, devuelve una lista de registros translated movie. Capa Modelo 163 • get translated movie: Recibe como argumentos el identificador de una pelı́cula y un identificador de idioma y devuelve la información en un registro translated movie con información de la pelı́cula traducida al idioma especificado, para ello necesita hacer una unión entre las tablas movie y movie info. Q = query [{Movie, MovieInfo} || Movie <- table(movie), MovieInfo <- table(movie_info), Movie.id = MovieId, MovieInfo.id = {MovieId, LanguageId}] end 6.2.3.4. Funciones de edición de datos Para editar un campo de un registro almacenado en una tabla de Mnesia se debe ejecutar una transacción que siga estos pasos: 1. Obtener el registro con los valores viejos, normalmente utilizando la función wread del módulo mnesia, que obtiene un registro dada su clave y hacer que la transacción ejecute un bloqueo de escritura. 2. Crear una variable copiando los datos del registro anteriormente obtenido, excepto los datos que deben ser modificados. 3. Escribir este nuevo registro en la base de datos utilizando la función write del módulo mnesia. Las funciones que realizan este tipo de operación son: set active user: Modifica el usuario activo para un determinado Set Top Box. set cookie: Modifica el campo cookie de un Set Top Box para que señale a una nueva cookie, que también es almacenada en la base de datos por esta función. remove cookie: Modifica el campo cookie de un Set Top Box para indicar que no está asociado a ninguna cookie. También borra de la base de datos la cookie anteriormente referenciada por el Set Top Box. 164 Capı́tulo 6. Implementación de VXMLS 6.2.3.5. Funciones de inserción de datos La inserción de datos se hace utilizando la función write del módulo mnesia dentro de una transacción. En caso de que se intente insertar un registro con una clave que ya esté presente en la base de datos, el nuevo registro suplantará al antiguo. Las funciones que insertan datos son todas aquellas que empiezan por new. La mayorı́a solamente son utilizadas durante la inicialización de la base de datos, los únicos datos que se insertarán durante la ejecución normal de VXMLS son los registros shown movie correspondientes a las pelı́culas que vayan viendo los usuarios y los valores de las cookies utilizadas para identificar las diferentes sesiones. 6.2.3.6. Funciones de acceso a los campos de un objeto valor Las siguientes funciones simplemente acceden al campo correspondiente del registro que se les pasa como argumento y devuelven su valor: get user name: Devuelve el nombre de un usuario. get language id: Devuelve el identificador ISO de un idioma. get language name: Devuelve el nombre de un idioma. get genre name: Devuelve el nombre de un género. get genre id: Devuelve el identificador de un género. get translated movie id: Devuelve el identificador de una pelı́cula traducida. get translated movie title: Devuelve el tı́tulo de una pelı́cula traducida. get translated movie file: Devuelve el fichero en el que se encuentra el vı́deo de una pelı́cula traducida. get translated movie synopsys: Devuelve el resumen de una pelı́cula traducida. Capa Vista 6.3. 165 Capa Vista La vista de la aplicación se implementa en un único módulo vxmls xml, que exporta las funciones menu2xml, error report y ok message. Estas funciones permiten obtener los documentos XML que serán devueltos en las respuestas de VXMLS a las peticiones de los Set Top Boxes clientes. Durante la generación de un documento XML es necesario realizar varias concatenaciones de cadenas de texto (en Erlang una cadena de texto se representa como una lista de caracteres). La concatenación de listas es una operación costosa, ya que requiere que las dos listas involucradas sean recorridas completamente. Para evitar este problema, las cadenas se almacenarán en listas arbitrariamente profundas y se aplanarán al final de todo. De esta forma la función costosa, en este caso aplanar, sólo se ejecutará una vez. Por ejemplo, para concatenar la "hola ", con "mundo" se almacenarán en la lista ["hola ", "mundo"]. Una vez terminada la generación de las cadenas se obtendrá un lista profunda como, por ejemplo ["ejemplo:", ["hola ", "mundo"], [[[["."]]]]] la función flatten del módulo lists aplicada a esta lista devolverá la cadena esperada: "ejemplo:hola mundo.". Para la generación de los documentos XML, se definen las siguientes funciones internas al módulo: render empty tag: Recibe como argumentos el nombre de una etiqueta y, opcionalmente, una lista con pares argumento valor y devuelve una lista que representa a dicha etiqueta, con los argumentos especificados en caso de que los haya. También recibe como argumento una cadena que será añadida al principio de la lista devuelta. Esto es útil para indentar el resultado de forma que sea más legible. > flatten(render_empty_tag("", "etiqueta", [{"atributo", "valor"}])). > "<etiqueta atributo=\"valor\"/>\n" > > flatten(render_empty_tag("", "etiqueta")). > "<etiqueta/>\n" render tag: Es similar a la anterior, pero también recibe un argumento a mayores con el contenido de la etiqueta. 166 Capı́tulo 6. Implementación de VXMLS > flatten(render_tag("", "etiqueta", [{"atributo", "valor"}], "Contenido")). > "<etiqueta atributo=\"valor\">Contenido</etiqueta>\n" > > flatten(render_tag("", "etiqueta", "Contenido")). > "<etiqueta>Contenido</etiqueta>\n" Utilizando estas funciones, un registro menu se transforma en una representación XML utilizando la función render tag para una etiqueta menu cuyo contenido es otro conjunto de etiquetas que describen el menú. Para ejecutar esta función de forma sencilla se permite al módulo vxmls xml acceder a la representación interna de menú. render_menu(Menu) -> render_tag("", "menu", ["\n", render_version(Menu#menu.major_version, Menu#menu.minor_version), render_header(Menu#menu.header), render_timer(Menu#menu.timer), render_init_actions(Menu#menu.init_actions), render_options(Menu#menu.options)]). El resto de funciones render *** se implementan de forma similar. La función xml2menu obtendrá la representación del menú en XML utilizando la función anterior y le añadirá la cabecera XML para devolver el documento completo. Las funciones error report y ok message se pueden implementar utilizando las funciones render tag y render emtpy tag directamente. Por ejemplo, la función error report es como sigue: error_report(Reason) -> [header("vxmls", ?VXMLS_DTD), %% cabecera XML render_tag("", "vxmls", ["\n", render_tag(" ", "error", Reason)])]. Capa Controlador 6.4. Capa Controlador 6.4.1. Generación de la respuesta HTTP 167 Para la generación de los documentos HTTP a devolver se implementa la función http document que recibe como argumentos el contenido del documento y una lista con campos de la cabecera y sus correspondientes valores. Esta función aplanará la lista generada antes de devolverla, por lo que el contenido puede estar representado por una lista arbitrariamente profunda (ver la sección 6.3). También añadirá automáticamente el campo Content-length de acuerdo con el la longitud del contenido. 6.4.2. Traducción de mensajes En el módulo vxmls lang se implementa un servidor genérico (comportamiento gen server ), que almacenará en su estado tablas con pares clave-valor en las que almacenar los mensajes a mostrar en la interfaz. Cada tabla guardará mensajes para un determinado idioma. Para almacenar esta información se utilizan las funciones del módulo ets del entorno Erlang/OTP. Este módulo permite crear tablas similares a las utilizadas en una base de datos, pero de carácter no persistente. Las tablas de los diferentes idiomas se cargan bajo demanda en el momento en el que se solicita un mensaje de un determinado idioma que no fuera sido cargado anteriormente. Los mensajes para cada idioma se describen en un fichero de texto de nombre vxmls lang.xx siendo xx el identificador ISO de idioma. Este fichero debe contener tuplas separadas por puntos de la forma {Clave, Cadena}. Estos valores se almacenarán en una tabla ets de nombre xx vxmls lang indexadas por Clave. Por ejemplo: suponiendo que el fichero vxmls lang.es contiene una lı́nea {main_menu_header, "Menú Principal"}. una solicitud del mensaje a mostrar para main menu header en español devolverá la cadena “Menú Principal”. 168 Capı́tulo 6. Implementación de VXMLS Para ocultar la existencia del proceso servidor al resto de la aplicación, se exporta la función get message, a la que se le pasan el identificador del mensaje y el identificador ISO del idioma en el que se necesita el mensaje, obtiene dicho mensaje del servidor y lo devuelve. 6.4.3. Fachada En el módulo vxmls se definen las funciones que serán llamadas por Inets. Estas funciones deberán capturar los errores en un bloque catch para evitar la muerte del proceso utilizado por Inets para evaluar la función (ver la sección 6.1.7). De esta forma, se puede generar un mensaje de error explicativo utilizando la función error report del módulo vxmls xml. El módulo error manager exporta la función format error a la que se le pasa la razón del error y devuelve una cadena de texto informando de las causas del mismo. 6.4.4. Creación y destrucción de sesiones Las funciones login y logout del módulo vxmls utilizan las funciones con el mismo nombre del módulo vxmls login para crear y destruir respectivamente las sesiones de los clientes. login obtendrá los valores de los argumentos client y passwd pasados en la petición HTTP y comprobará que son válidos usando la función validate client del módulo vxmls lib. Esta función encriptará la clave en claro pasada en el argumento passwd y comprobará que la cadena coincide con la cadena encriptada almacenada en la base de datos (que obtiene utilizando las funciones exportadas por el modelo). En caso de que sea ası́ se considera que el cliente está autorizado y se crea una sesión generando una cookie para el usuario y almacenándola en la base de datos. Después de esto genera una respuesta de confirmación con la función ok message del módulo vxmls xml y devuelve el documento HTTP correspondiente con los campos necesarios para indicarle al cliente que establezca la cookie. Para los usuarios registrados (es decir, para los que se ha creado una sesión), es posible acceder a sus datos utilizando la función validate cookie del módulo vxmls lib, que recibe como argumento la variable Env pasada por Inets (ver la sección 6.1.2), obtiene la cookie de la misma y utiliza las funciones del modelo para obtener el usuario asociado al Set Top Box para el que se creó la cookie. En caso de que la cookie no sea válida, esta función provoca el error not logged in o cookie not valid. Capa Controlador 169 La función logout comprueba la sesión con validate cookie y elimina la cookie del modelo. Después de esto genera un mensaje de confirmación como en el caso anterior y devuelve un documento HTTP con los campos necesarios para indicarle al cliente que elimine la cookie. En caso de que se detecte algún error, ambas funciones devuelven un mensaje de error. case catch ... % otros casos ... {’EXIT’, Reason} -> http_document(error_report(format_error(Reason)) end. 6.4.5. Propiedades de personalización La función set property del módulo vxmls delega su trabajo en la función con el mismo nombre del módulo vxmls properties. Esta función obtendrá el cliente asociado a la sesión utilizando la función validate cookie, el nombre y valor de la propiedad de los argumentos pasados en la petición HTTP y utilizará la función set property del modelo para realizar los cambios oportunos en el mismo. Después de esto devuelve un documento HTTP con un mensaje de confirmación. En caso de error devuelve un documento HTTP con un mensaje de error. Las propiedades que pueden ser definidas están determinadas por el modelo, en caso de que sea necesario añadir nuevas propiedades de personalización este módulo no necesita ser modificado, solamente se deberá ampliar la función set property del módulo vxmls db (ver la sección 6.2.3.1). 6.4.6. Generación de menús La función get menu del módulo vxmls delega en la función con el mismo nombre exportada por el módulo vxmls menu dispatcher. Este módulo se encargará de generar los documentos que representen el menú que se debe mostrar para cada petición, dependiendo del estado del modelo. Para añadir flexibilidad a la definición de la interfaz, el módulo vxmls menu dispatcher no generará los menús solicitados directamente, sino que obtendrá el documento XML de otros módulos que pueden ser cargados dinámicamente. De esta forma se puede variar la definición general de la interfaz sin modificar 170 Capı́tulo 6. Implementación de VXMLS el código existente, incluso sin tener que detener la aplicación. Los pasos que se siguen para obtener la definición XML de un determinado menú son los siguientes: 1. Comprobar que la sesión es válida y obtener el identificador del Set Top Box cliente con la función validate sesion del módulo vxmls lib. 2. Obtener los datos del usuario activo del Set Top Box utilizando las funciones del modelo. 3. Obtener el valor de argumento menu en la URL, y una lista con el resto de pares argumento-valor. 4. Generar el documento XML llamando a la función xml document del módulo asociado al menú solicitado. El nombre del módulo que genera el menú será vxmls nombre, donde nombre es el nombre del menú obtenido en el paso 3. De esta forma, el módulo vxmls menu dispatcher no depende de los nombres de los menús definidos ni de los argumentos que estos reciben. En caso de que se desee añadir un nuevo menú, por ejemplo nuevo menu, se deberá escribir un nuevo módulo vxmls nuevo menu que exporte una función xml document de acuerdo con la siguiente especificación: Recibe cuatro argumentos: ClientId, User, LanguageId y Arguments. • ClientId: Identificador del Set Top Box cliente asociado a la sesión. • User: Describe el usuario activo en el Set Top Box, los valores de sus atributos se pueden obtener con las funciones del modelo (ver la sección 6.2.3.6). En caso del que el Set Top Box no tenga asociado un usuario activo este valor será false. • LanguageId: Identificador ISO del idioma en el que se debe mostrar el menú. El módulo vxmls menu dispatcher pasa el idioma por defecto en este campo en caso de que el Set Top Box no tenga un usuario asociado. • Arguments: Lista de pares nombre-valor con los argumentos con los que se solicita el menú. Deberá devolver una lista arbitrariamente profunda que represente el documento XML que define el menú solicitado. En caso de que se produzca algún error, la función debe causar la finalización del proceso que la ejecuta. Capa Controlador 6.4.7. 171 Composición de objetos Menu El módulo vxmls menu exporta una serie de funciones para crear objetos Menu sin necesidad de conocer la estructura interna de este tipo. compose: Recibe como argumento una lista de componentes y devuelve el objeto Menu correspondiente. Los componentes se puede generar con el resto de funciones exportadas por este módulo. timer: Genera un objeto Timer, recibe como argumentos el tiempo la lista de acciones a ejecutar en caso de que éste expire. action: Genera un objeto Action. Recibe como argumentos el tipo de la acción y los datos que sean necesarios para definirla. option: Genera una opción. Recibe como argumentos el texto a mostrar para la opción y la lista de acciones a ejecutar en caso de que la opción sea seleccionada. default change option actions: Devuelve una lista con las acciones para cambiar la opción seleccionada. default bind key actions: Devuelve una lista con las acciones que vinculan las teclas arriba, abajo, SEL y STOP con las acciones definidas por defecto para la navegación por los menús. default cancel option: Devuelve una opción “Cancelar” genérica, que en caso de ser seleccionada provoca la vuelta al menú anterior. default next menu option: Devuelve una opción genérica para saltar a otro menú. no show next menu option: Similar a la anterior, pero oculta el menú al que se salta. Por ejemplo, para generar el un menú que muestra las opciones para acceder la selección de idioma o a la selección de usuario se utiliza el siguiente código (se han eliminado las referencias a los módulos): xml_document(_Client, _User, LanguageId, []) -> Header = get_message(LanguageId, settings_header), Menu = compose([{header, Header}, {init_actions, default_bind_key_actions()}, 172 Capı́tulo 6. Implementación de VXMLS {options, settings_options(LanguageId)}]), menu2xml(Menu). settings_options(LanguageId) -> ChooseUser = get_message(LanguageId, settings_choose_user), ChooseLanguage = get_message(LanguageId, settings_choose_language), [no_show_next_menu_option(ChooseUser, "choose_user_menu"), no_show_next_menu_option(ChooseLanguage, "language_menu"), default_cancel_option(LanguageId)]. 6.4.8. Menús definidos La interfaz definida se compone de los siguientes menús: main menu: Menú mostrado como raı́z de la interfaz. Sus opciones permiten el acceso al menú settings menu y a movies menu. settings menu: Contiene opciones que permiten acceder a los menús choose user menu y language menu. choose user menu: Permite elegir entre los distintos usuarios asociados al Set Top Box. language menu: Permite elegir el idioma en el que se desea que se muestre la interfaz. movies menu: Sus opciones permiten acceder al menú genre menu con los argumentos necesarios, dependiendo de si se desea obtener una lista con las novedades, una lista con pelı́culas antiguas o una lista con las pelı́culas vistas. genre menu: Muestra opciones para acceder al menú movie list menu con los argumentos necesarios para mostrar las pelı́culas, nuevas, antiguas o vistas del género seleccionado. movie list menu: Muestra opciones para acceder al menú movie menu para una determinada pelı́cula. movie menu: Sus opciones permiten iniciar la reproducción del vı́deo al tiempo que se accede al menú video playing menu (que inicialmente estará oculto), o acceder al menú synopsys menu. Capa Controlador 173 video playing menu: Muestra opciones para avanzar, retroceder, continuar o terminar la reproducción de vı́deo. synopsys menu: No tiene opciones, muestra un resumen de la pelı́cula en pantalla. video end menu: Aparece cuando un vı́deo finaliza, sus opciones permiten iniciar de nuevo la reproducción del vı́deo o regresar al menú movie menu. 6.4.9. Ejemplos de vxmls get menu 6.4.9.1. main menu El menú main menu está definido en el módulo vxmls main menu. En un principio deberá mostrar dos opciones, una para acceder al menú de personalización y otra para acceder al los menús de selección de pelı́culas, pero en caso de que el Set Top Box no tenga asociado ningún usuario (es la primera vez que accede a VXMLS) se debe mostrar el menú de selección de usuario directamente. Este menú no recibe argumentos, a no ser que se solicite como resultado de una acción ReloadMenu, en cuyo caso recibe el argumento reloaded con valor true (ver la sección 5.14.6). En primer lugar, la función xml document comprueba que el argumento User no es false, de ser ası́, devuelve el resultado obtenido de la función xml document del módulo vxmls choose user menu que define el menú de selección de usuario. Una vez comprobado que el Set Top Box tiene asociado un usuario obtiene la cabecera a mostrar para el menú solicitando el mensaje main menu header al servidor vxmls lang (ver la sección 6.4.2) y genera la lista de acciones de inicialización y la lista de opciones (para lo que también deberá solicitar a vxmls lang las cadenas a mostrar para cada una) y obtiene un objeto Menu utilizando la función compose menu del módulo vxmls menu, explicado en la sección 6.4.7. El objeto Menu obtenido se transforma en un documento XML utilizando la función menu2xml del módulo vxmls xml y se devuelve como resultado. 6.4.9.2. movie list menu El módulo vxmls movie list menu se encargará de generar los menús que permiten al usuario seleccionar una pelı́cula. Este menú recibe los parámetros filter, que puede tener los valores old, premiere o shown, y genre que tiene como valor el identificador de un género. La función xml document de este módulo 174 Capı́tulo 6. Implementación de VXMLS devolverá la descripción de un menú que tendrá como opciones las pelı́culas del género especificado y que cumplan las condiciones impuestas por el argumento filter. old: Pelı́culas no vistas y que no sean novedad. premiere: Pelı́culas no vistas clasificadas como novedad. shown: Pelı́culas ya vistas. La generación de este menú pasa por las siguientes fases: 1. Obtener el valor del género y del filtro de entre los argumentos recibidos. 2. Obtener el texto a mostrar en la cabecera del menú, utilizando el servidor vxmls lang (ver la sección 6.4.2). 3. Obtener la lista de pelı́culas para el género y filtro especificado utilizando las funciones necesarias del modelo. 4. Generar una opción para cada pelı́cula con ayuda de las funciones del módulo vxmls menu (ver la sección 6.4.7). 5. Con todo esto, crear el objeto Menu utilizando la función compose menu del módulo vxmls menu. 6. Generar el documento XML con la función menu2xml del módulo vxmls xml. 6.4.9.3. synopsys menu Este menú es ligeramente especial ya que no define ninguna opción y no se llegará a mostrar, se mantiene siempre oculto. El objetivo de este menú es mostrar en pantalla un resumen sobre una determinada pelı́cula. Para ello, sus acciones de inicialización no enlazarán las teclas por defecto, como la mayorı́a del resto de menús, sino que mostrarán el resumen en pantalla (acción ShowText) y enlazarán las teclas SEL y STOP con las acciones para volver al menú anterior, ocultando el resumen. Para generar este menú, el módulo vxmls synopsys menu exporta una función xml document que sigue los siguientes pasos: 1. Obtener el identificador de la pelı́cula del argumento movie. Extensibilidad 175 2. Obtener el resumen sobre la pelı́cula, traducida al idioma correspondiente, utilizando las funciones del modelo. 3. Crear el objeto menú con las funciones del módulo vxmls menu. 4. Devolver el documento XML obtenido de transformar el objeto obtenido en el paso anterior utilizando la función menu2xml del módulo vxmls xml. 6.5. Extensibilidad Como se explicó en apartados anteriores, DFBCIn se ha diseñado e implementado de forma que fuera sencillo incorporar nuevas funcionalidades extendiendo la interfaz Action. VXMLS es el encargado de dirigir el comportamiento de DFBCIn, por lo que en el momento en que DFBCIn sea capaz de realizar nuevas tareas se debe modificar VXMLS para que pueda comunicarle a DFBCIn que realice dichas tareas en algún instante determinado. Para añadir el soporte para una nueva acción en VXMLS es necesario agregar código en los módulos vmxls menu y vxmls xml. En vxmls menu es necesario añadir un nuevo caso a action para que pueda generar un objeto Action para la nueva acción. Ası́ mismo, puede ser necesario añadir un nuevo registro en la cabecera vxmls menu.hrl para almacenar la información sobre la acción. En vxmls xml se debe añadir un nuevo caso en la función render action para que pueda transformar la nueva acción en un bloque XML. Normalmente, dadas las caracterı́sticas de Erlang, estas modificaciones sólo suponen añadir una nueva clausula a las funciones ya existentes. Por ejemplo, cuando se añadió la acción LoadMp3 sólo fue necesario hacer las siguientes modificaciones: En la cabecera vxmls menu.hrl, se añade un nuevo registro load mp3 con el campo path para almacenar la información sobre el fichero o URL que se debe cargar: -record(action, {type, data = false }). % Tipo de acción % Información sobre la misma 176 Capı́tulo 6. Implementación de VXMLS %%% Registros para almacentar información en el campo %%% data de action -record(change_option, {value, mode }). % An integer % (?RELATIVE | ?ABSOLUTE) % Continúa con la definición de otros registros % para las diferentes acciones % ... %%% Nuevo registro para la acción recién a~ nadida -record(load_mp3, {path % The file path (or URI) }). En el fichero vxmls menu.erl se debe añadir un nuevo caso a la función action para que pueda crear acciones de este tipo: action(show_text, Lines) when list(Lines) -> Data = #show_text{lines = Lines}, #action{type=show_text, data = Data}; % Constructores para otras acciones % ... %%% Nuevo constructor para una acción LoadMp3 action(load_mp3, Path) when list(Path) -> Data = #load_mp3{path = Path}, #action{type = load_mp3, data = Data}. En el fichero vxmls xml.erl se añade un nuevo caso para la nueva acción en la función render action para que pueda generar el código XML para esta nueva acción: render_action(Indent, unload_video, _Data) -> render_empty_tag(Indent, "unloadVideo"); % Otras funciones para renderizar las distintas acciones Extensibilidad 177 % ... %%% Nuevo caso para renderizar la función recién a~ nadida render_action(Indent, load_mp3, Data) -> render_empty_tag(Indent, "loadMp3", [{"path", Data#load_mp3.path}]); % ... Como se vio en este mismo capı́tulo en la sección 6.4.6, la implementación de VXMLS posibilita modificar la interfaz definida sin afectar a los módulos existentes, salvo aquellos módulos que son los encargados de devolver los documentos XML correspondientes a cada menú particular. Por lo tanto, redefinir la interfaz mostrada para adaptarla a las posibilidades que ofrecen las nuevas acciones sólo afecta al código de los módulos que definen cada menú. Capı́tulo 7 Validación Índice General 7.1. Despliegue del sistema . . . . . . . . . . . . . . . . . . 179 7.2. Tamaño del software para el Set Top Box . . . . . . . 180 7.3. DFBCIn . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 7.3.1. Problemas de rendimiento . . . . . . . . . . . . . . . . 181 7.3.2. Problemas con avifile . . . . . . . . . . . . . . . . . 182 7.4. VXMLS . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 7.4.1. Comportamiento general . . . . . . . . . . . . . . . . . 184 7.4.2. Problemas con Inets . . . . . . . . . . . . . . . . . . . 184 7.5. Streaming . . . . . . . . . . . . . . . . . . . . . . . . . . 184 7.1. Despliegue del sistema El sistema utilizado para la validación de las pruebas se compone de los siguientes subsistemas: DFBCIn se ejecutará en el Set Top Box explicado en la sección 2.2.4.3. VXMLS se ejecutará en un PC conectado por red al Set Top Box. VoDKA correrá sobre un cluster de máquinas. 179 180 Capı́tulo 7. Validación PC ejecutando VXMLS Cluster servidor de video Red de interconexion DFBCIn funcionando en el Set Top Box Figura 7.1: Despliegue del sistema final 7.2. Tamaño del software para el Set Top Box En el Set Top Box utilizado para las pruebas se instaló un disco duro para facilitar el desarrollo, pero el sistema final deberı́a caber en una memoria flash. El tamaño de los diferentes componentes de software que se deben instalar es: Aplicación DFBCIn: Al ser una aplicación escrita C en y enlazada dinámicamente con las bibliotecas utilizadas, el espacio ocupado se reduce a 548 kilobytes, entre los que se cuentan las imágenes y fuentes utilizadas para dibujar la interfaz y la biblioteca dinámica utilizada para implementar el módulo gráfico. DirectFB : A pesar de estar enlazada estáticamente, DirectFB es una librerı́a muy ligera en cuanto a espacio ocupado, sólo ocupa 4,4 megabytes. Avifile: La versión utilizada de avifile necesita una instalación de 13 megabytes. Expat: La biblioteca expat solamente necesita la instalación de 804 kilobytes. Instalación de Linux : Los más de 40 mega bytes restantes (asumiendo una memoria Flash de 64 megabytes) son más que suficientes para una instalación de Linux sin X Windows. El sistema completo puede ser almacenado en una memoria Flash de 64 mega bytes. Nótese que en caso de que se hubiera utilizado el sistema X Windows esto DFBCIn 181 serı́a imposible, ya que una instalación tı́pica de dicho sistema ocupa más de esos 64 mega bytes. 7.3. DFBCIn La aplicación DFBCIn se ejecutará sobre un Set Top Box conectado a un televisor, utilizando un mando a distancia como sistema de entrada. Figura 7.2: DFBCIn funcionando en el Set Top Box Se observó el correcto funcionamiento del mando a distancia y de la salida de televisión del Set Top Box. 7.3.1. Problemas de rendimiento La tarjeta gráfica incorporada por el Set Top Box utilizado, una SiS 650, no está soportada por la versión de DirectFB utilizada, esto quiere decir que todas las operaciones serán realizadas sin ayuda de la aceleración hardware proporcionada por la tarjeta. Debido a esto, la reproducción de vı́deo que se consiguió en un principio era demasiado pobre, ya que la CPU no era suficientemente rápida para decodificar y dibujar todos los frames en tiempo real. Para aligerar la carga durante, la primera medida tomada fue la de disminuir la resolución de salida, que originalmente era 800x600, a 640x480, con una profundidad de color de 16 bits. Con esta medida se consiguió una pequeña mejora, pero el vı́deo seguı́a acusando saltos evidentes. Lo siguiente que se intentó fue adaptar el tamaño del 182 Capı́tulo 7. Validación vı́deo para evitar tener que realizar escalados a la hora de copiar los frames en la superficie primaria. Se comprobó que con un vı́deo codificado con un ancho de 640 pixels, de forma que la copia de los frames a la superficie primaria simplemente consiste en una copia byte a byte, los resultados obtenidos fueron casi aceptables. La solución definitiva fue codificar los vı́deos con un ancho de 320 pixels. La copia de los frames a la superficie primaria supone una operación de escalado, pero escalar al doble de tamaño es relativamente sencillo (sólo se deben duplicar los pixels), y el ahorro computacional que supone trabajar con imágenes que ocupan la cuarta parte que en el caso anterior es suficiente para que el flujo de frames sea suave. La perdida de resolución se hace evidente en un monitor, pero las pruebas en la televisión resultaron bastante aceptables. Aún ası́, operaciones tales como reproducir vı́deo con un menú dibujado por encima ralentizan el sistema de forma que el vı́deo vuelve a saltar de forma desagradable. Para evitar esto de diseñó la interfaz definida por VXMLS de forma que siempre se detenga el vı́deo antes de dibujar un menú por encima. El menú se dibujará sobre un frame estático, por lo que no habrá problemas de ralentización. 7.3.2. Problemas con avifile La implementación de IDirectFBVideoProvider para ficheros AVI distribuida con DirectFB utiliza la versión 0.6 de la biblioteca avifile. Durante las pruebas se detectaron varios problemas: 1. Al final de la reproducción, el vı́deo vuelve a empezar, esto imposibilita por completo la detección del final del vı́deo tal y como se explicó en la sección 5.12.7.1. El problema se encontró en las fuentes de avifile, que explı́citamente volvı́an al principio del vı́deo cuando se alcanzaba el final (presumiblemente debido a un cambio hecho para alguna prueba por los programadores de DirectFB). 2. Utilizando el proveedor de avifile, DFBCIn se colgaba al ejecutar una acción VideoStop durante la reproducción de vı́deo. El problema se encontró en un interbloqueo entre Interface (ver la sección 5.10.2) y la reproducción de vı́deo controlada por avifile. Cuando Interface llama a videoStop para detener la reproducción debe esperar a que esta función termine, manteniendo su estado bloqueado, avifile intenta terminar de decodificar un frame antes de detener la reproducción, cuando tiene el frame disponible ejecuta la función videoCallback que fue instalada en videoLoad y después deberı́a detener la reproducción. El problema surge porque videoCallback debe ejecutar la función notifyNewFrame para VXMLS 183 avisar a Interface de que hay un nuevo frame disponible, y esta función necesita acceder al estado de Interface para modificar sus banderas, por lo que debe esperar a que Interface libere el semáforo que esta tiene bloqueado mientras trata de terminar la ejecución de videoStop. 3. Si se produce un fallo durante la reproducción de vı́deo (no deberı́a producirse ningún fallo, pero recuérdese que para terminar la aplicación durante las pruebas se provoca un fallo de forma intencionada), DFBCIn termina de forma incontrolada, a veces provocando fallos de segmento y otras quedando totalmente bloqueada. Este fallo se debe a que la implementación del proveedor de vı́deo para avifile no es completa y no sincroniza la destrucción del reproductor de vı́deo con la liberación de las superficies utilizadas. Se puede dar el caso de que se intente escribir un nuevo frame en una superficie que ya no tiene memoria reservada. 4. La versión 0.6 se avifile utiliza los codecs de Windows en vez de utilizar una biblioteca nativa de Linux, por lo que se produce una sobrecarga debida a las emulaciones necesarias. 5. Las funciones GetPos y SeekTo no siempre funcionan correctamente. El problema 1 se solucionó eliminando esa “caracterı́stica” en las fuentes de avifile. El problema 2 se solucionó desplazando la llamada a la función de Interface interfaceNotifyNewFrame a un nuevo hilo de ejecución. Durante la inicialización de Video (ver la sección 5.12.7) se crea un nuevo hilo interfaceNotifier que será despertado por la función instalada durante videoLoad como callback en el proveedor. Una vez despertado este hilo llamará a interfaceNotifyNewFrame (deberá esperar en algunos casos a que Interface libere su estado, pero ahora no bloquea la ejecución) y volverá a dormirse (ver la sección 5.12.7). Para solucionar el problema 2 se reescribió el código del proveedor de vı́deo proporcionado por DirectFB para adaptarlo a la versión 0-7.34 de avifile, que ya permite la utilización de otros codecs, como los de la biblioteca de Linux ffmpeg. En el apéndice C se puede ver la interfaz a implementar para un proveedor de vı́deo. El nuevo proveedor de vı́deo implementado ya no tiene el problema 1. Los problemas 3 y 5 están todavı́a sin solucionar. El problema de la liberación de memoria se debe solucionar mejorando la implementación del proveedor de vı́deo y el segundo problema parece que es debido a un fallo en avifile. 184 Capı́tulo 7. Validación 7.4. VXMLS 7.4.1. Comportamiento general VXMLS se ejecuta sobre un PC convencional, para comprobar que el correcto funcionamiento durante la ejecución de las pruebas se utilizaron diversas herramientas suministradas con la plataforma Erlang/OTP, como la aplicación tv que permite ver los contenidos de las tablas de Mnesia y de las tablas ets y la aplicación pman que permite monitorizar los procesos en ejecución dentro de un nodo Erlang. No se detectaron problemas en el funcionamiento de VXMLS, la interfaz definida funciona correctamente y los datos almacenados en la base de datos influyen en la misma como es esperado. 7.4.2. Problemas con Inets Durante las pruebas de sistema se comprobó que Inets no cerraba la conexión inmediatamente a pesar de enviar el campo Connection: close [35] en la cabecera, sino que tardaba una pequeña fracción de tiempo desde que terminaba de enviar la respuesta hasta que cerraba la conexión. Esto provocaba que la aplicación DFBCIn quedara bloqueada a la espera de que se cerrara la conexión, lo que derivaba en una molesta latencia entre el momento en el que el usuario pulsaba una tecla a la que estaba asociada alguna acción que necesitara realizar una petición a VXMLS para completar su ejecución y el momento en el que los resultados de la ejecución de dicha acción se hacı́an visibles. Para solucionar este problema, se implementó VXMLS de forma que añadiera el campo Content-Length y se hizo que httpGet diera prioridad a la información de dicho campo sobre el hecho de saber que la conexión va a ser cerrada (ver la sección 5.11.1). 7.5. Streaming Tras las pruebas ejecutadas utilizando ficheros locales para comprobar el correcto funcionamiento de DFBCIn en cuanto a reproducción de vı́deo, se realizaron las pruebas sobre medios servidos por VoDKA. Para ello se utilizó el sistema de ficheros virtual desarrollado en [37], que permite el acceso a medios distribuidos por streaming sin modificar la implementación del reproductor de vı́deo, introduciendo una serie de capas que ocultan el Streaming 185 acceso al medio remoto como si de un fichero local se tratara. En la figura 7.3 se muestra un diagrama con las capas introducidas por este proxy. DFBCIn Proveedor de Video Proxy Sistema Virtual de Ficheros Sistema de Buffers Protocolo de Streaming Servidor de Video Red Figura 7.3: Capas de acceso al servicio de streaming La reproducción de vı́deo utilizando esta técnica supone una carga computacional añadida para el Set Top Box lo que en ocasiones puede disminuir la calidad del vı́deo reproducido, dependiendo de la capacidad del Set Top Box. Figura 7.4: Cluster servidor de vı́deo utilizado Capı́tulo 8 Conclusiones y lı́neas futuras Índice General 8.1. Conclusiones . . . . . . . . . . . . . . . . . . . . . . . . 187 8.2. Continuación de este proyecto . . . . . . . . . . . . . . 189 8.2.1. DFBCIn . . . . . . . . . . . . . . . . . . . . . . . . . . 189 8.2.2. VXMLS . . . . . . . . . . . . . . . . . . . . . . . . . . 190 8.1. Conclusiones En este proyecto se ha desarrollado un sistema cliente-servidor para permitir el acceso controlado a los medios de un servidor de streaming. Para ello se han utilizado los lenguajes de programación C, C++ y Erlang, ası́ como diferentes recursos existentes para dichos lenguajes. Para la comunicación entre la aplicación cliente y el servidor se han utilizado los estándares HTTP y XML. La aplicación cliente fue instalada con éxito en un Set Top Box basado en Linux, de forma que el usuario puede utilizar un mando a distancia para interactuar con la aplicación al mismo tiempo que ve los resultados en un televisor. Algunos de los recursos utilizados, como DirectFB, aún están en fase de desarrollo y presentan ciertas complicaciones y limitaciones, pero en general su funcionamiento es bueno. 187 188 Capı́tulo 8. Conclusiones y lı́neas futuras El diseño y la implementación de ambas aplicaciones se ha hecho de forma que puedan ser extendidas fácilmente para aumentar la funcionalidad que ofrecen. Los principales puntos a destacar sobre este proyecto son los siguientes: El lenguaje C es necesario para desarrollar aplicaciones para entornos con recursos limitados como los Set Top Boxes, pero su uso presenta muchas complicaciones: • El control de memoria debe ser explı́cito, esto complica la creación y de “objetos” que deben ser compartidos por varios módulos. La gestión de memoria es la mayor fuente de errores cuando se programa en C. • Es complicado plasmar un diseño hecho en UML en un programa en C, ya que no es orientado a objetos. • La programación multi-hilo hace el código confuso y propenso a errores, ya que los distintos hilos se ejecutan sobre el mismo espacio de memoria de datos. Es necesario añadir explı́citamente el control de concurrencia, lo que facilita la aparición de errores de tipo interbloqueo y race conditions que no ocurren de forma determinista y son muy difı́ciles de localizar. • C no proporciona ningún mecanismo de control de errores, debe codificarse de forma explı́cita. Erlang es un lenguaje funcional que permite crear aplicaciones que se ejecutan sobre un entorno que se encarga de la gestión de memoria. Las ventajas observadas tras la programación de VXMLS fueron: • No es necesario programar el control del memoria. • Los lenguajes funcionales minimizan los efectos colaterales, lo que disminuye el número de errores introducidos en el código y simplifica la depuración. • Erlang permite aislar el control de errores en un proceso que sólo se dedique a eso. Esto aumenta la claridad del código ya el código de control de errores no está mezclado con el código que proporciona la funcionalidad de la aplicación. • Cada proceso concurrente en Erlang tiene su propio espacio de memoria y no puede afectar al resto de procesos. La única forma que tienen los procesos para comunicarse entre sı́ es el paso de mensajes. Esto elimina por completo los problemas de tipo race condition y facilita en gran manera la detección de interbloqueos. Continuación de este proyecto 189 • Todo esto hace que el desarrollo de una aplicación en Erlang sea mucho más rápido que el desarrollo de una aplicación en C. • En su contra, las aplicaciones consumen más recursos y son más lentas que las desarrolladas en lenguajes de más bajo nivel. DirectFB Proporciona una interfaz interesante para acceder al sistema framebuffer de Linux. Las pruebas realizadas demostraron que es un framework escalable y suficientemente estable, a pesar de estar aún en fase de desarrollo. El sistema de interfaces y proveedores que utiliza para acceder a diferentes medios como fuentes, imágenes o vı́deos y a las posibilidades de aceleración gráfica de las diferentes tarjetas gráficas permite la ampliación de los formatos y plataformas soportadas de forma sencilla. Durante el desarrollo de este proyecto se pudieron comprobar las caracterı́sticas de este framework : • Permite desacoplar la aplicación del hardware subyacente, salvo por las diferencias de eficiencia entre las plataformas con aceleración hardware soportada y las que deben ser emuladas por software. • Aunque es suficientemente robusto, en ocasiones puede dejar el sistema inestable, sobre todo por culpa de interfaces que no están completamente desarrolladas, como por ejemplo el proveedor de vı́deo utilizado para ficheros AVI. • Está implementado para soportar aplicaciones multi-hilo. • El hecho de utilizar DirectFB para controlar el apartado gráfico de la aplicación condiciona la forma en la que se va a hacer el control de la entrada al sistema, pero como se vio en la implementación de DFBCIn, el código dependiente de DirectFB se puede aislar del resto de la aplicación fácilmente. 8.2. Continuación de este proyecto 8.2.1. DFBCIn El principal problema de la implementación actual es que la reproducción de vı́deo se está haciendo sin ayuda de la aceleración hardware proporcionada por la tarjeta gráfica. Serı́a interesante implementar el soporte de dicha tarjeta para DirectFB, de esta forma se podrı́an aprovechar mejor las capacidades gráficas del Set Top Box. 190 Capı́tulo 8. Conclusiones y lı́neas futuras El proveedor de vı́deo utilizado para reproducir ficheros AVI fue modificado para solucionar algunos problemas que se detectaron (ver la sección 7.3.2), pero sigue presentando algunas deficiencias: Su destrucción no es segura y puede dejar bloqueado el sistema en caso de que se produzca algún error durante la reproducción de vı́deo. Las funciones de desplazamiento y la que devuelve la posición actual no siempre funcionan correctamente. Para solucionar estos problemas se deberá mejorar la implementación del proveedor de vı́deo proporcionado (usa la librerı́a avifile) o desarrollar uno nuevo para otro reproductor como Xine o MPlayer1 . También se debe mejorar el aspecto gráfico de la aplicación permitiendo imágenes en lugar de texto para identificar las opciones, fondo de pantalla configurable, mosaicos de vı́deos, diferentes aspectos visuales (temas) ... Se podrán añadir nuevas acciones que permitan, por ejemplo, reposicionar la reproducción de fichero mp3 que se está reproduciendo, envı́o de mensajes a otros usuarios, calificación y clasificación de pelı́culas, creación de los perfiles de usuario (por ahora deben ser creados directamente en la base de datos), etc. El control de errores debe ser mejorado. Por ahora, cualquier error grave provoca la finalización de la aplicación con un mensaje de error, en el sistema final será necesario un sistema de recuperación de errores. Para completar el middleware del Set Top Box será necesario implementar un API para permitir la ejecución de aplicaciones multimedia externas. 8.2.2. VXMLS El servidor VXMLS implementado no es mas que un prototipo para demostrar el funcionamiento de DFBCIn. Un servidor más completo podrı́a incorporar las siguientes mejoras: Incorporar una herramienta de administración que permita añadir información a la base de datos. Almacenar mucha más información sobre los usuarios y las pelı́culas. 1 MPlayer puede no ser una buena elección, ya que no se amolda a la programación multi-hilo de DirectFB debido a su uso intensivo de variables globales Continuación de este proyecto 191 Definir una interfaz mucho más flexible, que tenga en cuenta parámetros como el perfil del usuario, el dı́a y la hora, ... Permitir más propiedades de personalización como por ejemplo: imagen de fondo por defecto, música preferida para los menús, última pelı́cula vista y hasta qué posición, ... Recolectar más información sobre los usuarios a medida que interactúan con la interfaz: cuáles son las secciones más visitadas, qué tipo de pelı́culas son las más vistas, ... Proporcionar acceso a otro tipo de servicios como telebanca, información, tcomercio, etc. Para acceder a este tipo de servicios no es necesario modificar DFBCIn, se puede utilizar la acción SetProperty para obtener los comandos seleccionados por el usuario. Apéndice A DTD utilizados A.1. DTD utilizado para la definición de menús <!ELEMENT menu (header?, version, image?, timer?, initActions?, option*)> <!ELEMENT header (#PCDATA)> <!ELEMENT version (#PCDATA)> <!ELEMENT image EMPTY> <!ATTLIST image file CDATA #REQUIRED cache (yes | no) #IMPLIED> <!ELEMENT timer (action+)> <!ATTLIST timer seconds NMTOKEN #REQUIRED> <!ELEMENT initActions (action+)> <!ELEMENT option (text, action+)> <!ELEMENT text (#PCDATA)> <!ELEMENT | | | | | | | | | action (changeOption nextMenu previousMenu hideMenu showMenu playVideo stopVideo resumeVideo unloadVideo seekVideo 193 194 Apéndice A. DTD utilizados | | | | | | setProperty bindKey reloadMenu loadMp3 pauseContinueMp3 killMp3Player)> <!ELEMENT changeOption EMPTY> <!ATTLIST changeOption value NMTOKEN #REQUIRED mode (relative | absolute) #IMPLIED> <!ELEMENT nextMenu (menuArgument)*> <!ATTLIST nextMenu name CDATA #REQUIRED> <!ELEMENT menuArgument EMPTY> <!ATTLIST menuArgument name NMTOKEN #REQUIRED value NMTOKEN #REQUIRED> <!ELEMENT hideMenu EMPTY> <!ELEMENT showMenu EMPTY> <!ELEMENT previousMenu EMPTY> <!ATTLIST previousMenu depth NMTOKEN #IMPLIED reinit (yes | no) #IMPLIED> <!ELEMENT playVideo (action+)> <!ATTLIST playVideo file CDATA #REQUIRED> <!ELEMENT stopVideo EMPTY> <!ELEMENT resumeVideo EMPTY> <!ELEMENT unloadVideo EMPTY> <!ELEMENT seekVideo EMPTY> <!ATTLIST seekVideo mode (forward | backward | absolute) #REQUIRED time NMTOKEN #REQUIRED> <!ELEMENT bindKey (keyActions | keyEvent)> <!ATTLIST bindKey key ( PROGRAM | POWER | MENU |TITLE | OSD | LANGUAGE | ANGLE | SUBTITLE | VOLUP | VOLDOWN | MUTE | LR | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | VGATV |ENT | UP | DOWN | LEFT | RIGHT | SEL | PLAY | STOP | SLOW | EJECT | REWIND | FORWARD | NEXT | PREVIOUS) #REQUIRED> DTD utilizado para la especificación de las respuestas de VXMLS 195 <!ELEMENT keyEvent EMPTY> <!ATTLIST keyEvent type (EXECUTE | NULL) #REQUIRED> <!ELEMENT keyActions (action+)> <!ELEMENT setProperty EMPTY> <!ATTLIST setProperty name (language) #REQUIRED value NMTOKEN #REQUIRED> <!ELEMENT reloadMenu EMPTY> <!ELEMENT loadMp3 EMPTY> <!ATTLIST loadMp3 path CDATA #REQUIRED> <!ELEMENT pauseContinueMp3 EMPTY> <!ELEMENT killMp3Player EMPTY> A.2. DTD utilizado para la especificación de las respuestas de VXMLS <!ELEMENT vxmls (ok | error)> <!ELEMENT ok EMPTY> <!ELEMENT error (#PCDATA)> Apéndice B API del módulo gráfico de DFBCIn /* Initilization functions */ void graphicsInit(int argc, char **argv); void graphicsDeinit(void); /* Menu drawing functions */ void graphicsLoadMenu(Menu menu); void graphicsUnloadMenu(void); void graphicsSetBox(short showBox); /* Video functions */ void graphicsLoadVideo(const char *videoFile); void graphicsPlayVideo(void); void graphicsStopVideo(void); void graphicsResumeVideo(void); double graphicsGetVideoPosition(void); void graphicsGotoVideoPosition(double videoPosition); void graphicsBlitVideo(void); void graphicsUnloadVideo(void); void graphicsSetNewFrameCallback(void (*callback)(void)); void graphicsSetVideoEndCallback(void (*callback)(void)); /* Other graphic functions */ void graphicsLoadText(Array lines); void graphicsUnloadText(void); 197 198 Apéndice B. API del módulo gráfico de DFBCIn void graphicsFlipScreen(short showBackground); /* Input control */ InputKey graphicsTestInput(void); Apéndice C Definición de IDirectFBVideoProvider DEFINE_INTERFACE( IDirectFBVideoProvider, /** Retrieving information **/ /* * Retrieve information about the video provider’s * capabilities. */ DFBResult (*GetCapabilities) ( IDirectFBVideoProvider *thiz, DFBVideoProviderCapabilities *caps ); /* * Get a surface description that best matches the video * contained in the file. */ DFBResult (*GetSurfaceDescription) ( IDirectFBVideoProvider *thiz, DFBSurfaceDescription *dsc ); /** Playback **/ 199 200 Apéndice C. Definición de IDirectFBVideoProvider /* * Play the video rendering it into the specified rectangle * of the destination surface. * * Optionally a callback can be registered that is called * for each rendered frame. This is especially important if * you are playing to a flipping surface. In this case, you * should flip the destination surface in your callback. */ DFBResult (*PlayTo) ( IDirectFBVideoProvider *thiz, IDirectFBSurface *destination, const DFBRectangle *destination_rect, DVFrameCallback callback, void *ctx ); /* * Stop rendering into the destination surface. */ DFBResult (*Stop) ( IDirectFBVideoProvider *thiz ); /** Media Control **/ /* * Seeks to a position within the stream. */ DFBResult (*SeekTo) ( IDirectFBVideoProvider *thiz, double seconds ); /* * Gets current position within the stream. */ DFBResult (*GetPos) ( IDirectFBVideoProvider *thiz, 201 double *seconds ); /* * Gets the length of the stream. */ DFBResult (*GetLength) ( IDirectFBVideoProvider *thiz, double *seconds ); /** Color Adjustment **/ /* * Gets the current video color settings. */ DFBResult (*GetColorAdjustment) ( IDirectFBVideoProvider *thiz, DFBColorAdjustment *adj ); /* * Adjusts the video colors. * * This function only has an effect if the video provider * supports this operation. Check the providers capabilities * to find out if this is the case. */ DFBResult (*SetColorAdjustment) ( IDirectFBVideoProvider *thiz, DFBColorAdjustment *adj ); Apéndice D Tipos de datos en Erlang Tipos de datos constantes: Son tipos de datos que no pueden ser divididos en tipos más primitivos. • Números: por ejemplo 123, -789, 3.14159, 7.8e12, -1.2e-45. Se subdividen en enteros y flotantes. • Átomos: por ejemplo abc, ’Un átomo con espacios’, lunes, verde, hola mundo. Son simplemente constantes con nombre. • Pids: Identificadores de proceso. Tipos de datos compuestos: Se utilizan para agrupar otros tipos de datos: • Tuplas: Por ejemplo {a, 12, b}, {}, {1, 2, 3}. Se utilizan par almacenar un número fijo de elementos. Los elementos no tiene porque ser del mismo tipo. • Listas: Por ejemplo [], [a, b, 12], [22], [a, ’hola amigo’]. Se utilizan para almacenar un número variable de elementos. Al igual que para las tupas, los elementos no tiene que ser del mismo tipo. A partir de la versión 4.4 se introdujeron dos nuevos tipos: Registros: Son similares a las tupas, pero con campos con nombre, internamente se representan como tuplas cuyo primer elemento es el nombre del registro y los siguientes elementos contienen los valores de los campos del registro. Funciones de orden superior : El tipo fun representa funciones que pueden ser pasadas como argumentos a otras funciones y que pueden ser devueltas como resultado de evaluar una función. 203 204 Apéndice D. Tipos de datos en Erlang Estos tipos se almacenan en variables. Los nombres de las variables empiezan con mayúsculas. Una variable puede obtener su valor una sola vez, una vez asignado su valor éste no puede cambiar. Apéndice E Instalación y ejecución E.1. Estructura de la distribución En el directorio Interface se encuentran los ficheros necesarios para compilar e instalar DFBCIn. En el directorio Server se encuentran los ficheros necesarios para compilar e instalar VXMLS. En el directorio libs/DirectFB se encuentra la versión de DirectFB utilizada para el desarrollo de DFBCIn. En el directorio libs/Avifile se encuentran las dos versiones de avifile utilizadas en este proyecto. En el directorio libs/Expat se encuentra la versión de expat utilizada para este proyecto. En el directorio doc se encuentra la memoria del proyecto en formato PDF y las fuentes en LATEXpara generarla. E.2. Instalación de VXMLS E.2.1. Compilación En el fichero vxmls.mk se definen ciertas variables que pueden ser modificadas antes de compilar la aplicación: 205 206 Apéndice E. Instalación y ejecución PORT : Puerto en el que se va a instalar el servidor. MNESIA DB DIR: Directorio en el que Mnesia va a crear los ficheros necesarios para mantener la base de datos. EXTRA ARGS : Argumentos que se pasarán a VXMLS en el script de arranque. Normalmente no será necesario añadir ninguno. MAX CLIENTS : Numero máximo de conexiones que aceptará Inets. ADMIN : Dirección de correo del administrador. Este dato es utilizado por Inets para generar las páginas HTML que devuelve en caso de error, por lo que para este proyecto no es relevante. ERL BIN : Directorio que contiene el compilador y el emulador de Erlang EPMD BIN : Directorio en el que se encuentra el demonio mapeador de puertos de Erlang. NODENAME : Nombre del nodo Erlang en el que se ejecutará el servidor. MP3 MAIN FILE : Nombre del fichero (o URL) con la música que se reproducirá durante la exposición de menús. También se definen otras variables que normalmente no deberı́an ser modificadas, excepto EFLAGS de la que puede interesar eliminar la bandera +debug info para evitar que el compilador introduzca información de depuración en los ficheros compilados. Tras la edición de este fichero, el comando make deberı́a compilar todos los ficheros fuente, generar el fichero de configuración utilizado por la aplicación Inets inets/conf/vxmls.conf y los scrpits vxmls y vxmls initdb en el directorio bin. E.2.2. Instalación Antes de ejecutar VXMLS por primera vez es necesario crear la base de datos, para ello se debe ejecutar el script bin/vxmls initdb, que creará un nuevo esquema para Mnesia e introducirá los datos indicados en el fichero de configuración vxmls.conf El script bin/vxmls permite arrancar y detener la ejecución del servidor. bin/vxmls start: Arranca el servidor en segundo plano Instalación de DFBCIn 207 bin/vxmls debug: Arranca el servidor en primer plano, para facilitar las labores de depuración (para terminar la ejecución se debe evaluar la función vxmls:stop()). bin/vxmls stop: Detiene la ejecución del servidor. E.3. Instalación de DFBCIn E.3.1. Requisitos previos Para el correcto funcionamiento de DFBCIn es necesario realizar ciertas modificaciones en las fuentes de DirectFB y de avifile. En el directorio patches están los parches necesarios para hacer esas modificaciones: avifile-patch: Corrige ciertos problemas de en las fuentes de la versión de avifile distribuida con DirectFB. avifile-provider-patch: Adapta el proveedor de vı́deo para ficheros AVI distribuido en el paquete DirectFB-extra en su versión 0.9.16 para que pueda ser compilado con la versión 0.7.34 de avifile. avifile0.7-0.7.34-patch: Corrige un error en un fichero de la versión 0.7.34 de avifile. El primer parche es necesario en caso de que se compile DirectFB-extra con la versión de avifile distribuida con DirectFB, los otros dos deberán ser aplicados en caso de que se utilice la versión 0.7.34 de avifile. Además de DirectFB y avifile, también es necesaria la biblioteca expat. Una vez ejecutadas estas modificaciones se deben compilar e instalar las bibliotecas. Es necesario que DirectFB se compile con soporte para imágenes PNG y frecuentes True Type. El soporte para SDL es opcional, pero será necesario si se desea ejecutar DFBCIn sobre las X Windows sin necesidad de un kernel con soporte para el dispositivo framebuffer. El paquete DirectFB-extra se deberá compilar después de haber instalado DirectFB y avifile. E.3.2. Compilación En el fichero dfbcin.mk se definen ciertas variables que pueden ser modificadas antes de compilar la aplicación: 208 Apéndice E. Instalación y ejecución DEFAULT GRAPHLIB : Indica con qué biblioteca gráfica se va a enlazar DFBCIn • dfblib: Se enlaza con la biblioteca que utiliza DirectFB. • charlib: Se enlaza con la biblioteca basada en la consola de texto. VXMLS PORT : Puerto en el que esta escuchando VXMLS. HOSTNAME : Nombre de la máquina en la que está funcionando VXMLS. VXMLS CLIENT : Nombre que utilizará el Set Top Box para registrarse en VXMLS. VXMLS PASSWD: Clave que utilizará el Set Top Box para registrarse en VXMLS. DFB INCLUDE : Directorio en el que se encuentran las cabeceras de DirectFB. Una vez editado este fichero, el comando make compilará las fuentes, creará el fichero de propiedades conf/properties y generará el script bin/vxmls. E.3.3. Ejecución La biblioteca gráfica se encuentra en el directorio lib, para ejecutar DFBCIn es necesario indicarle al enlazador de código su ubicación, por ejemplo, añadiendo la ruta a la variable de entorno LD LIBRARY PATH. El ejecutable dfbcin se encuentra en el directorio src, su ejecución comenzará el funcionamiento de DFBCIn a pantalla completa. Para realizar pruebas es más cómodo ejecutar el script bin/dfbcin sdl, que permite ejecutar DFBCIn en una ventana dentro del entorno X-Windows. Para que funcione DFBCIn es imprescindible que VXMLS esté escuchando en el puerto y máquina especificados en el fichero dfbcin.mk. Apéndice F Glosario API Application Programming Interface, Conjunto de funciones que proporciona una biblioteca. ASF Advanced Streaming Protocol, Protocolo de streaming avanzado. Protocolo de streaming propietario de Microsoft. ATM Asynchronous Transfer Mode, Modo de transmisión ası́ncrono. Tecnologı́a de conmutación basada en la transmisión de celdas de tamaño fijo. ATSC Advanced Television Systems Comitee, Comité de Sistemas de Televisión Avanzada. BIF Built In Function, Función integrada. CBR Constant Bit Rate, Tasa de bits constante. CPU Central Processing Unit, Unidad Central de Proceso. DAC Digital to Analog Converter, Conversor de analógico a digital. DAO Data Access Object, Objeto de acceso a datos. Patrón de diseño utilizado para desacoplar los métodos de acceso a sistemas de almacenamiento persistente. DASE Digital TV Application Software Environment, Estándar para el desarrollo de middleware propuesto por ATSC. DTD Document Type Definition, Definición de tipo de documento. Especifica la estructura que debe respetar un documento XML para que se considere válido. 209 210 Apéndice F. Glosario DVB Digital Video Broadcasting, Transmisión broadcast de vı́deo digital. ERTS Erlang Runtime System, Entorno de ejecución de Erlang. ESI Erlang Scripting Interface, Interfaz similar a CGI, optimizada para aplicaciones Erlang. CGI Common Gateway Interface, Estándar para comunicar aplicaciones externas con servidores de información. HTML Hypertext Marckup Language, Lenguaje ampliamente utilizado para la publicación de documentos de hipertexto en Internet. HTTP Hypertext Transfer Protocol, Protocolo estándar de Internet utilizado para la transmisión de documentos a nivel de aplicación. IRD Integrated Recevier Decoder, Decodificador Receptor Integrado. ISDB Integrated Services Digital Broadcasting, Estándares para la transmisión de vı́deo digital utilizados en Japón. ISO International Organization of Standarization, Organización Internacional de Estandarización. LANE Local Area Network Emulation, Emulación de red de área local. LIRC Linux Infrared Remote Control, Control remoto por infrarrojos para Linux. MDI Multiple Document Interface, Interfaz multi-documento. MHP Multimedia Home Plataform, Estándar de plataforma software para televisión interactiva. MMIO Memmory Mapped Input Output, Registros de un dispositivo a los que se puede acceder utilizando una dirección de memoria en lugar de usar instrucciones de E/S especiales. MPEG Moving Picture Experts Group, Familia de estándares para codificar archivos de contenido audiovisual. MP3 MPEG Layer 3, Formato de compresión para ficheros de audio. NTSC National Television Standards Comitee, Comité generador de estándares para televisión en Estados Unidos. 211 OCAP OpenCable Application Plataform Specification, Especificación de plataforma de aplicación de OpenCable. PAL Phase Alternation by Line, Estándar de sistema de vı́deo ampliamente utilizado en todo el mundo. PNG Portable Netwok Graphics, Formato de compresión de imágenes sin pérdidas. Pixel Cada uno de los puntos que forma una imagen digital. QoS Quality of Service, Calidad de servicio. RTOS Real Time Operating System, Sistemas operativos de tiempo real. RTP Real-Time Transport Protocol, Protocolo de transporte en tiempo real. RTSP Real-Time Streamming Protocol, Protocolo de streaming en tiempo real. SDL : Simple DirectMedia Layer, Biblioteca que permite un acceso portable a los dispositivos gráficos, dispositivos de sonido, ratón y teclado. SQL Structured Query Language, Lenguaje de consulta estructurado. Lenguaje utilizado para realizar consultas sobre bases de datos. SVGA Super VGA, Estándar gráfico mucho más avanzado que VGA. TCP Transmission Control Protocol, Protocolo estándar de Internet para establecer una enlace orientado a conexión. URI Uniform Resource Identifier, Identificador uniforme de recursos. URL Uniform Resource Locator, Localizador uniforme de recursos. Estándar que permite referirse a un recurso y su localización. VESA Video Electronic Starndards Association, Organización para el desarrollo de estándares para hardware gráfico. VGA Video Graphics Array, Estándar de hardware Gráfico diseñado por IBM. Proporciona tanto el modo gráfico como el modo texto. La mayorı́a de las tarjetas de vı́deo actuales proporcionan compatibilidad VGA. VoD Video on Demand, Vı́deo bajo demanda. XDML Extensible Document Meta Language, Meta lenguaje de documentos extensible. Permite definir documentos de forma que puedan ser exportados a varios formatos. 212 Apéndice F. Glosario XML Extensible Markup Language, Estándar que permite la definición de la estructura de un documento independientemente de su contenido. Bibliografı́a [1] A. Hundt, “DirectFB overview (v1.0).” http://www.directfb.org/documentation/DirectFB overview V0.1.pdf, September 2001. [2] C. Cooper, “Using expat.” http://www.xml.com/pub/a/1999/09/expat/index.html, september 1999. [3] J. L. Armstrong, M. C. Williams, C. Wikström, and S. R. Virding, Concurrent Programming in Erlang. Prentice Hall, 2nd edition ed., 1996. [4] “Introduction to erlang/otp.” http://erlang.org/doc/r9b/doc/system architecture intro/part frame.html. [5] “Vodka project home page.” http://vodka.lfcia.org/. [6] I. E. Mateos, “Administración personal de medios,” Master’s thesis, Facultade de Infomática de A Coruña, September 2002. [7] A. C. Inc, “About darwin streaming server.” http://www.publicsource.apple.com/projects/streaming/. [8] P. Wilkinson, M. DeSisto, H. Rother, and Y. Wong., “Ibm videocharger 101. ibm redbook.” International Technical Support Organization, 1999. [9] Oracle, “Oracle video server administrators guide and command reference,” 1998. Release 3.0 for UNIX. [10] Oracle, “Oracle video server system technical overview.” Oracle White Paper, 1998. Release 3.0 for UNIX. [11] Philips, “Webcine server.” http://www.mpeg-4player.com/products/server/index.asp. [12] I. Cisco Systems, “A distributed video server architecture for flexible enterprise-wide video delivery.” http://www.cisco.com/warp/public/cc/pd/mxsv/iptv3400/tech/dvsa wp.htm, 2000. White Paper. 213 214 Bibliografı́a [13] S. M. Inc., “Sun storedge media central streaming server.” http://www.sun.com/storage/media-central/. [14] S. G. Chan and F. Tobagi, “Hierarchical storage systems for interactive video-on-demand,” Tech. Rep. CSL-TR-97-723, Stanford University, Computer Systems Laboratory, 1997. [15] T. Chiueh, C. Venkatramani, and M. Vernick, “Design and implementation of the stony brook video server,” tech. rep., 1997. Software – Practice and Experience. [16] T. Chiueh, C. Venkatramani, and M. Vernick, “Performance evaluation of stony brook video server,” Tech. Rep. ECSL-TR-24, 1997. [17] T. Chiueh, C. Venkatramani, and M. Vernick, “Adventures in building the stony brook video server,” in Proceedings of ACM Multimedia ’96, (Boston, MA.), 1996. [18] D. Du, J. Hsieh, and J. Liu, “Building video-on-demand servers using sharedmemory multiprocessors,” tech. rep., Distributed Multimedia Research Center and Computer Science Department, University of Minnesota, and Ronald J. Vetter, Computer Science Department, North Dakota State University, 1996. [19] J. Sánchez, V. Gulı́as, A. Valderruten, and J. Mosquera, “State of the art and design of vod systems,” in International Conference on Information Systems Analisis, SCI’00-ISAS’00.ISBN 980-07-6694-4, (Orlando, USA), pp. 174–176, July 2000. [20] J. Whitehead and M. Wiggins, “Webdav: Ietf standard for collaborative authoring on the web,” IEEE Internet Computing, pp. 34–40, September/October 1998. [21] “Digital video broadcasting home page.” http://www.dvb.org. [22] “Dase web site.” http://www.dase.org. [23] C. T. Laboratiories, “Ocap 2.0 profile,” in OpenCable Application Specification, April 2002. [24] BartCalder, JonCourtney, BillFoote, LindaKyrnitszke, D. Rivas, C. Saito, J. V. Loo, and T. Ye, “Javatv api technical overview,” tech. rep., Sun Microsystems, November 2000. Bibliografı́a 215 [25] “Opentv fact sheet.” http://www.opentv.com/company/docs/CompanyAtGlance.pdf. [26] “Canal+ technologies.” http://www.canalplus-technologies.com. [27] “Alticast web site.” http://www.alticast.com. [28] “Liberate technologies.” http://solutions.liberate.com. [29] “Microsoft tv.” http://www.microsoft.com/tv/default.asp. [30] “Nds core middleware.” http://www.nds.com/interactive tv/middleware.html. [31] “Greg haerr’s microwindows and nanogui page.” http://www.microwindows.org. [32] “Qt overview.” http://www.trolltech.com/products/qt/. [33] “Directfb.org.” http://www.directfb.org/. [34] G. Uytterhoeven, “The linux frame buffer device subsystem.” http://home.tvd.be/cr26864/Linux/Expo/Paper.ps.gz. [35] R. Fielding, J. Gettys, J. Mogul, H. Frystyk, L. Masinter, P. Leach, and T. Berners-Lee, “RFC 2616: Hypertext transfrer protocol – http/1.1,” June 1999. [36] T. Bray, J. Paoli, C. M. Sperberg-McQueen, and E. Maler, “Extensible markup language (xml) 1.0 (second edition).” http://www.w3.org/TR/RECxml, October 2000. [37] J. P. Fernández, “Desarrollo de un proxy para el acceso a medios bajo la apariencia de un sistema de ficheros,” Master’s thesis, Facultade de Infomática de A Coruña, September 2002. [38] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns. ISBN:0201-63361-2, Addison-Wesley, October 1994. [39] Inets reference manual. [40] Mnesia reference manual. [41] Mnemosyne reference manual. [42] T. Berners-Lee, R. Fielding, U. C. Irvine, and L. Masinter, “RFC 2396: Uniform resource identifiers (uri): Generic syntax,” Agust 1998. 216 Bibliografı́a [43] G. E. Kasner and S. T. Pope, “A cookbook for using the model-view controller user interface paradigm in smalltalk-80,” Journal of Object-Oriented Programming, pp. 26–49, August/September 1988. [44] D. Alur, J. Crupi, and D. Malks, Core J2EE Patterns: Best Practices and Desing Strategies. ISBN:0130648841, Prentice Hall/Sun Microsystems Press, 1st ed., June 2001. [45] D. Kristol and L. Montulli, “RFC 2109: Http state management mechanism,” February 1997. [46] “Lirc: Linux infrared remote control.” http://www.lirc.org/. [47] “Linux avi file library.” http://avifile.sourceforge.net/. [48] “The common gateway interface specification.” http://hoohoo.ncsa.uiuc.edu/cgi/interface.html. [49] DirectFB Reference Manual. [50] “The apache http server project.” http://www.httpd.apache.org/.