ESCUELA SUPERIOR DE INGENIEROS DE BILBAO

PROYECTO
DE

Diseño e Implementación de un agente de usuario SIP

Documento nº 2 – Diseño
Alumno Fecha Firma Gotxi García, Ibon Marzo 2002

Profesor Cátedra

Profesor Ponente

Curso Académico 2001/2002

ESCUELA SUPERIOR DE INGENIEROS DE BILBAO

PROYECTO
DE

Diseño e Implementación de un agente de usuario SIP

Parte nº 1 – Diseño de Alto Nivel
Alumno Fecha Firma Gotxi García, Ibon Marzo 2002

Profesor Cátedra

Profesor Ponente

Curso Académico 2001/2002

2

Indice

1. INTRODUCCIÓN......................................................................................................................................9

2. DISEÑO DE LA INTERFAZ DE USUARIO........................................................................................11 2.1. CONFIGURACIÓN DEL SOFT-PHONE.............................................................................................................12 2.1.1. CONFIGURACIÓN A TRAVÉS DE UN FICHERO DE CONFIGURACIÓN......................................................................12 2.1.1.1. Sección SIP.......................................................................................................................................15 2.1.1.2. Sección de Dispositivos de audio......................................................................................................16 2.1.1.3. Sección de Registro...........................................................................................................................18 2.1.2. CONFIGURACIÓN A TRAVÉS DE UN SISTEMA DE VENTANAS..............................................................................20 2.2. INICIACIÓN............................................................................................................................................21 2.2.1. ARGUMENTOS POR LA LÍNEA DE COMANDOS................................................................................................21 2.2.2. INICIALIZACIÓN BÁSICA DEL AGENTE.........................................................................................................24 2.3. DEPURACIÓN.........................................................................................................................................30 2.3.1. DESARROLLO FUTURO............................................................................................................................33 2.4. ACCIONES DISPONIBLES...........................................................................................................................35 2.4.1. REALIZAR UNA LLAMADA DE AUDIO Y PREGUNTAR POR CAPACIDADES..............................................................38 2.4.1.1. Llamada de audio..............................................................................................................................41 2.4.1.2. Preguntar por las capacidades remotas..............................................................................................44 2.4.2. RECIBIR UNA LLAMADA DE AUDIO............................................................................................................48 2.4.3. FINALIZAR UNA LLAMADA DE AUDIO.........................................................................................................50 2.4.4. PROBAR LOS FLUJOS RTP Y LOS CÓDECS DE AUDIO.....................................................................................52 2.4.5. OTRAS ACCIONES POSIBLES.....................................................................................................................54 2.4.5.1. Consultar la configuración del agente................................................................................................54 2.4.5.2. Visualizar u ocultar la ventana de depuración...................................................................................54 2.4.5.3. Mostrar la ventana "Acerca de KSua"...............................................................................................55 2.4.5.4. Salir del agente..................................................................................................................................55 3. DISEÑO DE LA PILA SIP AMPLIADA...............................................................................................57 3.1. HILO GENERADOR DE HILOS.....................................................................................................................59 3.1.1. INTRODUCCIÓN A LA PROGRAMACIÓN CON HILOS..........................................................................................59 3.1.2. LOS HILOS ‘SUA_THREAD’..................................................................................................................60 3.1.3. LA LISTA DE HILOS ‘TH_LIST’.................................................................................................................61 3.1.4. ACCESO A LA RED................................................................................................................................62 3.1.4.1. Envío de información........................................................................................................................65 3.1.4.2. Modelo basado en transacciones........................................................................................................68 3.1.5. GESTIÓN DE LOS HILOS SIP DE APOYO......................................................................................................69 3.1.5.1. Creación de hilos como cliente por medio de eventos.......................................................................69 3.1.5.2. Creación de hilos como servidor leyendo de la red...........................................................................71 3.1.5.3. Gestión de hilos (eventos ‘SUA_ThMan_Event’).............................................................................73 3.2. HILOS SIP DE APOYO.............................................................................................................................74 3.2.1. CREACIÓN DE SESIONES COMO CLIENTE (‘SUA_THREAD_CLIENT’)................................................................74 3.2.2. SESIONES COMO SERVIDOR (‘SUA_THREAD_SERVER’)...............................................................................80 3.2.3. REGISTRO CON UN SERVIDOR (‘SUA_THREAD_CREG’)..............................................................................85 3.2.4. PREGUNTA DE CAPACIDADES COMO CLIENTE (‘SUA_THREAD_COPT’)...........................................................88 3.2.5. PREGUNTA DE CAPACIDADES COMO SERVIDOR (‘SUA_THREAD_SOPT’).........................................................90 3.3. PÁRSER SIP/SDP.................................................................................................................................90

3

.........5............................................................ HERRAMIENTAS PARA LA GENERACIÓN DE PÁRSERS............3..................................................................................................110 4.......................................2...........................................................................1....104 3..4.............4...............92 3................................................4.....................................................................115 4..................... PILA RTP Y CODIFICACIÓN/DECODIFICACIÓN...........................................................................2...........................3....................................................4..........4......103 3....................110 3........................................................115 4............3......3..115 4................3....1......................... Generación de mensajes SIP......................................... EVENTOS DE LLAMADA EN SENTIDO DE LA GUI HACIA LA PILA... Hilo RTP...... Eventos relacionados con una llamada saliente.4...2.................. BÚFFERES DE AUDIO......4...................116 4...................1..................1...........3..........................................................92 3..................... HILOS DE AUDIO: LECTURA/ESCRITURA DE UN DRIVER OSS.....94 3.......................113 4..............................................3.........................................115 4..............2...................................................................................2...........................3...................................................................................................................113 4... BÚSQUEDAS DNS EN SIP............................4......3.........116 4 ..............2................... Párser RTP....................... EVENTOS DE LLAMADA EN SENTIDO DE LA PILA HACIA LA GUI...............................4.......3............................................................91 3......................1................ DISEÑO DE LA METASEÑALIZACIÓN......3.......................................................3....114 4... EVENTOS DE CONTROL DE LLAMADA................. EVENTOS DE TEXTO..........................2................3........ Comprobación de la corrección de los mensajes...........1.................................113 4............2.................. GESTIÓN DEL VOLUMEN.............................. PÁRSER SIP/SDP A MEDIDA (CUSTOM).....3.................2.96 3..94 3......................................................3..................................................................... Extracción de información de las cabeceras SIP/SDP.............................................................................................................................92 3....109 3..................1.......................................................... EVENTOS DE VOLUMEN......... EVENTOS DE GESTIÓN DE HILOS................ Eventos de registro...................................................... ARQUITECTURA MULTIMEDIA...................................1.3.......................1........ Eventos relacionados con una llamada entrante........2..........................102 3................1................3..

.....25 FIGURA 4: VENTANA DE SELECCIÓN DE DIRECCIÓN IP........47 5 ......................................36 FIGURA 8: VENTANA DE INICIO DE LLAMADA........................................................................................................................................................21 FIGURA 3: VENTANA DE INICIO Y PRESENTACIÓN...................42 FIGURA 11: VENTANA DE CONTROL DE LLAMADA.43 FIGURA 12: CUADRO RESUMEN DE INICIO DE SESIÓN.......................................................................................................................................................................................10 FIGURA 2: PROPUESTA PARA VENTANA DE CONFIGURACIÓN..................................................................................................................................................45 FIGURA 13: VENTANA DE PREGUNTA DE CAPACIDADES REMOTAS...............................27 FIGURA 5: RESUMEN DEL PROCESO DE INICALIZACIÓN..........................................40 FIGURA 10: PROGRESO DEL INICIO DE SESIÓN..........................................................................................................................................31 FIGURA 7: KSUA EN LA BANDEJA DEL SISTEMA (SYSTEM TRAY).....................30 FIGURA 6: VENTANA DE DEPURACIÓN.........................................Indice de figuras FIGURA 1: DISEÑO MODULAR DEL AGENTE DE USUARIO SIP.......39 FIGURA 9: LIBRETA DE DIRECCIONES SIP........

................................................................86 FIGURA 27: PREGUNTA DE CAPACIDADES COMO CLIENTE.......................48 FIGURA 15: CUADRO RESUMEN DE RECEPCIÓN DE LLAMADAS.......................................FIGURA 14: CUADRO RESUMEN DE PREGUNTA DE CAPACIDADES.......................................................................................................................................................84 FIGURA 26: RESUMEN DEL PROCESO DE REGISTRO..................................................80 FIGURA 24: INICIO DE SESIÓN COMO SERVIDORES (SUA_THREAD_SERVER)..82 FIGURA 25: RECHAZO DE LLAMADA COMO SERVIDORES.......67 FIGURA 21: INICIO DE SESIÓN COMO CLIENTES (SUA_THREAD_CLIENT).....................................................53 FIGURA 17: VISUALIZACIÓN DE LA VENTANA DE DEPURACIÓN...............89 FIGURA 28: DISEÑO DEL PÁRSER SIP/SDP..................................................64 FIGURA 20: MÓDULO DE REENVÍO DE MENSAJES....................................................................................................................................................91 6 ...............................................................................55 FIGURA 19: SISTEMA DE ACCESO A LA RED PARA LEER..............................................................50 FIGURA 16: EJECUCIÓN DEL AGENTE CON LA OPCIÍON -RTP_TEST.............................78 FIGURA 23: FIN DE SESIÓN COMO SERVIDORES.............................55 FIGURA 18: VENTANA "ACERCA DE KSUA"..........75 FIGURA 22: FIN DE SESIÓN COMO CLIENTES................................................

.....................................................FIGURA 29: ARQUITECTURA GENERAL DEL SISTEMA MULTIMEDIA..................................................................................100 FIGURA 32: SITUACIÓN DE CONGESTIÓN EN LOS BÚFFERES DE AUDIO....105 FIGURA 34: BÚFFER DE RECEPCIÓN DE PAQUETES RTP ADELANTADOS: DETECCIÓN DE SECUENCIA..........................................................................................................................................................................................................101 FIGURA 33: ESQUEMA FUNCIONAL DEL HILO RTP........96 FIGURA 30: FUNCIONAMIENTO DE LOS BÚFFERES DE AUDIO..............................98 FIGURA 31: BÚFFERES DE AUDIO COMO BÚFFERES CIRCULARES...................................................................................108 7 ........................................108 FIGURA 35: BÚFFER DE RECEPCIÓN DE PAQUETES RTP ADELANTADOS: VACIADO POR CONGESTIÓN.

.................................................54 TABLA 10: CABECERAS SIP SOPORTADAS POR EL PÁRSER.22 TABLA 4: CONFIGURACIÓN DE LAS LIBRERÍAS KDE...................92 TABLA 11: CABECERAS SDP SOPORTADAS POR EL PÁRSER...................23 TABLA 6: INFORMACIÓN SOBRE LAS VERSIONES DE LAS LIBRERÍAS QT/KDE ENLAZADAS...............................................................................................................12 TABLA 2: OPCIONES DE LA LÍNEA DE COMANDOS...........................................................................93 TABLA 12: DESCRIPCIÓN ABNF DE UNA SIP-URI...................................................23 TABLA 7: EJEMPLO DE MENSAJE DE ERROR EN XML.................23 TABLA 5: INFORMACIÓN ACERCA DEL AUTOR DEL PROYECTO....................................................21 TABLA 3: CONFIGURACIÓN DE LAS LIBRERÍAS QT..................................................................................................................................................................................................................................110 8 .....................................................................................................................34 TABLA 8: EJEMPLO DE INFORMACIÓN RECIBIDA AL PREGUNTAR POR CAPACIDADES 47 TABLA 9: CONSULTA DE LA CONFIGURACIÓN DEL AGENTE..........Indice de tablas TABLA 1: FICHERO DE CONFIGURACIÓN DE EJEMPLO........

debe ser expansible. deberá mantenerse constante a lo largo de las versiones que se puedan producir en el futuro. Podemos enumerar los tres siguientes: • • • Diseño de la interfaz de usuario. scripts. programas para la consola. Como es lógico se produce un mapeo entre lo que el usuario quiere y la implementación de esto. se introduce el concepto de señalización específica: debe consistir en procedimientos más sencillos que los que brinda SIP a bajo nivel.. Diseño del esquema general de la pila SIP ampliada.1.). En el segundo punto veremos cómo la pila SIP realiza estas actividades usando el protocolo SIP (y tal vez también otros si fueran necesarios). y para ello. Será una señalización lo suficientemente sencilla para abstraer el funcionamiento SIP a unos cuantos mensajes. Esta pseudo-señalización tiene una dificultad añadida en el hecho de que SIP todavía no se encuentra en un punto estable de su desarrollo. etc. pregunta de capacidades. Por lo tanto.931 o la H. sin que la inclusión de nuevos métodos modifique el funcionamiento de los antiguos. un servidor de streaming de vídeo/audio unidireccional o incluso un 'recolector' de flujos multimedia en múltiples formatos para redistribuirlos luego en formato de una sesión SIP (podríamos pensar en encorsetar en SIP formatos WindowsMedia de Microsoft.. El punto fuerte (ó débil... Vamos a centrarnos en explicar qué hace nuestro agente de usuario SIP y cómo lo hace. se da un mapeo más explícito entre las 'llamadas' a la pila que la interfaz gráfica realiza (y las respuestas que obtiene asociadas) y las acciones realizadas por la pila. Para acometer este objetivo vamos a marcarnos una serie de puntos que nos serán de gran utilidad para intentar explicar de forma clara este diseño. ni expondremos la organización del mismo. realizar y recibir llamadas. etc. Además. pero muy completa para aquellas que requieran de un control fuerte sobre las operaciones. pero también lo suficientemente específica como para dar funcionalidades complejas.. pero dejaremos la definición detallada de la implementación para la parte en la que se trata el diseño de bajo nivel. Toda esta señalización entre la pila y la interfaz la denominaremos señalización específica ó metaseñalización para diferenciarla de la señalización SIP y de otras (por ejemplo la Q. En esta sección no mostraremos código. pero lo más completa posible. QuickTime de Apple ó Real de Real Networks).323) porque se define específicamente para este proyecto. En el primer punto detallaremos qué es lo que ve el usuario al ejecutar nuestro agente y cómo puede operar con él de forma sencilla (configuración. ni relataremos las clases usadas.). Además. Diseño de la señalización específica GUI-pila. registro.. y la introducción que se realizó en el documento nº1 MEMORIA puede ser muy útil. La idea básica de este diseño en tres partes consiste separar claramente lo que es funcionalidad SIP y lo que no lo es. Este proyecto busca construir un soft-phone. Deberá ser muy sencilla para aplicaciones que busquen unas prestaciones limitadas. Habrá que tener muy presentes conocimientos concretos acerca de SIP. Pero SIP ofrece muchas más posibilidades: podríamos querer diseñar un contestador automático. o teléfono para realizar llamadas VoIP. y cada día se añaden 9 . INTRODUCCIÓN Por diseño de alto nivel entendemos aquella parte de nuestro proyecto que define de forma explícita pero general el comportamiento del mismo. dependiendo del resultado final) de este esquema es la definición de la interfaz entre la pila y los usuarios de la pila (interfaces gráficas.

características o se modifican las existentes. La siguiente gráfica muestra un ejemplo del diseño propuesto en tres módulos: Figura 1: Diseño modular del agente de usuario SIP 10 . Más adelante veremos las soluciones aportadas. Como se puede comprobar. el problema de su diseño es relativamente complejo.

Este teléfono deberá ser capaz de realizar llamadas de audio bidireccionales.2. es una premisa en este diseño que la pila SIP sea general y universal. En el caso concreto que tratamos el front-end es una GUI. En este proyecto en concreto se establece una relación N:N entre los servicios ofrecidos por la pila. deberíamos hablar en este punto de front-end y no de interfaz gráfica o interfaz de usuario. DISEÑO DE LA INTERFAZ DE USUARIO En este primer punto vamos a explicar todas las acciones que el usuario puede realizar con nuestro agente de usuario SIP. Comencemos pues a concretar cómo hemos hecho uso de la pila SIP para crear un soft-phone. usaremos las librerías Qt/KDE en GNU/Linux para crear una interfaz gráfica. por ejemplo. y que nuestra interfaz gráfica sólo debe saber cómo invocar un determinado método de la interfaz común cuando el operador humano así lo requiera. en SIP un agente de usuario es un extremo capaz establecer. configuraciones posibles y acciones. Pero a grandes rasgos. Detallaremos todas las ventanas. mantener y liberar sesiones) sea un soft-phone está marcado por esta interfaz con el usuario. Vamos ha hacer un estudio completo de esta interfaz diseccionándola en los siguientes puntos: 11 . el hecho de que nuestro agente de usuario (como se recordará. y los que la interfaz gráfica proporciona al usuario: el usuario podrá invocar todos los servicios de la pila. ó teléfono SIP para transmitir voz sobre IP. pero no olvidemos que es la pila quien brinda estas posibilidades. También deberá ser capaz de realizar el proceso inverso: preguntar al usuario humano por información cuando la pila así se lo indique. Las posibilidades en este terreno son infinitas si tenemos en cuanto los tres campos principales para las futuras extensiones de la pila: • Soporte para mensajería instantánea • Soporte para el streaming de vídeo • Soporte para transmitir SOAP sobre peticiones MESSAGE SIP (SOAP over SIP y los nuevos servicios Web) En el tercer punto del desarrollo hablaremos un poco más sobre qué le depara a SIP y cómo implementar eso con esta pila. basada en ventanas y totalmente integrada en el escritorio KDE de GNU/Linux. Pero supongamos que la pila continúa su evolución y proporciona. podremos conseguir funcionalidades muy diversas e interesantes. Como se ha mencionado ya antes en la memoria. pero éstos no son todos los proporcionados por SIP. o podría ser otra librería que usase de forma puntual nuestra pila SIP. La razón es obvia: en el marco de este proyecto la funcionalidad ofrecida se ha considerado ampliamente suficiente. soporte para mensajería instantánea: podríamos usar la pila (que físicamente sería una librería dinámica creada con GNU Libtool) para crear: a) un soft-phone con soporte para mensajería o b) un mensajero (messenger) que no soportase llamadas de voz. registrarse con un proxy y preguntar por sus capacidades a entidades SIP remotas. Realmente. Por lo tanto. Como hemos dicho. Veremos cómo manejar la pila SIP a través del uso de los procedimientos disponibles en la señalización específica para conseguir construir un soft-phone. nos damos cuenta de que usando una pila única y múltiples front-end o interfaces con el usuario. y que se use mediante una interfaz pública y definida. pero en otros podría no serlo: podría ser una aplicación con salida por la consola.

Así que de momento... dirección SIP. Para estudiar el formato de este fichero se adjunta el archivo de ejemplo que se distribuye en el tarball del proyecto: . etc. Existen disponibles dos formas de configurar el UA: a través de un fichero de configuración específico ó a través de la propia interfaz gráfica. códecs de audio disponibles. 2. claro está. usando siempre.1.1. se ha optado por una lista de tags en un fichero de texto. Se basa en editar unos tags que serán leídos por el front-end (es muy importante dejar claro que la pila no lee este fichero. Esto tiene la innegable ventaja de la sencillez.• • • • Configuración del soft-phone Iniciación Depuración Acciones disponibles 2. dirección IP actual. No se basa en ningún otro.)./doc/sua.1. Configuración a través de un fichero de configuración Este modelo basado en un fichero de texto externo es muy común en los entornos UNIX en general. Configuración del soft-phone En este punto vamos a enumerar las posibilidades de configuración de la pila a través de la interfaz que estamos detallando: es decir. un API estándar (como DOM ó SAX). El formato de este fichero es muy sencillo y ha sido creado específicamente para este proyecto. La forma de pasarle estos parámetros a la pila es muy clara y está definida en los procedimientos de la señalización específica. También sería más fácil acceder a su contenido. ni en ningún modelo establecido. La desventaja más clara de usar aquí XML es la dificultad añadida para realizar una tarea sencilla. también modificados a través de una serie de ventanas de configuración (que se verán en el siguiente punto). Pero la grave tara del ostracismo al que se le somete. Los parámetros necesarios se le pasarán en forma de una estructura C ó de una clase C++) y eventualmente.. para que la pila funcione de forma correcta deberá disponer de una serie de parámetros (nombre del usuario. Una variante más compleja pero muy atractiva es el empleo de XML para este tipo de ficheros de texto: el fichero sería autocontenido y mucho más fácil de editar para gente extraña a este proyecto. pero es labor del front-end ó interfaz recopilarlos y pasárselos a la pila.conf Tabla 1: Fichero de configuración de ejemplo 12 .

Las líneas que comienzan por el carácter almohadilla ('#') se consideran comentarios. 13 . Las líneas vacías no se tienen en cuenta.es UA_SERVER_PROXY: No UA_PUBLIC_ADDRESS: sip:public_user@somedomain. If not given. you can specifiy this field # you can also specify a port using sip:user@host:port # *** If you decide not to set this parameter.es UA_SERVER_USER: public_user_login UA_SERVER_PASSWORD: public_user_password En general se aplican tres normas generales: • • • Se busca un parámetro por línea.1 # UA_USER_NAME(optional) # name of the user. use the server as proxy server # "No".168.1. $(USER) or $(LOGNAME) # will be used UA_USER_NAME: Ibon Gotxi García # Audio Devices(optional) # ####################### UA_AUDIO_DEV: /dev/dsp UA_MIXER_DEV: /dev/mixer UA_AUDIO_PORT: 34000 # Registering (optional) # ###################### # UA_SERVER: your proxy/redirect server # UA_SERVER_PROXY: # "Yes".#Structure of the file: # header + double dot + space + data # Lines beggining with # are comments # # Sip (optional) # ###################### # UA_ADDRESS(optional) # the actual sip address of this host # this is usually something like sip:user@host # *** If you are using a static ip. use the server as redirect server(currently not supported) # UA_PUBLIC_ADDRESS: the stable sip address for u # UA_SERVER_USER: login for this server # UA_SERVER_PASSWORD: pass for this server UA_SERVER: sip:somedomain. ksua will try to # find one suitable sip address for u(sip:login@host_ip) # this is interesting if you have dinamic ip address # # the ip used by the ua will be derived from this address UA_ADDRESS: sip:ibon@192.

pero podría ser otro (aunque actualmente el soporte para otros protocolos de transporte no esté escrito) y en ese caso se debería especificar a través de la dirección SIP actual. Como se aprecia. porque deberán coincidir.168. pero no es necesario especificarlo. Es muy lioso para un usuario tener que configurar a mano esta dirección. Para este proyecto se ha dejado así porque ha habido siempre problemas más urgentes que solucionar. Como se ve. Por ejemplo. Mucha gente no sabe (y no tiene porqué saber) qué es una dirección IP y aunque lo sepan pueden estar usando una dirección dinámica. pero al final se han impuesto tres criterios para seleccionar qué debe ser configurable y qué no: • Nunca se incluirán cabeceras que puedan o deban derivarse de otras: esto parece bastante lógico al principio.1. Con un fichero así muchos usuarios inexpertos no podrán configurar adecuadamente su soft-phone porque tal vez hayan introducido dos espacios entre los dos puntos y el valor_de_cabecera. Es decir. pero requiere cierto estudio. • Cualquier espacio tras valor_de_cabecera será suprimido. por lo tanto no es lógico permitir especificar la dirección IP en otra cabecera aparte. Este comportamiento se puede cambiar si se desea. • No debe existir ningún espacio antes de Nombre_de_cabecera. • Todas las cabeceras deben ser opcionales: esto es una gran ventaja aunque conlleva cierto tiempo escribir el código que así lo permita.1:6060. transport=tcp” asume 6060/TCP.1” asume 5060/UDP pero “sip:ibon@192. lo que hace muy penoso el tener que editar el fichero de configuración cada vez que tu IP cambie. Pero tal vez futuras versiones deban llevar un fichero de configuración en XML. Para solucionar esto se construye la dirección SIP actual en dos pasos: primero. ¿Cómo escapamos el carácter espacio?. existe una cabecera para especificar la dirección SIP actual. que en principio usará la dirección IP del host en el que se ejecute. Como se ha mencionado antes. Para evitar tener que comprobar que ambas son iguales y manejar el error generado si no lo son. Pero el enorme inconveniente estriba en que no todo el mundo es cuidadoso al escribir texto. La ventaja consiste en que un fichero de configuración vacío funciona. en definitiva este fichero de configuración es una sucesión de pares cabecera/valor. se buscan en orden las variables de entorno LOGNAME y USER y el valor de la primera no nula será el campo de usuario de la dirección SIP. Para otros valores más complejos se realizan ciertas deducciones: el caso más típico es para la dirección SIP actual. A lo largo del proceso de desarrollo de este UA su número y tipo ha evolucionado mucho. porque asume el conocimiento de la dirección IP del host en cuestión.13. este formato particular tiene la ventaja de que es muy sencillo leer los valores valor_de_cabecera desde código C si la persona que escribe el fichero de configuración presta atención a todas las normas. • Debe existir un espacio (y sólo uno) entre Nombre_de_cabecera y valor_de_cabecera. se decide no permitir especificar la IP por separado. el problema se va complicando. La dirección IP se busca usando netdevice (mirar la 14 . • No debe existir ningún espacio entre Nombre_de_cabecera y los dos puntos.Para la definición de las entradas se asume un formato estricto: • El formato es "Nombre_de_cabecera: valor_de_cabecera" sin las comillas dobles de comienzo y final.168. Por ejemplo. Lo mismo pasa con el puerto en el que el UA escucha: por defecto se asume el 5060/UDP. El front-end que se ha diseñado asume ciertos valores por defecto. Para evitar inconsistencias se permite el uso de direcciones SIP completas y se evita duplicar parámetros. los dispositivos de sonido son /dev/mixer y /dev/dsp (lo cual es válido para la mayoría de los sistemas) y el puerto para los flujos multimedia es 34000/UDP. Además surge otro problema mucho más importante: ¿qué ocurre si valor_de_cabecera comienza o termina con un espacio?. y el resto los deduce del sistema. “sip:ibon@192.

página de manual GNU/Linux para netdevice . Como se ha establecido en la definición de las normas globales que rigen las cabeceras.0.transport=tcp). ya que con un conocimiento básico de SIP será fácil realizar la configuración: si alguien sabe cómo construir una dirección SIP para que el UA escuche en la dirección A.low level access to Linux network devices) ya que tal vez nuestra máquina tenga más de una interfaz de red (veremos más sobre esto en el diseño de bajo nivel).1. Veremos en más detalle el proceso de generación. conseguimos una dirección 'fresca' cada vez que ejecutemos nuestro UA. un host en forma de FQDN (sip:user@host. De esta forma.B. Por lo tanto. Si hay más de una interfaz se preguntará al usuario para que decida (obviando evidentemente la interfaz lo ó loopback). Además contribuye a que no sea necesario aprender cosas nuevas para manejar este UA. porque debemos decir qué le puede interesar cambiar al usuario más exigente.1. Para ello usamos las siguientes cabeceras: • UA_ADDRESS: el valor de esta cabecera deberá ser la dirección SIP actual. Como ya hemos mencionado antes en la memoria. Una vez que tenemos claro cómo deben ser las entradas en nuestro fichero de configuración y que intuimos qué parámetros deben ser incluidos y cuáles no. La dirección SIP actual y un nombre de usuario completo que aparecerá de forma textual. Y si estamos seguros de que nuestra IP es estática o de que vamos a usar otro puerto para SIP siempre lo podremos especificar mediante una entrada en el fichero de configuración. 2. pasemos a estudiarlos más en detalle.2nd_level_domain. Pero se espera que en un futuro. en el sentido de que a una dirección básica de la forma sip:user@host se le pueden añadir diversas extensiones como un password (sip:user:password@host) para autenticación básica. Sección SIP Básicamente existen dos parámetros elementales a configurar aquí.C. Si el usuario no especifica ningún valor. muchos parámetros están de forma hard-coded a lo largo del código.0.1. denominamos así a la dirección válida (si no existe soporte para NAT deberá usar una IP pública) pero no conocida del UA.1). Para que esta dirección actual se mapee a una dirección SIP pública (conocida por las personas que nos quieran localizar) se realiza el proceso de registro que veremos más adelante. pero se intuye que es extremadamente útil en el caso de usar direcciones IP dinámicas. Este modo de operar es altamente beneficioso porque evita el definir decenas de cabeceras para expresar el comportamiento del UA. Se ha optado por dividir el fichero en tres secciones: SIP. dispositivos de audio y registro. • Deben poder personalizarse todos los parámetros: esta otra premisa tampoco es fácil de resolver. Así. Como en el momento de realizar este proyecto no se ha buscado el desarrollar una versión estable del UA. un puerto (sip:user@host:6060) para indicar dónde escucha nuestro UA (por defecto se usa el 5060/UDP) ó un protocolo de transporte (sip:user@host:7070.1st_level_domain) ó de dirección IP (sip:user@10. Todas estas opciones y alguna más están soportadas mediante una clase que encapsula el comportamiento de las direcciones SIP. se recomienda especificar este valor únicamente en los casos 15 . nuestro front-end se encargará de generar una dirección correcta con ayuda del usuario si fuera necesario. le resultará directo configurar este front-end simplemente pasándole la dirección a la cabecera UA_ADDRESS. se ha optado por incluir sólo lo más básico. UA_ADDRESS acepta direcciones SIP completas.D y en el puerto 1234. aspectos como el tamaño de los datos multimedia en cada paquete RTP (para controlar el rendimiento) o la posibilidad de incluir de una lista de direcciones de contacto no-SIP deberán ser tenidos en cuenta. UA_ADDRESS es opcional. Deberemos asumir un diseño que satisfaga tanto al más meticuloso como al más despreocupado.

2. Sin embargo. La idea detrás de esto es que en los mensajes SIP puede haber múltiples cabeceras Contact (o una única con múltiples direcciones SIP de contacto) en las que se especifica cómo llegar al usuario de forma directa.. se posibilite la selección de la salida para el audio en forma de plug-ins (uno para OSS. posiblemente lejano. el valor de esta cabecera será una cadena de texto de longitud variable. De las tres secciones ésta es sin duda la que más trabajo de configuración sigue necesitando. que en SIP recibe el nombre de 'Display Name'. 16 . y cuando no. • UA_USER_NAME: se trata de otro parámetro también opcional que sirve para establecer el nombre real de la persona que esta usando este UA. Se trata éste de un caso muy parecido al que ocurre en el correo electrónico. ya sea a través de SIP o de otros medios. y consisten en decir a la persona a la que llamamos dónde puede contactar con nosotros. o estemos detrás de un firewall que no tenga abierto el puerto 5060/UDP y debamos escoger otro. Enumeraremos estos y posteriormente comentaremos posibles extensiones de esta sección. Por lo tanto. a día de hoy todavía no se soporta la posibilidad de usar esta cabecera. y muchos para almacenamiento en disco con reproducción o sin ella. etc. porque sería un poco diferente a las demás. porque se han incluido únicamente tres parámetros básicos. otro para ALSA. Las posibilidades son muy amplias. Aunque parezca no tener sentido. pues no se incluirá ningún 'Display Name' en las cabeceras que lo soporten. Hay que tener muy presente que para el acceso a los dispositivos de sonido se ha empleado únicamente los drivers OSS (Open Sound System). Sección de Dispositivos de audio En esta sección se describen las cabeceras necesarias para configurar los hilos de audio y el hilo RTP. al recibir una llamada. y que se adjunta a varias cabeceras SIP como From y Contact. 2. el UA te muestra una ventana con el nombre real de la persona llamante y no sólo con su dirección SIP. Sin embargo. números de teléfono o pares nick_name/servidor_irc para contactar a través de chat IRC.. de una forma muy sencilla: cada plug-in será una librería dinámica que el usuario decidirá usar y todos los plugins tendrán una función conocida (algo así como get_func_ptr) que devuelva una estructura con una lista de punteros donde se nos indican las funciones a usar para leer audio en un determinado formato ó para escribir de múltiples maneras (proporcionando un puntero a una función para cada operación).. Se prevé que en un futuro. esta cabecera también es opcional. la diferencia de UA_CONTACT con respecto a las anteriores estriba en que podrían existir múltiples cabeceras UA_CONTACT y se crearía una lista de direcciones de contacto que luego se pasarían a la pila para su inicialización. Siempre deberá existir como mínimo una cabecera Contact que contenga una dirección SIP (que generalmente coincidirá con la dirección SIP actual especificada en UA_ADDRESS) pero pueden existir otras y no ser direcciones SIP: se podrían especificar direcciones de correo electrónico (usando el tag 'mailto'). En general. ya sea directamente una IP o un nombre de dominio. • UA_CONTACT (en proceso de estudio): la inclusión de esta cabecera está actualmente en proceso de estudio y desarrollo.1.1. Cuando se especifique se usará.). donde un correo suele llevar asociada a la dirección del remitente una cadena de texto con su nombre real. esto no se ha llegado a implementar todavía por lo que nos ceñiremos a una salida únicamente con OSS. El entorno de uso más habitual es aquel en el que.en los que usemos una dirección IP estática. Hay que tener en cuenta además que la dirección IP que use el UA será la que se obtenga de la dirección SIP aquí especificada.

. actualmente sólo se usa RTP como protocolo de transporte para los flujos de audio. Pero para diversas configuraciones de red el usuario podría especificar otro (atravesar cortafuegos. pero sobre esto hablaremos más detalladamente en el diseño de bajo nivel. de su posibilidad de recuperar o no paquetes perdidos. Por otra parte. sirve para acceder al mixer físico de la tarjeta de sonido. se asume /dev/dsp como dispositivo DSP. ya que de esa manera podríamos querer usar una u otra en función de su rendimiento. TCP es mucho más apto que UDP para atravesar firewalls.. sonido monofónico). mientras que /dev/dsp usa muestras lineales sin signo de 8 bits. se utilizará el puerto 34000. Generalmente al usar OSS puede tomar dos valores: /dev/dsp y /dev/audio. es decir. dos reproductores de ficheros multimedia) sería interesante el mismo comportamiento para el protocolo de streaming (o transporte de información multimedia en tiempo real). Ya hemos visto que en esta sección hemos hablado de tres parámetros a configurar que nos permiten unas posibilidades muy básicas. etc. por ejemplo. 17 . siendo /dev/dsp un enlace simbólico a alguno de ellos.. ó del protocolo de transporte que usen (TCP ó UDP) ya que. De la misma forma que hemos visto que sería deseable usar plug-ins para seleccionar la forma en la que accedemos a los drivers de sonido (al estilo de xmms ó xine. Su uso y configuración se realiza mediante llamadas al sistema ioctl para control de periféricos. Pero este es un tema a tratar en el futuro. Sería muy interesante el disponer de una cabecera llamada algo así como UA_OUTPUT_PLUGIN que permitiese especificar una librería de enlace dinámico como las que hemos comentado para grabar y reproducir el audio. Estaría bien el poder disponer de varias pilas para transporte en tiempo real de información multimedia. También permite seleccionar la fuente del audio a la hora de grabar.. Si no se especifica.. Si no se especifica. se asume /dev/mixer como mixer por defecto. etc. Al igual que ocurre con el DSP.). que se escapa del enfoque de este documento de diseño..• UA_AUDIO_DEV: representa el dispositivo que se ha de usar para reproducir muestras de audio a través del DSP hardware en cuestión (tarjeta de sonido). Hace posible el cambiar los volúmenes de reproducción y grabación para varias fuentes de sonido. Una operación de lectura sobre este dispositivo graba audio de la fuente seleccionada (micrófono) y una operación de escritura reproduce el sonido en los altavoces. • UA_AUDIO_PORT: sirve para especificar el puerto UDP que usará el hilo RTP para mandar y recibir paquetes para transmitir los flujos multimedia (en el estado de desarrollo actual únicamente se utiliza un canal de audio. que generalmente será /dev/mixer. • UA_MIXER_DEV: este dispositivo. /dev/dsp1.. Si no se incluye este parámetro. Esto haría la pila SIP más reducida y más modular. no todos los sistemas tienen ambos dispositivos: /dev/audio es más propio de sistemas de Sun Microsystems (Solaris) mientras que /dev/dsp es más general en el resto de sistemas UNIX. Sin embargo. Se da la posibilidad de elegir este dispositivo al usuario porque puede ser común tener varios dispositivos DSP (usando varias tarjetas de sonido) que se numerarán como /dev/dsp0. NATs. La diferencia entre ellos es que /dev/audio acepta muestras de audio codificadas con la ley logarítmica mu. y mediante este parámetro se le da al usuario la posibilidad de seleccionar el que más le convenga. podrán existir varios mixer en el sistema.

Todas las respuestas a estas peticiones llegarán no al proxy. sino al UA. También debemos tener claro a estas alturas cómo se realiza el procedimiento de registro a bajo nivel (aunque sin duda lo comentaremos más adelante). En esta cabecera esta. Todavía no ha sido probado contra servidores 'forking proxy'. por lo que deberemos incluir los cinco para realizar la tarea ó alguno ó ninguno para no registrarnos. necesitaremos como mínimo cinco parámetros que explicamos a continuación. únicamente deberemos escribir la dirección del proxy tal y como se nos dé para cada cuenta en particular. ni mucho menos. al crear la cuenta se nos indicará la dirección pública asociada y un login y password. los servidores proxy pueden ser proxy 'normales' o forking proxy. Así. si un usuario llamado ‘igotxi’ tiene una cuenta en el ‘dominio domain. sino que se mandará una copia de la petición inicial (con la inclusión de algunas cabeceras.com” y su proxy será “sip:domain. pero ilustra un caso típico.1. • UA_SERVER: esta cabecera contiene la dirección SIP del servidor con el que nos registramos. Si el servidor funcionase como un 'forking proxy'. De alguna forma. el servidor esconde al usuario todo el proceso de localización de la dirección SIP actual del usuario llamado. ésta será típicamente “sip:igotxi@domain. por lo que será este el encargado de discernir contra qué UA debe establecer la sesión. Este comportamiento supone simplificar los servidores y hacerlos más robustos. Se establecerá en el servidor un proceso iterativo a través de la lista de direcciones de contacto asociadas a la dirección pública hasta que la llamada sea aceptada.com”. pero precisamente carecen de campo user. Existen dos tipos de servidores. los redirectores devuelven ante una petición de inicio de sesión una respuesta de redirección (3xx) con la lista de direcciones de contacto para que sea el UA el que implemente el proceso de iteración. el registro en SIP consiste en mapear una dirección SIP pública con una lista de direcciones SIP de contacto: posiblemente alguna de ellas será también la dirección SIP actual del agente de usuario que estamos configurando. servidores proxy ó servidores redirectores. La forma de conseguir cuentas en servidores SIP es irrelevante a este proyecto: un ejemplo típico es a través de formularios web.2. la interacción entre el UA y el servidor proxy se 'transparenta' dentro del proceso de diálogo global. éste acepta el uso de servidores proxy tradicionales y de servidores de redirección. no se iterará a través de las direcciones de contacto. como una nueva Via para el servidor) a todas las entradas en el 'location service' (mapeo entre dirección pública y lista de direcciones de contacto) de forma simultánea. • UA_SERVER_PROXY: esta cabecera permite especificar si el servidor de nuestro dominio debe actuar como un servidor proxy (usando el valor "Yes" ó "yes") ó como un servidor de redirección (usando "No" ó "no"). Necesitaremos esos valores para las siguientes cabeceras. De esta forma. Más adelante veremos más en detalle qué hacer con esta información. Esta cabecera toma como valor por defecto 'Yes' para usar servidores proxy. Este comportamiento no es obligatorio. Se incluye esta posibilidad porque están bastante extendidos los servidores que funcionan de forma dual (proxy/redirector) y se permite que el usuario escoja la forma de funcionamiento especificando un tag llamado 'action' en las cabeceras de contacto. Las direcciones SIP de los proxies suelen ser semejantes a las de los usuarios. Sección de Registro Como ya se ha comentado varias veces. Ya se han comentado las diferencias entre ambos comportamientos: funcionando como proxy.3. Hay que recordar que si no se suministra alguno de ellos el registro no se intentará. distinción no importa. 18 .com’. A su vez. Por último.1. rechazada ó no haya ningún agente de usuario atento a las peticiones (Busy Everywhere). pero hace más complejos los clientes. Para que tenga éxito. En el momento actual de desarrollo de nuestro UA.

Habitualmente usará un dominio en lugar de un nombre de host y mirando en los registros SRV del DNS de ese dominio podremos conocer el servidor SIP responsable del dominio en cuestión. En el servidor se creará una lista basada en prioridades (location service). pero nunca es un requisito obligatorio). necesitaremos un mecanismo para hacer saber al servidor SIP que conocemos el par login/password asociados a la dirección SIP pública. En cada lugar que esa persona frecuente existirá un UA con una dirección SIP no-pública. puerto y protocolo de transporte). de forma que automáticamente se asignen prioridades más altas a los registros más recientes. De esta manera podemos implementar configuraciones en las que si el UA principal (el primero de la lista) no contesta. para que alguien escuchando la red no pueda enterarse. Llegados a este punto. lo cual no es nunca una buena idea. • UA_SERVER_USER: se puede comprobar que existe una clara prioridad para proteger el acceso al 'location service' frente a terceros usuarios. No ha ningún problema para escribir esta información en claro. como el uso de formularios Web ó autómatas por correo electrónico). La manipulación de estas prioridades se realiza mediante un campo 'q' en las cabeceras de contacto. La forma más obvia de autenticación es mediante el uso de un nombre de usuario y un password asociados a una dirección SIP pública. El hecho es que se asume que se tiene un triplete conseguido de forma segura (nótese que no es necesario que exista una relación entre el login y el campo 'user' de una dirección SIP. Este comportamiento es muy útil para implementar contestadores automáticos. y se comparan los resultados. Sin embargo. y usando el par login/password.• UA_PUBLIC_ADDRESS: en esta cabecera se especifica la dirección SIP pública de nuestro UA. y se supone que usa procedimientos fuera de banda (ó procedimientos no relacionados con SIP. basada en la dirección de transporte en la que está escuchando (triplete dirección IP. Para proteger el acceso se implementan políticas de autenticación. porque basándose en unos parámetros aleatorios generados por el servidor y por el UA. que incluiremos al firmar nuestro correo electrónico y que procuraremos mantener constante a lo largo del tiempo. pero deberemos hacerlo de una forma segura. y un UA puede poner su dirección en lo alto de la lista para que sea a él al que primero se le consulte. y no pasa nada por que otra gente pueda leer este fichero de configuración y conocer nuestro login. que podrían poner su dirección actual en la parte más alta de la lista y de esta forma recibir todas las llamadas que se pretendiese establecer con el usuario legítimo. Se pretende que la dirección SIP pública sea única para localizar una persona (o incluso un 'bot') en múltiples lugares. Aunque la autenticación por resumen es más complicada que la básica (requiere realizar un 'challenge' al servidor) se puede decir sin lugar a dudas que es mucho más efectiva. con el password el tema es muy diferente. este sistema cuenta con la ventaja de que los valores aleatorios generados caducan en un lapso de tiempo concreto. para que el servidor pueda saber de forma exacta que quien accede a la lista de direcciones de contacto es el titular de la dirección pública. Nunca debería usarse la autenticación básica porque manda el par login/password en texto claro. Será la dirección que todo el mundo debe conocer para llegar a nosotros. La cabecera UA_SERVER_USER es evidentemente el lugar para configurar el login de la cuenta que usemos. por lo que no tiene sentido copiar un resumen de la red y pretender usarlo más adelante. Todos esos agentes registrarán contra el servidor SIP su relación dirección pública/dirección actual. En el servidor se realiza el mismo proceso. Existen dos procedimientos: 'Basic Authentication' (autenticación básica) y 'Digest Authentication' (autenticación por resumen). podremos acceder a otros UAs secundarios que respondan a las peticiones. 19 . Además. La autenticación por resumen es mucho más atractiva. debiéndose obtener el mismo resumen para autenticar al usuario. Suelen coincidir para favorecer la sencillez. genera un resumen criptográfico (usando una función hash MD5) que es enviado. La forma de conseguir el triplete login/password/dirección no se especifica en SIP. como veremos a continuación.

porque el hecho de incluir una nueva entrada modifica el diseño de la interfaz gráfica para el acceso a los parámetros. De esta manera se confiaría la seguridad de nuestra cuenta SIP a la seguridad de nuestra clave privada. deberemos establecer una política para evitar que otras personas conozcan nuestro password. Configuración a través de un sistema de ventanas Este segundo modelo de configuración del UA es muy interesante. colocándolo en un subdirectorio específico. Sin embargo. Este es el método más sencillo y eficiente de protección. es un campo a proteger. Evidentemente. para evitar que alguien acceda al servidor sin nuestro consentimiento y conocimiento. a mi modo de ver se impondría una segunda tendencia. Pero vayamos por partes. pero encriptar el password. La idea básica que se trata de exponer en este punto es que editar un fichero de configuración es un proceso muy poco amigable. posiblemente usando criptografía asimétrica. y se dispondría de un botón para 'cargar' una nueva configuración. dotado únicamente de permiso de lectura para el propietario. pero tiene el inconveniente de que almacenar una clave la hace vulnerable. porque la clave no se queda almacenada para que alguien la lea a posteriori. esto son sólo hipótesis para un futuro.conf. restringiendo el acceso al fichero de configuración. Nótese que este puede ser un proceso relativamente complejo: se deberá parar la ejecución de todos los hilos de servicio SIP. El UA que estamos desarrollando se encuentra naturalmente en una fase preliminar. aunque actualmente se encuentre en proceso de desarrollo.• UA_SERVER_PASSWORD: en esta cabecera se establece el password que se debe usar para calcular el resumen criptográfico que usaremos para autenticarnos con nuestro servidor SIP y así poder modificar nuestro 'location service'. de manera que todos los procesos de registro aparecerán de forma transparente al usuario. y que los usuarios inexpertos rehusan con frecuencia. De cualquier forma. • Incluir el password en este fichero. Por lo tanto. De momento. El segundo se basa en no confiar en los permisos de acceso. en la que no se ha tomado ninguna medida para proteger este campo en el fichero de configuración.1. 2. seguirá en desarrollo hasta que la lista de cabeceras aceptables en el fichero de configuración en modo texto sea estable. y pedirle al usuario que lo introduzca mediante una caja de diálogo cada vez que se necesite. únicamente contamos con la posibilidad de incluir el password en claro en el fichero de configuración sua. Después se deberá arrancar de nuevo el hilo generador y escribir la nueva configuración en el fichero de texto. En la siguiente figura se presenta una propuesta de lo que podría ser esta ventana de configuración de la que estamos hablando. parar el hilo generador de hilos y liberar todos los recursos asociados. Para protegerlo se podrían intentar dos métodos: el primero. La innegable ventaja es la mayor facilidad de uso. tiene la enorme dificultad de que el usuario tiene que introducir manualmente el password y eso es algo que no gusta y bastante propenso a errores. 20 . Los parámetros aparecerían en cajas de texto. Es más. En este sentido se imponen dos grandes tendencias: • No incluir el password en el fichero de configuración. Sería mucho más conveniente disponer de una sucesión de ventanas (o de una única ventana con varias pestañas) para ver la configuración actual y poder cambiarla. Pero conforme avance su desarrollo.2.

Por lo tanto. Argumentos por la línea de comandos Después de que tenemos nuestro fichero de configuración escrito. no de hacer software 'amigable'. y posiblemente volvamos a decir. este proyecto trata de investigar y de desarrollar. Vamos a dividir esta sección en dos partes: una para hablar de los argumentos pasados por la línea de comandos y otra para estudiar los primeros momentos en la vida de nuestro agente.1. Fundamentalmente por este motivo se ha decidido posponer el desarrollo de esta parte del proyecto hasta que la pila sea más estable. haciendo las modificaciones bastante pesadas para el programador. la lista de parámetros que puede aceptar la pila todavía no es estable y puede variar si deseamos una nueva funcionalidad. Iniciación En este punto vamos relatar qué ocurre desde que ejecutamos el agente hasta que el mismo se queda en modo 'latente'. hablaremos siempre de las acciones que realiza el front-end sobre la interfaz con la pila (que hemos denominado sistema de señalización específica ó metaseñalización). Por lo tanto.KDE's sip user agent Generic options: --help --help-qt --help-kde --help-all Show help about options Show Qt specific options Show KDE specific options Show all options 21 . a la espera de acciones del usuario ó de la recepción de peticiones a través de la red.Figura 2: Propuesta para ventana de configuración Como ya se ha dicho en varias ocasiones. Además.2. como se ha podido comprobar. Además. El ejecutable acepta argumentos por la línea de comandos (como todo programa en UNIX) que pasamos a describir: Tabla 2: Opciones de la línea de comandos Usage: sua [Qt-options] [KDE-options] [options] KSua . conseguir que el proceso de configuración sea sencillo no entra en las prioridades inmediatas. 2. 2.2. deberemos ejecutar el programa que se comporta como un UA SIP. dejando para luego el cómo esas acciones son realizadas por la pila en sí. las ventanas asociadas a la configuración deberían variar también.

y que sirven para dar una visión global del ejecutable y de los parámetros que acepta por la línea. --fg. --btn. --inputstyle sets XIM (X Input Method) input style. --cmap Causes the application to install a private color map on an 8-bit display. --rtp_test <l r c> Rtp test "lip:lport rip:rport coding" Se puede comprobar claramente que existen básicamente tres grupos de opciones a pasar al ejecutable por la línea de comandos. --visual TrueColor forces the application to use a TrueColor visual on an 8-bit display. --version --license -- Show author information Show version information Show license information End of options Options: -c. como la fuente usada. --background <color> sets the default background color and an application palette (light and dark shades are calculated). --noxim disable XIM. Si invocamos el ejecutable con la opción --help-qt nos aparecerá información sobre cómo configurar algunos de los parámetros referentes a las librerías Qt. use -dograb to override. Possible values are onthespot./src/sua --help./src/sua --help-kde y obtendremos un menú como el siguiente con ayuda: 22 . Así. --title <title> sets the application title (caption). --font <fontname> defines the application font. es decir. que son las que se visualizan arriba. el tipo de colores de fondo o cuestiones relacionadas con el teclado y el ratón. --bg. . --nograb tells Qt to never grab the mouse or the keyboard. --foreground <color> sets the default foreground color.--author -v. el texto presentado aparece al invocar a sua (nuestro UA se llama sua: SIP User Agent) con la opción --help. --session <sessionId> Restore the application for the given 'sessionId'. overthespot. if the application is using the QApplication::ManyColor color specification. --fn. offthespot and root. --name <name> sets the application name. --config <config_file> Alternative config file -r. --button <color> sets the default button color. Por un lado tenemos las opciones generales. --sync switches to synchronous mode for debugging. --dograb running under a debugger can cause an implicit -nograb. --ncols <count> Limits the number of colors allocated in the color cube on a 8-bit display. Concretamente: Tabla 3: Configuración de las librerías Qt Qt options: --display <displayname> Use the X-server display 'displayname'. --im <XIM server> set XIM server. Para ver las opciones específicas para configurar las librerías KDE tendremos que ejecutar sua con la opción .

--dcopserver <server> Use the DCOP Server specified by 'server'. La otra opción específica es --r ó --rtp-test que nos permite usar el UA únicamente para generar y recibir flujos multimedia usando RTP. puede ser usada para comprobar la probabilidad que tienen dichos flujos para alcanzar un destino. --waitforwm Waits for a WM_NET compatible windowmanager. Como ya se ha mencionado. Así.0.Tabla 4: Configuración de las librerías KDE KDE options: --caption <caption> Use 'caption' as name in the titlebar.es> O si quisiéramos ver las versiones tanto del ejecutable como de las librerías usadas./src/sua --version: Tabla 6: Información sobre las versiones de las librerías Qt/KDE enlazadas Qt: 2. usaríamos . A esta opción --rtp-test se le pasan cinco parámetros: los pares dirección IP y puerto UDP para cada extremo de la comunicación y el códec para el audio. Esta opción. --miniicon <icon> Use 'icon' as the icon in the titlebar. Más adelante veremos en detalle esta opción que da como resultado un comportamiento totalmente diferente del UA.1 KSua: 0. si quisiéramos ver el nombre del autor del software usaríamos . to get core dumps. esta opción para testar el funcionamiento de RTP se usó para afinar en la calidad del audio transmitido y recibido: eliminación de micro cortes. donde se puede comprobar que existen parámetros para configurar desde el icono de la aplicación hasta el estilo de la interfaz gráfica./src/sua --author: Tabla 5: Información acerca del autor del proyecto KSua was written by Ibon Gotxi Garcia <igotxi@wanadoo. PCM con ley mu y GSM.. que no da como resultado la ejecución de un UA sino la de únicamente un test para RTP. Por una parte. para atravesar firewalls ó para usar la compresión más adecuada obteniendo unos resultados determinados. El segundo grupo de opciones son las más generales.3. --style <style> sets the application GUI style. Esta opción es muy útil si queremos realizar pruebas con una nueva configuración porque de esta manera no tendríamos que tocar el directorio local para nuestro UA.0-alpha3 Por último. --geometry <geometry> sets the client geometry of the main widget.1. tenemos la opción -c ó --config que nos permite especificar un fichero de configuración para sua en una ruta cualquiera. --icon <icon> Use 'icon' as the application icon.1 KDE: 2. En este proyecto en concreto. pequeño estudio sobre la cantidad de audio a transportar en un paquete RTP. hay actualmente tres códecs soportados: PCM con ley A. --nocrashhandler Disable crash handler.. 23 . existen dos opciones que son específicas para este agente de usuario. etc.. pero tiene una gran utilidad en ambientes de desarrollo.

conf. esto es. desde donde se está llamando al agente. Concretamente. este comportamiento se justifica por una posible compatibilidad futura. pero sin éste no lo hará. Creación de la ventana de depuración (oculta).2. Lectura/Generación/Comprobación de la configuración. por lo que no las comentaremos. se usará. La secuencia de acciones es la siguiente: • • • • • • • Creación de la ventana principal (oculta).conf en el directorio actual. como decimos. se realizan las acciones encaminadas a levantar el UA. El primer paso a dar consiste en localizar el fichero de configuración. Por lo tanto vamos a centrarnos en la Lectura/Generación/Comprobación de la configuración del agente. siempre se pasará a la clase encargada de realizar el proceso de configuración una cadena que contendrá un valor no nulo si el usuario ha especificado un fichero ó nulo si no lo ha hecho. ésta sea comprobada. Si no es así. para lo cual será necesario tener presente la estructura del fichero de configuración ya visto. todos los parámetros de configuración del UA pueden ser deducidos ó se pueden tomar valores por defecto. ya que con un fichero vacío el UA funcionará. Destrucción de la ventana de inicio. La iniciación de la pila SIP y el proceso de registro los veremos más adelante. donde la variable de entorno $HOME contendrá el directorio del usuario y . en el futuro un fichero de configuración vacío tal vez no sea válido.2. y por eso se ha decidido explícitamente exigir su presencia para poder ejecutar el agente.2. Como se ha comentado. y no sabemos si en una futura versión de la pila necesitaremos un parámetro que no sea ni deducible ni sustituible por un valor por defecto. Como ya hemos explicado. esto ocurre hoy. A partir de este momento. si así ha sido configurado el UA. Pero. Esto nos puede hacer pensar en un funcionamiento poco ortodoxo. se buscará uno en estos dos lugares: • Primero en el directorio de configuración del agente.sua será el lugar donde se almacenen todos los datos relacionados con nuestro UA. Intento de registro. En principio siempre se confiará primero en la decisión explícita del usuario. se encuentran dentro del constructor de la clase de aplicación (y sobre esto trataremos en el tomo de bajo nivel). La ventana principal pasa a ser visible. se pasaría a buscar el archivo sua. Se buscará el archivo sua. Si existe dicho fichero de configuración especificado por el usuario. Por lo tanto. en el apartado correspondiente y por eso tampoco se tratarán aquí. que hablando a bajo nivel. • Si dicho fichero no se encontrase. la ruta total será: $HOME/. Iniciación de la pila SIP creando el hilo generador de hilos. Sin embargo. Inicialización básica del agente Una vez que comienza el programa. Las tres primeras acciones de la secuencia y la última no son relevantes desde el punto de vista técnico. 24 .sua/sua. y nada más empezar el proceso de configuración nos aseguraremos de que si se ha pasado una ruta para el mencionado fichero.conf. se crea el objeto de aplicación con el valor pasado por la línea para localizar el fichero de configuración si existiese. Creación de la ventana de inicio (visible). y por lo tanto un fichero de configuración vacío es totalmente válido. y en el caso de que tengamos la seguridad de no estar dentro del test para los hilos de audio/RTP.

Figura 3: Ventana de inicio y presentación Mientras esta ventana se encuentra visible. En la figura 3 se puede observar esta ventana. si el fichero no existiese se presentaría un caja de diálogo con el problema y se abandonaría la ejecución. se busca la dirección IP de la máquina. mediante la variable de entorno $LOGNAME. se ejecuta un método llamado "Parameters Init_Window::Load_Params(QString)" que precisamente tomando un path hasta el fichero devuelve un objeto Parameters que contiene la configuración del UA. se construye una: para el campo de usuario se toma el nombre de login del usuario en la máquina. la pública y la de servidor) para evitar malformaciones. pero de una forma un poco especial. tales como la selección de la dirección IP ó el fichero de configuración usado. Pero supongamos un entorno normal de ejecución.Llegados a este punto. Una vez localizado el fichero se visualizará la ventana de inicio. Esta función dispone de un temporizador que va actualizando la barra de progreso y va leyendo entradas del fichero. La forma tradicional de encontrar la dirección IP en un sistema UNIX pasa por llamar a la función de la librería estándar de C "gethostname" y con el nombre de máquina devuelto. llamar 25 . Analicémoslo por partes: • Si UA_ADDRESS no está presente. comprueba su validez. con su línea de texto donde se indica que en este preciso momento se están cargando los parámetros y cómo también se imprime por la consola algunas conclusiones importantes. concretamente se realiza una chequeo más exhaustivo para las direcciones SIP (la actual. su correspondiente miembro en el objeto Parameters queda vacío. Una vez 'cargada' la configuración se pasa a generar los valores no especificados. Si una cabecera no está presente o si no posee un valor admisible. Para cada parámetro. que presenta una barra de progreso y un cuadro de texto para informar de manera sencilla al usuario sobre el arranque del agente. Para el campo hostport.

a "gethostbyname" para calcular su IP (en principio no hace falta la dirección IP para construir el campo hostport de una dirección SIP. se puede usar un nombre de máquina. Para cada interfaz de red. Lo haremos pasándole una estructura 'ifreq'. pasándole una estructura 'ifconf'. hay que realizar dos llamadas ioctl. Lo importante de este proceso es que para todas las interfaces. Después de esta llamada. pasándole como argumento la estructura 'sin_addr' de la estructura 'sockaddr_in'. La primera de ellas será SIOCGIFFLAGS. eth1 ó ppp0) y el campo 'ifr_addr' que contendrá la dirección IP asociada a la interfaz pero en formato binario. comprobaremos que no estamos estudiando dicha interfaz local (no tiene el flag IFF_LOOPBACK puesto a uno) y llamaremos a ioctl por segunda vez. Con estos datos. eth0. Esta operación se realiza en el método "QHostAddress Init_Window::Get_Local_IP()" usando 'netdevice'. esta vez la llamada será SIOCGIFADDR para obtener dos datos que nos interesan en otra estructura 'ifreq': el campo 'ifr_name'. su dirección de enlace) pero no nos son interesantes en este momento. ya tenemos la IP de la máquina y no son necesarias más operaciones. pero en general. y nos servirá para discriminar la interfaz de red local (lo ó loopback). que es el sistema que existe en GNU/Linux para el acceso a bajo nivel a las interfaces de red. a la que le pasamos la lista de dispositivos. pero para construir determinadas cabeceras y para SDP necesitamos la dirección IP en formato decimal de puntos). Si la lista sólo tiene una entrada. Podríamos haber obtenido más datos (por ejemplo. deberemos estar preparados para poder ejecutar el UA en casos mucho más complicados. distintas de la local. Se impone por lo tanto extraer la lista de dispositivos de red y sus direcciones IP. crearemos una lista de dispositivos (en un objeto QdeviceList) de entre los cuales el usuario deberá elegir uno. por ejemplo. en este momento se crea una ventana de tipo 'Select_Ip_Window'. Sin embargo. hemos obtenido su nombre y su dirección IP asociada. Un ejemplo lo tenemos con los ordenadores que poseen varias interfaces de red. debemos conocerlas todas y darle luego a elegir al usuario. algunas con direcciones públicas y otras con privadas. deberemos copiarla a una estructura 'sockaddr_in' (usando memcpy) y luego convertirla usando la función 'inet_ntoa'. Pero puede no ser así. y que será la encargada de mostrar al usuario todas las posibilidades y permitirle que elija una. que contiene el nombre de la interfaz (que podrá ser. En la siguiente figura se aprecia el aspecto de esta ventana: 26 . Por lo tanto. y almacenaremos los flags en su campo 'ifr_flags'. este procedimiento es válido para sistemas con una única dirección IP. Por eso. Para pasarla a formato decimal de puntos (en definitiva a una cadena de texto). Esta estructura nos servirá para iterar a lo largo de todas las interfaces de red. El proceso es sencillo: se crea un socket y se realiza sobre él una llamada ioctl SIOCGIFCONF.

Además. se ha añadido algo más de información al estudiar el nombre de las interfaces y añadir una cadena de texto al nombre de dicha interfaz. podemos construir la dirección SIP actual. encargado de encapsular el funcionamiento de una dirección de red.Figura 4: Ventana de selección de dirección IP Consta básicamente de una caja de lista (objeto del tipo QlistBox) en la que se muestran todas las interfaces y sus direcciones IP. con el nombre de usuario conseguido mediante el examen de $LOGNAME y la dirección IP así seleccionada. Si a UA_AUDIO_PORT no se le da ningún valor. Cuando la selección este hecha. la dirección IP que use el agente a lo largo de su vida será siempre la calculada por este método. Si UA_MIXER_DEV no se especifica se usa '/dev/mixer'. como las interfaces ethX son Ethernet. Sin embargo. La ejecución del agente quedará bloqueada hasta que el usuario decida qué dirección IP va a usar. Por ejemplo. Esto es muy lógico. También hubiese sido perfectamente válido. Este comportamiento se basa en la premisa de que para intentar el registro se deben rellenar las cinco cabeceras relacionadas (servidor. Además. porque no tiene sentido intentar registrar una 27 • • • • . invalidará a las demás. Por lo tanto. Si no se ha especificado ningún servidor SIP mediante UA_SERVER. dirección SIP pública. entonces los miembros de la clase Parameters asociados al registro quedarán vacíos. se usará el puerto UDP 34000. que no será pública. • Si UA_USER_NAME no se especifica. modo. login y password) y si alguna de ellas no toma valor. se destruirá el objeto ventana y la función "QHostAddress Init_Window::Get_Local_IP()" devolverá un objeto QHostAddress. para que otros agentes puedan llegar a nosotros de forma directa. de manera similar a como ocurre en el correo electrónico. esta cabecera se pone a disposición de los usuarios para que incluyan su nombre real en las peticiones. y que igualaremos a la primera de las direcciones de contacto. Como ya se ha comentado. Lo mismo ocurre con las interfaces PPP que se llaman pppX. se toma el nombre de usuario que se encuentra en la variable de entorno $USER. Si UA_AUDIO_DEV no se especifica se usa '/dev/audio'. pues así se indica. podría haberse dejado en blanco y no incluir ningún nombre.

la primera entrada de la lista contendrá un puntero al hilo maestro. denominada 'codecs'. porque no se apoyarían en ningún código. la forma de implementar el códec es independiente de la pila. y cargará el objeto 'Codec' con punteros a las funciones de inicialización del códec. Se crea de forma dinámica (en el montículo ó 'heap') un objeto 'SUA_Call_Event'. y ésta no puede ofrecer códecs más allá de los previstos por el programador de la pila. hay que señalar el hecho de que con estas cabeceras no se realiza ningún proceso de generación ó deducción de valores.QCustomEvent*)" para mandar físicamente el evento. que es el encargado de contener los eventos relacionados con la señalización específica y en el ámbito de la señalización de la llamada. de vídeo y para los códecs tanto de audio como de vídeo. pero claro. del front-end. una vez fijada la función que nos servirá de guía para el plug-in. 28 . bastante más complicado. pero veamos un poco acerca de cómo se lanza el hilo de registro: 1. Por lo tanto no tendría sentido si dispusiésemos de una entrada en el fichero de configuración para especificar distintos códecs. porque en tal caso. Así pues. da una idea de hacia dónde se podrá encaminar la pila en futuras versiones. que lanzará el hilo generador de hilos (o hilo maestro) y si es necesario registrarse contra un servidor SIP. Sin embargo. la lista se construye de forma estática y por el propio front-end. que en principio podría pensarse posible (deducir el login a partir del campo de usuario de la dirección SIP pública). El tema de estos hilos SIP se tratará en el apartado correspondiente a la pila SIP. El hilo de ejecución retornará al constructor del objeto aplicación. Por otra parte. porque no podemos permitir nunca que el hilo principal (el que se encarga de manejar el front-end) se bloquee en una llamada a la pila. Antes de terminar la ejecución de la función "Parameters Init_Window::Load_Params(QString)" se provee de valores a una lista encadenada contenida en el objeto 'Parameters'. Se impondrá una arquitectura basada en plug-ins: para acceso a los dispositivos de audio. llamará a una función de guía en la librería. Usamos la función "Qapplication::postEvent(QObject*. codificación y decodificación. acaba el proceso de carga y deducción de la configuración del agente. Y además. Esto ocurre así porque los códecs están integrados en la pila. Hay que tener en cuenta que una de las características de los eventos en Qt es que son asíncronos. Se prefiere dejar toda la configuración al usuario. y por supuesto. Tiene la ventaja de que. Realmente este tipo de objeto sólo almacena un número (tipo del códec) y una cadena de texto (descripción del códec). sin datos adicionales. Para localizar este hilo maestro se usa un variable global llamada 'th_list' que contiene una lista de todos los hilos de la aplicación. Este tipo de diseños es mucho más modular y dinámico que el que hemos propuesto. esta señalización específica rige la forma en la que el front-end pide a la pila que realice tareas y la forma en la que la pila devuelve los resultados a la interfaz gráfica. lanzará el correspondiente hilo de registro. 2. cuando creemos un objeto 'Codec' de un tipo 8 (PCM ley mu) el constructor de la clase se encargará de cargar la librería dinámica correspondiente (o incluso la especificada en el fichero de configuración). Como vimos. que almacenará en cada nodo un objeto de tipo 'Codec'. Se manda este objeto de evento de llamada al hilo generador de hilos. Este comportamiento es ideal para esta aplicación. las ventanas se quedarían 'congeladas' y ni se repintarían ni aceptarían datos de entrada del ratón ó del teclado.dirección contra un servidor si no conocemos el password ó la propia dirección a registrar. Concretamente se crea un evento de llamada de tipo "W_Register". Con esto último.

De esta forma. En realidad no es una facilidad novedosa. y nos sumergimos de lleno en una programación multihilo gobernada por eventos inter-hilo y señales intra-hilo (no señales UNIX. y en cuanto llame a la función. el llamante pasará un puntero a la función de respuesta. Estos eventos llegan porque las librerías Qt se encargan de ejecutar en el hilo del front-end la función "void Init_Window::CustomEvent(QCustomEvent*)" pasándole como parámetro el objeto de evento de llamada que habrá creado la pila SIP (concretamente el hilo de registro de esa pila) y que ha querido que nos llegue para finalizar esta inicialización del agente. 3. sin que dicha función llamada haya empezado siquiera su ejecución. el llamante no se bloquea y la función llamada tardará el tiempo que sea. 4. por lo que abandonamos ya la programación secuencial tradicional (un único hilo y ejecución paso a paso). Durante el tiempo que tarda el proceso de registro. y constituye la actual base para la implementación de la señalización específica. para resumir este punto sobre la inicialización del agente vamos a incluir el siguiente cuadro: 29 . El hilo principal detecta el final del registro mediante la recepción de un evento en sentido de pila a GUI de uno de los dos tipos siguientes: "W_Register_Successfull" ó "W_Register_Unsuccessfull". y pasar al siguiente estado. Dentro de la función principal del ejecutable (main) se llama al miembro 'exec' de dicho objeto aplicación. A partir de entonces. ya que existe también en C# con los delegados ó en CORBA con las llamadas asíncronas a procedimientos remotos. sino señales Qt para manejo de las interfaces gráficas). notificando su final mediante el puntero a la función de respuesta. finaliza el constructor del objeto aplicación de nuestro agente de usuario SIP. Por último. Una vez terminado el proceso de registro.Esta característica de los eventos es ampliamente usada en este proyecto. el control le será devuelto. el hilo del front-end se dedica a repintar la ventana de inicio y a incrementar el valor de la barra de progreso cada vez que salte un temporizador. La idea detrás de todo esto es la siguiente: llamar a una función que no retorne ningún valor por la pila y que acepte como parámetro un puntero a la función que se debe ejecutar cuando se acabe el procesamiento.

que es precisamente el que buscamos. sobre la forma en la que se realizaba la inicialización (saber qué fichero de configuración se estaba cargando. Este es un tema muy interesante. Era fundamental el disponer de información sobre la ejecución de los hilos (creación. entre otros). Para poder llevar a cabo esta tarea. Por un lado.3. destrucción. pero del que apenas vamos a tratar. ó que no sea sensible a ataques de formateo de cadenas. bloqueo en espera de ciertos acontecimientos). En este proyecto se puede entender la depuración a dos niveles. tenemos toda una serie de herramientas de red. Depuración Uno de los aspectos más importantes en el desarrollo de cualquier software es la depuración del código. de SDP y de RTP entre otros. durante el desarrollo del agente se hizo patente que el propio UA debía informar acerca de cuándo realizaba algo y de como lo hacía. conocer los 30 . El otro nivel de depuración. Sin embargo. entre las que destacamos los analizadores de protocolos.Figura 5: Resumen del proceso de inicalización 2. es el que asegura que el comportamiento de nuestro agente es el correcto respecto a los estándares de SIP. El objetivo es que el agente no se 'cuelgue' y que no permita el acceso a la máquina en la que se está ejecutando (que no presente desbordamientos de buffer. ésta la comprobación de que hemos escrito código sin problemas de seguridad y sin fallos en el funcionamiento.

Para cumplir estos requerimientos se ha creado la ventana de depuración del agente de usuario. porque le restaría flexibilidad: debemos establecer un mecanismo para que todo el código mande la información de depuración que crea necesaria a un 'ente' centralizado.valores generados para asegurar su validez. Para saber más concretamente de qué estamos hablando. que nos permitiese seleccionar la cantidad de información que se mostraba en la pantalla.. y posteriormente será dicho 'ente' (representado en este caso por la ventana de depuración) el encargado de tratarla. Sin embargo. Debe quedar claro que existe una necesidad de conocer el estado de la pila y de la interfaz gráfica para ayudar a su desarrollo.) y sobre las acciones que se desencadenaban tras una decisión del usuario. Debíamos buscar un método más adecuado.. La forma más sencilla de llevar a cabo todas tareas podría haber sido sacando información hacia la salida estándar (consola).. y que distinguiese claramente entre los mensajes de error. se trata de separar el problema en partes y de dejar abierta la puerta para futuras mejoras: sería muy interesante el uso de los logs del sistema (syslog). Se trata de una ventana que recibe los mensajes de depuración y los muestra. o la posibilidad de guardar en un fichero los acontecimientos más importantes. No a nivel SIP. ya que esta comprobación se realizaría estudiando los paquetes que se envían y se reciben por la red. sino a nivel de arquitectura. no es la solución que se aceptó por carecer de flexibilidad. los avisos y la simple información administrativa. etc. Pero esto nunca se podría realizar desde la pila. veamos una figura en la que aparece dicha ventana con cierta información: Figura 6: Ventana de depuración 31 . Como siempre.

y no todos los sistemas disponen de uno. siendo todos muy parecidos. con todas las diferencias que ello implica. de modo que con muy poco esfuerzo podemos cambiar el tamaño. • Seleccionar todo: un pequeño atajo para poder abarcar todo el texto de la ventana. La segunda apreciación es que se basa en el portapapeles del escritorio. y por lo tanto sólo se asegura su correcto funcionamiento con el Klipper de KDE como portapapeles.Esta ventana de depuración estará en principio oculta.h se define un objeto llamado 'SUA_Text_Event'. y por tanto no afectará en nada a su funcionamiento. etc. La barra de tareas cuenta con seis iconos. Esta ventana cuenta con una barra de estado (que en principio marcará el tipo del último mensaje recibido) y una barra de tareas (que podemos desanclar y anclar en cualquier otro extremo de la ventana de depuración). Al pulsar sobre el icono del disco. La gran ventaja de usar eventos para mandar la información de depuración es que los eventos son asíncronos y thread-safe (ó seguros entre hilos). KtextBrowser. salvo que el texto seleccionado desaparece de la ventana.. para permitir ver siempre los últimos mensajes recibidos). salvo que su visión sea explícitamente requerida por el usuario. A este respecto hay que hacer dos apreciaciones: primero. El objeto de la clase 32 . Es muy importante notar que ni la cierra ni la destruye. el color ó la fuente del mismo. que es un widget (ó elemento gráfico) que acepta texto y lo presenta en la pantalla. que no tiene nada que ver con el tipo de texto que este objeto transporta (cada evento de tipo 363636 transporta un texto de tipo diferente). • Copiar: permite almacenar en el portapapeles del sistema el texto seleccionado sin borrarlo de la ventana. Existen varios de estos objetos en las librerías Qt/KDE. al guardar los datos siempre te pregunta por el fichero de destino y no te permite asociar una instanciación ó ejecución del agente con un fichero de log. • Cerrar: oculta la ventana. El texto que se representa en esta ventana se enmarca dentro de un objeto 'QTextBrowser'. Todos permiten el uso de formateo HTML para nuestro texto. y sobre él se copiará todo lo que aparezca en la ventana.. En el fichero sip/events. como QtextView. además de barras de scroll horizontal y vertical (esta última es automática. Como se puede comprobar. que proporcionan las siguientes funciones (en orden de izquierda a derecha): • Borrar: permite limpiar la ventana de información. que es una funcionalidad muy útil porque permite extraer la información del agente de forma sencilla y poder usarla en otras aplicaciones (un ejemplo muy claro es el de mandar una cierta cantidad de información a un colega desarrollador por correo electrónico: no hace falta ni guardar un fichero de log). Para detallar más el comportamiento de este sistema de depuración veamos cómo cualquier elemento del sistema (tanto de la pila como del front-end) manda sus mensajes a esta ventana. • Guardar Como: permite volcar el contenido del buffer a un fichero de texto. a través del menú principal sobre el que hablaremos más adelante. • Cortar: igual a la anterior. Estos objetos usan un identificador de tipo de evento 363636 (todos ellos usan el mismo tipo de evento). Este subtipo nos permitirá visualizar texto en diferentes formas.. Este agente únicamente se ha probado en el escritorio KDE. aparecerá un diálogo para seleccionar el archivo de destino. Esto implica que mandar un evento es muy rápido para el hilo emisor. derivado de 'QCustomEvent' y que encapsula el funcionamiento de un evento definido por el usuario que transporta una cadena de texto y un tipo asociado. borrando y liberando el espacio reservado por el objeto que encapsula la ventana. Esto último se resumen en el hecho de que sólo se brinda el comportamiento de 'Guardar Como' y no el de 'Guardar'. es todavía un procedimiento bastante rudimentario: no existe la posibilidad de usar un sistema de salvado automático de datos.

• El emisor busca un puntero al objeto que constituye el sistema de depuración (contiene la ventana de depuración). El receptor recoge los eventos en su función 'void Debug_Window::customEvent(QCustomEvent*)' y en función de su tipo añade tags HTML al principio y al final de texto transportado para controlar el formato de salida.3. • 10: cabecera muy grande (final = <font color=blue size=+1 face=courier><strong> texto </strong></font><br>) • 11: cabecera verde (final = <font color=green face=courier><strong> texto </strong></font>) • 50: escritura de texto permanente en la barra de estado. lo cual nos retorna un puntero al objeto de depuración. La arquitectura general creo que es eficiente y está bastante clara: todo la información que le pueda ser útil al usuario debe ser enviada a un lugar concreto y centralizado. pero con una persistencia de dos segundos. controlaremos toda la información y podremos realizar dos tareas muy 33 . • 70: información generada por el hilo RTP (final = <font color=#8d6de8> texto </font>). • 71: información referente al hilo de AUDIO(final = <font color=red><strong> texto </strong></font>). que estará derivado de un objeto de widget. • 51: escritura de texto en la barra de estado. El objeto se ha de crear de forma dinámica. Veamos paso a paso el proceso: • El emisor crea un objeto 'SUA_Text_Event' pasándole el texto "¡Hola Mundo!" y el tipo 0 (tipo básico: usa fuente. y que será el receptor de todos estos mensajes de depuración.QCustomEvent*)' que retorna inmediatamente y que da igual desde qué hilo la llamemos. Con este puntero. • 60: información referente al proceso de registro (final = <font color=#917705> texto </font>). La forma de hacerlo es la siguiente: mediante una función global de KDE llamada 'static Kapplication *kApplication(void)' obtenemos un puntero al objeto de aplicación (que es único para cada instancia del ejecutable). Desarrollo futuro En este pequeño apartado me gustaría comentar ciertas ideas acerca del desarrollo previsto para este subsistema de la interfaz gráfica. De esta forma. Ese texto aparecerá en la barra hasta que sea sobrescrito.1.QCustomEvent*)'. • 81: código de aviso (warning)(final = <font color=blue><strong> texto </strong></font>) • 82: información SIP general (final = <font color=#1268aa> texto </font>) 2.'Debug_Window' que encapsula la ventana. tamaño y color por defecto) al constructor del objeto. • 80: código de error (final = <font color=red><strong> texto </strong></font>) y se escribe "Error" en la barra de estado de la ventana. • Con el puntero al objeto de evento de texto y el puntero a la ventana de debug. Después de transcurrido ese lapso de tiempo. mandamos el evento con la función global '::postEvent(QObject*. llamamos al miembro 'Debug_Window* Ksua_App::Get_DebugW(void)'. que a su vez derivará de un objeto 'QObject'. desaparecerá. debe ser conocido por el emisor para hacerle llegar el evento a través de la función '::postEvent(QObject*. Existen tipos muy diversos: • 0: texto neutral (final = texto).

. SAX es un API enfocada a eventos. y si no hay ninguna acción en contra.identificador del emisor: cada hilo tiene un id asociado --> <emisor>126</emisor> <!. se creará un fichero para almacenar los errores.el mensaje en sí --> <texto>error en el campo Via del INVITE</texto> <!. Las dos APIs más comunes son SAX (Simple API for XML) y DOM (Document Object Model). al contrario. rtpinfo. Cuando 34 . en lugar de en texto plano. podríamos estructurar un mensaje de error de la siguiente forma: Tabla 7: Ejemplo de mensaje de error en XML <!. El primer punto consiste en imitar de cierta manera el comportamiento de un editor de textos normal. audioerror.tipo: siperror. rojo y con una cruz blanca delante de cada mensaje de error. sipwarning. Siempre teniendo en mente el objetivo de dejar constancia de lo que hemos hecho ó de quien está intentando contactar con nosotros y de qué manera. y muchas librerías de uso más o menos genérico cuentan con la suya propia (por ejemplo Qt). DOM. otro para almacenar la información genérica y quizás un tercero para almacenar toda la señalización SIP que envía el UA y que recibe. Cuando se ejecute el agente. En cuanto al segundo punto. sería de gran ayuda que la información almacenada estuviese en XML. es el API más orientada a árboles. Se pueden registrar manejadores que se llamarán cada vez que ocurra uno de estos eventos..fecha de generación del mensaje --> <fecha> <año>2002</año> <mes>01</mes> <día>21</día> <hora>20:12:45</hora> </fecha> <!. sipinfo. por lo que es sencillo encontrar APIs para manejar XML. un icono circular.interesantes: • Asociar uno ó varios ficheros de log a cada instanciación del agente. para que pueda ser analizada a posteriori por otros programas.. genera eventos cuando encuentra etiquetas. ó un signo de exclamación en azul delante de cada warning. A medida que el párser XML analiza el documento. Así podremos estudiar el comportamiento general de forma detallada. --> <tipo>siperror</tipo> <!. También sería interesante poder realizar copias de seguridad de los logs. Para mejorar la interfaz gráfica de la ventana de depuración se podría pensar en anteponer a cada línea de depuración un pequeño icono que sirviese de llamada de atención para centrar a la persona que lea: por ejemplo.se podrían añadir muchísimos más campos --> </mensaje> Este sistema de estructuración de la información está ampliamente extendido. • Almacenar la información en XML.Descripción de un mensaje --> <mensaje> <!. La mayor parte de los lenguajes tiene una. atributos y datos. ya que sería autocontenida y fácilmente analizable con herramientas externas. Por ejemplo. añadirles comentarios escritos a mano (para poder marcar puntos concretos) ó desechar información demasiado trivial.

Por otra parte. lo que recibes del párser es un nodo del documento. Este icono es representativo de la aplicación que se está ejecutando de manera semejante a la barra de tareas (donde aparecen las ventanas minimizadas). pero deberán estar accesibles inmediatamente si el usuario los necesita (para realizar una llamada. Acciones disponibles En este apartado nos vamos a centrar en explicar todo lo que el usuario puede hacer con la pila SIP a través de la interfaz gráfica que hemos desarrollado. El resultado es el siguiente: 35 . por no decir que se trataría de una ventana vacía con un escueto menú. ya que la solución más directa de crear una ventana y tenerla siempre minimizada no era del todo adecuada. como si no. A la hora de diseñar este front-end. pudiendo transmitir cierta información al usuario usando animaciones ó códigos de colores (azul puede indicar espera. Si la pulsación se realiza con el botón derecho. ya que deberá prestar atención a las llamadas que otras personas nos hacen a nosotros. Para poder usar la información albergada en un DOM. por ejemplo). la ventana principal de la aplicación se muestra y se activa. los menús que se presentan y las ventanas en las que se representará la información.4. Se trata de una región del Panel de KDE (Kicker) que permite que se le 'adosen' pequeñas ventanas (normalmente con un tamaño de 24x24 pixeles). a la espera de recibir información por la red (inicios de sesiones) ó acciones del usuario. Este sistema para ejecutar aplicaciones en la bandeja del sistema es realmente útil para casos similares a los demonios de UNIX (daemons). verde que la sesión evoluciona de forma correcta y rojo que se ha producido un error). pero se consume mucho menos espacio de pantalla. incluyendo una opción para abandonar la ejecución del programa. Se debía buscar una solución elegante para este problema. Con cualquiera de estas dos APIs nos podríamos crear un sencillo script Perl para leer la información de depuración que ha generado ó que está generando nuestro agente. tanto si queremos llamar. La solución a la que se ha llegado ha sido la de usar la bandeja del sistema de KDE (System Tray).usamos el párser DOM. desde el punto de vista estético no era una maravilla. 2. Cuando el usuario pulsa con el botón izquierdo del ratón en el icono. Después de haber configurado e iniciado nuestro agente. aparece un menú emergente (popupmenu) con los comandos específicos de la aplicación. Enumeraremos las distintas acciones. se debe navegar por el árbol e inspeccionar el contenido de cada nodo. Estos programas deberán correr durante un periodo de tiempo indefinido y generalmente largo sin ninguna iteración con el usuario (esperando a que otra persona inicie una sesión con nosotros). Detrás del nodo está un árbol de nodos que representan la estructura lógica del documento. Demostraremos cómo una interfaz minimalista puede cumplir todos estos requerimientos y muchos más. éste se queda en un estado 'latente'. A la hora de desarrollar nuestro front-end hemos considerado que este sistema se ajusta perfectamente a nuestras necesidades. Otra característica muy interesante del System Tray es que la aplicación puede alterar el icono que se representa. porque ocupa un espacio precioso en nuestro escritorio. con un icono estático ó dinámico. debíamos tener muy en cuenta este punto: el agente debe estar siempre ejecutándose. ya que el contenido de esta ventana sería mínimo. sin ocupar grandes espacios en la pantalla y permaneciendo siempre activa y disponible.

se trata de una solución minimalista y elegante. Veámoslo más en concreto: • Selección del icono: Selección del icono: se realiza en el constructor del objeto 'KSua_DockWidget' mediante la típica llamada a la función miembro 'Qwidget::setIcon(QPixmap*)'. de la clase 'KSua_Window'. La funcionalidad encargada al objeto 'KSua_DockWidget' es la de mostrar el menú emergente (popupmenu) que observamos en la figura 7. y será el encargado de implementar el funcionamiento requerido. Este objeto representa el centro de la interfaz gráfica. El constructor de 'KSua_Window' llama a su función miembro 'void Ksua_Window::updateDocking(void)' que crea un objeto del tipo 'KSua_DockWidget' y esconde la ventana principal. la de manejar las pulsaciones del ratón sobre el icono de la bandeja del sistema y la de seleccionar y actualizar (si fuera necesario) dicho icono. que oculta su complejidad detrás de una pequeña interfaz. nada más empezar. un objeto para la ventana principal. El constructor de este objeto crea.Figura 7: KSua en la bandeja del sistema (System Tray) Como se puede comprobar. la función principal del agente (main) se encarga al arrancar de crear un objeto de aplicación ('KSua_App'). Como ya hemos visto. la forma en la que estas 36 . • Pulsaciones del ratón: Manejo de las pulsaciones del ratón: como ya hemos dicho. Vamos a comentar primero cómo hemos realizado esta solución y cómo hemos alterado un poco el funcionamiento por defecto de la bandeja para adecuarla a nuestras necesidades. Mediante sucesivas llamadas a dicha función podríamos realizar animaciones ó diversos efectos de cara al usuario.

Cuando esto pase. cuando se llama a la función se pasa un puntero a un objeto menú emergente (con el menú antiguo) y se devolverá el menú nuevo. porque es en definitiva la forma que tiene el usuario para comunicarse en primera instancia con la pila. En este caso.. de forma natural. Siempre nos interesará que al pulsar en el icono con cualquier botón del ratón aparezca el menú emergente. que siempre será 'Qt::RightButton'. En el primer grupo estarán los ítems: "Make Call". hay que hacer lo mismo cada vez que se levanta el botón (release) sobre el icono. cada vez que se pulsa con el botón derecho del ratón sobre el icono de la bandeja). actualizar los contenidos antes de mostrárselos al usuario). se llamará a 'void Ksua_DockWidget::contextMenuAboutToShow(KPopupMenu*)' que deberá devolver el menú en el puntero pasado a la función. en un 'ítem' no seleccionable ó pulsable: algo así como el título del menú. Generalmente se pone a -1 para que Qt escoja el primero libre. int accel=0.int)'. El proceso es idéntico: se llama a 'void KSua_DockWidget::mouseReleaseEvent(QMouseEvent*)' se duplica el objeto 'QMouseEvent' ahora del subtipo 'QEvent::MouseButtonRelease'.QString. La construcción del menú se realiza cada vez que se requiere su visualización por parte de KDE (por ejemplo. Sin embargo.etc. mediante la función 'void KpopupMenu::insertTitle(QPixmap.) en el que se copie literalmente el original. la idea de que al pulsar el botón izquierdo aparezca la ventana principal no es muy interesante en este proyecto. La primera operación será. const QObject *receiver. int index=-1)' que 37 . separadas precisamente por separadores ('void KpopupMenu::insertSeparator(int)'). Además de 'engañar' al sistema cada vez que se pulsa sobre el icono. al manejar menús. como se trata del título y queremos que aparezca en primer lugar. el menú emergente. Este comportamiento no es obligatorio. Siempre mostrará este menú y nunca la ventana principal (que será un ventana vacía). se llama a la función 'void KSua_DockWidget::mousePressEvent(QMouseEvent*)' con la idea de que cualquier aplicación pueda realizar un procesamiento antes de que se muestre el popupmenu ó la ventana principal (por ejemplo. "Preferences" y "Debug". se modifica el campo que expresa el botón responsable para que contenga 'Qt::RightButton' y se devuelven los datos a KDE mediante 'void KSystemTray::mouseReleaseEvent(QMouseEvent*)'. Tal y como deseábamos.. y en el tercero únicamente "Quit". Realmente. El truco consiste en crear un nuevo objeto QMouseEvent (el original contendrá información sobre la pulsación: posición. usaremos la función 'int QMenuData::insertItem (const QString &text.. ya que la ventana principal no tiene nada. Este nuevo evento se lo pasaremos de vuelta a KDE a través de la función 'void KSystemTray::mousePressEvent(QMouseEvent*)' para que muestre. salvo el botón que ha causado el evento. por tanto. pero así se expresa en las normas de estilo de las GUIs para KDE. • Menú emergente Creación del menú emergente: este menú va a ser importantísimo en el front-end. En el mundo Qt/KDE. En el caso de no necesitar un icono junto al nombre del ítem del menú.aplicaciones en la bandeja funcionan con respecto a las pulsaciones del ratón depende de qué botón del ratón sea accionado. Después del título del menú emergente. La forma de incluir este título es especial. por lo que engañaremos a la aplicación de la siguiente manera: cuando se pulsa sobre el icono. borrar el menú viejo y crear uno nuevo. En el segundo: "About KSua" y "About KDE". const char *member. le asignaremos la posición 0. int id=-1.int. es decir. es un objeto que realiza varias funciones pero no tiene una 'cara visible'. Para manejar las posiciones usaremos una variable que iremos incrementando. El último parámetro es también un entero e indica la posición del ítem en el menú. siempre se tienen que especificar dos enteros al final de la mayoría de las funciones: el penúltimo parámetro es un entero que asigna un identificador al ítem ó título del menú. botón. aparecerán tres grupos de entradas. La primera entrada es un poco especial ya que suele ser el propio icono de la aplicación y el nombre.

la forma que tiene el objeto que controla la creación del menú para localizar un puntero a la ventana principal es a través del método 'KSua_Window *KSua_App:: Get_MainW(void)' del objeto de aplicación que es global y conocido por todos. Para ello se ha de llevar un seguimiento del estado de la ventana mediante una variable miembro del objeto de tipo 'KSua_DockWidget'. y cómo probar la viabilidad de los flujos RTP extremo a extremo. El caso del ítem que se encarga de manejar la visualización de la ventana de depuración es un poco especial. Para ello deberemos conocer la dirección SIP pública de la persona a llamar y pulsar sobre el ítem "Make Call" en el menú emergente. por lo que tal vez ahora se comprenda mejor el hecho de que la ventana principal no está diseñada para ser visualizada. sin embargo. Además. int index=-1)'. una de las primeras acciones que buscaremos realizar con un soft-phone. Cuando necesitemos un icono (el caso de "Preferences" y de "Quit") usaremos la función 'int QMenuData::insertItem (const QPixmap &pixmap.permite asociar un texto a una entrada del menú. Un aspecto importante de todos estos ítems en el menú es que se permite especificar la función ejecutada cuando se pulse sobre ellos. const QObject *receiver. Realizar una llamada de audio y preguntar por capacidades. Veamos más en detalle todas estas acciones: 2. que es un botón que puede estar activado ó desactivado: lo mismo se busca para la ventana de depuración. La idea que se pretendía era semejante a la que da un widget de tipo 'check button'. Esta es. sino para manejar las acciones del usuario.4. const char *member. todos usan funciones miembro del objeto que encapsula la ventana principal (que es un objeto del tipo 'KSua_Window'). sino que es este mismo objeto quien lo hace.1. Existen. en este caso concreto la ventana principal no se encarga de ocultar/visualizar la ventana de depuración. y lo que es más importante. int accel=0. una función que será ejecutada cuando ese menú sea seleccionado. Excepto en el caso de "Debug" (que trataremos a continuación). Como se ha visto para el caso de mandar eventos a la ventana de depuración. naturalmente. dos acciones más que no se encuentran en el menú y que comentaremos también: qué se puede hacer cuando se recibe una llamada. Nos aparecerá la siguiente caja de diálogo: 38 . int id=-1. Una vez explicada la forma en la que se le presentan al usuario las distintas acciones posibles veamos qué se puede hacer con ellas. que podrá ser visible si el usuario mantiene el ítem del menú chequeado ó invisible si no lo tiene activado.

retornará un valor verdadero (TRUE). y si esta existiese. Si la libreta del sistema está presente y se puede acceder a ella. Para ello tenemos una caja de texto en la que podremos introducir este dato de forma manual. número de teléfono. Se trata de una aplicación externa que permite introducir una entrada por persona en una base de datos y almacenar ahí todos sus datos personales: dirección. De dicho registro se examinarán los cuatro campos de usuario (que tendrán un valor no regulado por KAB) en busca de una dirección SIP. Sin embargo. y luego sólo tendrá que seleccionarlas. también llamada KAB (KDE Address Book).. La segunda parte consiste en acceder a esos datos para presentárselos al usuario de alguna forma y permitirle que elija uno de ellos. y que además de devolver el contenido de la libreta. se copiaría esa cadena de texto a la caja de texto de la ventana de llamada. presenta la posibilidad de incluir cuatro campos de usuario personalizados. Lo primero que deberemos hacer para poder llamar será introducir la dirección SIP del destinatario. De esta forma. de forma manual y usando el programa 'kab' que se distribuye con KDE. el usuario introducirá las direcciones frecuentemente usadas en el KAB del sistema. Además. etc. al pulsar sobre el menú se ejecutará la función miembro de la ventana principal 'void Ksua_Window::On_Makecall(void)' que creará un objeto del tipo 'Make_Call_Window' y lo mostrará. Por este motivo se añade un pequeño botón a la derecha con un icono de un teléfono que nos va a servir para acceder a la libreta de direcciones del sistema..Figura 8: Ventana de inicio de llamada Como ya se ha visto. Esta tarea se realiza usando el API 'KabAPI' de KDE que se incluye en la librería libkab.. Veamos cómo es la selección de los registros: 39 . Para poder usar la libreta en nuestra aplicación deberemos realizar un proceso en dos partes: primero. Si hay algún fallo. presenta una caja de diálogo de la que el usuario seleccionará la entrada que desee. todos sabemos lo tedioso y propenso a errores que esto resulta (imaginemos cómo sería tener que hacer esto cada vez que mandamos un correo electrónico). deberemos introducir los registros en la base de datos. que nosotros aprovecharemos para incluir las direcciones SIP. KabAPI presentará dicha ventana y el usuario seleccionará una entrada (realmente se presentan los nombres de las personas sobre los que trata el registro). correo electrónico.so. retornará falso (FALSE). Todo esto se puede comprobar en la función 'bool MakeCall_Window:: On_Address_Book(void)' que se asocia a la acción realizada al pulsar el botón con el icono del teléfono. Si todo va bien.

Este agente es un poco especial y no permite los cambios automáticos en la compresión del audio. y sólo se puede seleccionar uno cada vez. y evita infinidad de errores en las direcciones. se necesitaría disponer de una ruta congestionada. En una aplicación profesional esta elección no tendría sentido. Es más. que nos permiten seleccionar el códec de audio para esta comunicación. 40 . como siempre. porque una comunicación se comenzará con un determinado códec. este simple sistema ayuda muchísimo a acelerar el proceso de realización de una llamada. El motivo para limitar así las funcionalidades de nuestro agente ha sido. Esta lista encadenada será usada a la hora de generar los payloads SDP y a la hora de crear los hilos RTP basados en estos códecs. el hecho de que el único desarrollador del proyecto no puede abarcar todo. Si continuamos el análisis de esta ventana veremos un grupo de botones de radio (radio buttons) exclusivos. pero es un problema bastante serio y que su solución puede requerir mucho esfuerzo). Aparecerán tantos botones como códecs haya disponibles en la pila. Estos botones se han incluido para forzar el uso de un sistema de compresión y para tareas de depurado. Además de necesitar un sistema para medir la congestión de la ruta (es algo que se dice muy rápido. Cuando seleccionemos el códec que deseemos (PCM u/A law van a 64Kbps y GSM a 13Kbps) se ejecuta la función asociada 'void MakeCall_Window::Set_Current_Codec(int)' que tomando el objeto de la clase 'Parameters' almacenado en el objeto de aplicación. y en función de la evolución del ancho de banda se determinará saltar a otro de mayor capacidad o de menor. y con los medios de que se ha contado esto no era fácil. aquel que el usuario haya seleccionado del grupo de botones del que estamos hablando. itera sobre su miembro 'codecs' (una lista encadenada con todos los códecs disponibles) deshabilitando todos excepto el seleccionado. por lo que a nivel SDP siempre se incluirán todos los códecs disponibles.Figura 9: Libreta de direcciones SIP Como se puede imaginar. a nivel SDP sólo se notificará la existencia de un único códec.

sólo se crea. nuestro agente realiza su trabajo de forma correcta. que indica a la pila que precisamente deseamos realizar una llamada.1. para evitar iniciar todo el proceso de llamada con una dirección incorrecta que nos llevará a error. en la que el control del hilo quedará bloqueado hasta que la ventana que se comporta como diálogo considere que ha terminado su ejecución. La función 'void Make_Call_Window::On_Dial()' es la encargada de iniciar los procedimientos que desembocarán en la puesta en marcha de la pila SIP.4.Una vez hayamos completado la información estaremos en disposición de llevar a cabo una de las dos siguientes acciones: 2. que será la encargada de mostrar información relativa al progreso de la llamada: cómo se envían los paquetes y cómo van siendo las respuestas parciales del extremo remoto. tratará de hacer ver al usuario que el agente hace 'algo'. es decir. se ejecuta la función 'bool Progress_Window::Exe_It(void)'. por ejemplo. El campo de datos o payload del evento llevará la dirección SIP del extremo remoto y se especificará que los eventos de respuesta lleguen a la ventana de progreso de llamada. O bien el usuario o bien la pila decidirán en un determinado punto acabar con la ventana. No ya tanto en el sentido visual del término. Su función miembro 'bool SUA_Address::Is_Address_Ok(void)' nos permite comprobar que la sintaxis de la dirección introducida por el usuario (ya se a través de la caja de texto ó a través del sistema KAB) es correcta. La mayor parte de las ventanas que usamos en este proyecto se comportan de forma contraria. devolverá TRUE si se ha conseguido iniciar la llamada y FALSE si no ha sido posible.1. La apariencia física de dicha ventana es la siguiente: 41 . pero tampoco es específica porque se usa en otra ocasión este agente) del tipo 'Progress_Window'. En definitiva. Como digo es más sencillo porque 'linealiza' el código y nos evita ir saltando. • Crear la ventana (diálogo) de progreso de llamada: consiste en crear una ventana semigenérica (no es genérica porque no es del tipo primitivo de Qt. • Lanzar un evento de inicio de llamada a la pila SIP: será un evento del tipo ' SUA_Call_Event' (evento relacionado con la llamada) y del subtipo 'W_Make_Call'. De momento no se ejecuta. asíncronamente: en un momento dado se crean y se visualizan. Llamada de audio. el usuario interactuará con ellas provocando la ejecución de alguno de sus métodos y la pila lo hará también pero a través del mecanismo de eventos. y que aunque se tarde varios minutos en conectar (porque. Como se comprueba. • Esperar a la conclusión de la señalización de inicio de sesión visualizando el diálogo de progreso de llamada: la ventana de progreso se comporta como una caja de diálogo. sino en su modo de operación síncrono. porque se ejecutará una determinada función de la ventana. que no se encuentra bloqueado. Los preparativos que realiza esta función serán: • Comprobar la corrección sintáctica de la dirección SIP a la que pretendemos llamar: en este agente de usuario hemos creado una clase llamada SUA_Address que se encarga de manejar las direcciones. Si pulsamos el botón "Dial" se inicia la llamada propiamente dicha. el usuario remoto no se encuentra delante de su soft-phone). Esta función. esconde todo el procedimiento de recepción de eventos con el progreso de la llamada y con el resultado de la misma. sino que está creada para esta aplicación. El comportamiento síncrono es mucho más sencillo. Veamos nuestro caso concreto para comprender mejor por qué es más sencillo un funcionamiento síncrono: después de lanzar el evento que indica a la pila que debe comenzar la señalización.

tras realizar las búsquedas DNS con éxito. 2. se mandan siete INVITEs al extremo remoto. pero también el del registro (por eso la hemos definido antes con el calificativo de semigenérica). está disponible. y no se recibe respuesta. la pila indica que el registro ha sido un éxito. Hemos conseguido inicializar la sesión SIP con éxito. pero en este caso porque. 4. W_Register_unSuccessfull: con este subtipo. no existe ningún usuario que concuerde con el nombre que hemos dado en la dirección SIP llamada. C_Received_404: evento que indica también un error. 42 . W_Register_Successfull: como ya se ha dicho. si bien existe el host que hemos especificado en la dirección SIP. la pila le dice al front-end que no ha sido capaz de finalizar el registro con éxito. se considera que no hay nadie escuchando esa dirección SIP y se decide abandonar. 5. Con este subtipo.Figura 10: Progreso del inicio de sesión El funcionamiento interno de la ventana de progreso es relativamente sencillo: se trata de una ventana manejada a través de eventos. La recepción de este evento mostrará un caja de diálogo con el error y abandonará esta ventana devolviendo FALSE. esta ventana se usa para controlar el progreso de llamada. cada uno de ellos separado del otro con un tiempo exponencial. Este mensaje se traduce por algo así como "Usuario no encontrado en host" (aunque el host exista y sea una entidad SIP). 3. eventos de gestión de hilo (SUA_ThMan_Event) fundamentalmente para gestionar la asociación entre el hilo llamante y esta ventana y para llevar a cabo salidas confirmadas hacia la pila. los más importantes. C_Running_Call: evento de confirmación positiva. C_Invite_No_Response: cuando. Retornaremos devolviendo TRUE y nos prepararemos para continuar con la llamada. los eventos de control de llamada (SUA_Call_Event) entre los que destacamos los siguientes subtipos: 1. Acepta eventos de texto ('SUA_String_Event') para recibir información de la pila y representarla en la línea de texto de la ventana. y por último. También provocará la notificación al usuario del error y la salida devolviendo FALSE. y hay una entidad SIP escuchando en él.

QString. también se puede deber a que si todos los códecs que implementan los extremos son diferentes no habrá ninguna coincidencia y no se podrá negociar. Pero una vez que la llamada esté en curso. La ventana de la que hablamos tiene la siguiente interfaz (trataremos sobre ella más adelante): Figura 11: Ventana de control de llamada Vamos a mostrar en la figura 12 un cuadro resumen en el que se puede apreciar todo el proceso de inicio de una sesión SIP. En este momento.QString. También se liberarán los recursos asociados. Esto se realiza con los eventos de gestión de hilos de tipo 'SUA_ThMan_Event'. 43 . C_No_Media_Mach: evento que indica que se ha producido un error en la negociación de los códecs multimedia aceptables por ambos extremos. mostrar un error y salir: se usará para mostrar el error una función definida de KDE. tendremos una llamada en curso. nuestro front-end mostrará una única ventana y será ésta la que reciba todos los eventos de la pila asociados a esta llamada (muy importante a la hora de finalizar la llamada). dicha ventana desaparecerá y deberá mandar todos los nuevos eventos a la ventana de tipo 'Call_Run_Window'. Provoca la notificación al usuario del error y la salida. • Si la llamada prospera. primero se le indicará al hilo que se encarga de la llamada que mande los eventos a la ventana de progreso. Por lo tanto. cuyo nombre proviene de 'Thread Management') y se destruirán tanto la ventana de realización de llamada como la de progreso. y concretamente al hilo en cuestión. Tal vez no siempre se trate de un error.bool)'. mostrar la ventana de llamada en curso y salir: si la salida de la ventana de progreso de llamada ha sido positiva (por la recepción de un evento de llamada C_Running_Call). se realizará una operación de cambio de asociación de punteros (cada hilo de la pila cuenta con un puntero a la ventana a la que tiene que mandar los eventos de señalización. se debe notificar al pila. En función del tipo de salida que se produzca tendremos dos tipos de acciones: • Si la llamada no ha prosperado. En este procedimiento de iniciación de una llamada. y se cerrarán tanto la ventana de progreso de llamada como la de realización de la llamada.6. se creará la ventana que controla el curso de la llamada (del tipo 'Call_Run_Window'). 'KMessageBox::error(int. del cambio en el receptor.

La elección del códec de audio es irrelevante en este caso.1. donde se muestra la ventana que nos aparece cada vez que queremos realizar una llamada. Si nos fijamos en la figura 8. y pulsar "Query".2. Por lo tanto. También se obtendrá información adicional. Preguntar por las capacidades remotas. nos damos cuenta de que existe un segundo botón en la parte inferior marcado con el texto "Query". Éste botón es el que nos permite preguntar a una entidad SIP remota sobre sus capacidades.2.4. Mediante una petición SIP de tipo OPTIONS somos capaces de conseguir información acerca de los métodos que implementa un determinado servidor proxy o un agente de usuario remoto. 44 . como el nombre del software que implementa esa entidad ó la compañía que la fabrica. tendremos que introducir la dirección SIP de la entidad (de las dos formas vistas antes: manualmente ó basándonos en KAB).

• Creación de la ventana de diálogo que soportará la petición de las capacidades: se trata de un objeto del tipo 'Query_Window' que tendrá un cometido similar al de la ventana de progreso de llamada vista antes. • Lanzamiento de un evento de control de llamada 'SUA_Call_Event' del tipo 'W_Query_Options' al hilo generador de hilos (que evidentemente pertenece a la pila SIP).Figura 12: Cuadro resumen de inicio de sesión Al realizar esta acción se ejecutará la función 'void Make_Call_Window::On_Options()'. Se realiza de la misma forma que para comenzar una llamada. que realizará lo siguiente: • Comprobar la corrección sintáctica de la dirección SIP que hemos introducido. 45 . Más adelante detallaremos los eventos que soporta.

pero éste no se ocupa de la sesión en cuestión. Para resolver este tipo de conflictos se crean los eventos de gestión de hilos. aunque mucho más sencilla. o bien se complete la transacción SIP (tanto si es de forma exitosa como si no) o bien el usuario cancele el proceso. Cuando la respuesta a la petición OPTIONS sea afirmativa. y le pasará al hilo el puntero a la ventana de petición de capacidades. En particular. manda el evento de llamada correspondiente ('W_Query_Options') al hilo generador de hilos (a través de un puntero global conocido por todos). En cierto modo la petición de capacidades se parece a la creación de una llamada de voz. De esta forma. • Ejecución de la cola de eventos de la ventana de diálogo referente a las capacidades (forma síncrona). En general. En la siguiente figura podemos ver la ventana de la que estamos hablando en pleno proceso de consulta a la dirección "sip:sip. Realmente. pero hablemos de éste en particular: cuando la ventana de inicio de llamada decide hacer una pregunta sobre las capacidades de una entidad SIP remota. sólo puede mandar eventos al hilo generador. la entidad remota devolverá una respuesta del tipo 200 en la que incluirá diversa información: nuestra pila SIP. Cuando pulsemos "Query" nos aparecerá una ventana de diálogo relativamente sencilla que sólo acepta estos tipos de eventos: • Eventos 'SUA_String_Event' con los cuales la pila 'imprimirá' información en esta ventana.para que la pila comience a tomar las acciones necesarias para realizar la petición. la comunicación desde la pila hacia el front-end es posible entre el hilo hijo y la ventana que recibe su información textual en forma de eventos. Mediante esta sencilla acción. usando su párser SIP. liberándose así de carga.com" (un proxy SIP): 46 . 'TM_Update_Th_Pointer' permite a un hilo comunicar a 'su ventana' cómo acceder a él. Sin embargo. Este hilo generalmente creará un hilo hijo para atender a esta sesión. • Eventos de gestión de hilos ('SUA_ThMan_Event') de tipo 'TM_Update_Th_Pointer': este tipo de eventos para la gestión de los hilos se comentarán con detenimiento más adelante.sipcenter. la ventana de inicio de llamada en la que nos encontramos se quedará bloqueada hasta que. extraerá dicha información y la irá enviando a esta ventana para su visualización en forma de eventos de cadena. los eventos de cadena se usan en la comunicación entre la pila y el front-end para la transmisión de información textual. ¿cómo sabe la ventana cómo mandar información en sentido contrario?.

Sending options to 195.Figura 13: Ventana de pregunta de capacidades remotas La información que aparece en dicha ventana se transcribe aquí como ejemplo: Tabla 8: Ejemplo de información recibida al preguntar por capacidades Client options thread running.. presentamos un resumen de la arquitectura general que permite implementar esta funcionalidad: 47 .28 Por último.217..6:5060 100 response: Trying Remote capabilities: Methods allowed: 0º: ACK 1º: CANCEL 2º: OPTIONS Server: ASB Service Host/1.169.2 Chimera/1..1.

etc. Como ya se ha comentado en alguna ocasión en esta misma memoria. Recibir una llamada de audio.) y enviará un mensaje a la ventana principal (aquella que. el primer elemento en notarlo será. La pila tomará las actuaciones necesarias (reserva de recursos. y el más claro ejemplo se presenta cuando alguien nos llama.. la pila. El modelo minimalista de presentación al usuario nace de estas premisas.4. Sólo en algunas ocasiones seremos nosotros quienes tomemos la iniciativa y le pediremos que haga algo. El modo de operación básico consistirá en tener nuestro UA 'esperando' todo el tiempo. En otras. creación de un hilo de servicio. almacenamiento del Call-Leg. 48 . el diseño de este agente debe tener un enfoque orientado hacia periodos de ejecución largos.Figura 14: Cuadro resumen de pregunta de capacidades 2... será el agente el que nos deba informar del devenir de los acontecimientos. Cuando alguien inicie una sesión contra nosotros. De momento estudiaremos este proceso desde el punto de vista de la interfaz gráfica.2. evidentemente. 'saltando' con información.

Una ventana del tipo 'Call_Run_Window'. entre otras. • Se mandará a la pila un evento de control de llamada ('SUA_Call_Event') del tipo 'W_Accept_Call'. algunos empiezan con la letra 'W' y otros con la letra 'S'. estando el hilo SIP funcionando como Servidor. Esta ventana permanecerá bloqueada hasta que el usuario tome una decisión. entre toda la variedad de eventos de llamada existentes. 'W' implica que el evento recorre el camino desde las ventanas ó 'Windows' hacia la pila. que será almacenada en el objeto y que se podrá consultar por medio del método 'QStringList SUA_Call_Event::Get_Ext_Info()'. Pero en este caso no queremos que la señalización llegue a esa ventana. un puntero a la ventana principal (precisamente a ésta donde ahora nos encontramos). Desde el punto de vista de la GUI. que en realidad representa para el hilo el lugar a través del cual se ha de comunicar con la interfaz gráfica. presentamos un diagrama de bloques en la figura 16. 'S' implica que el evento va desde la pila hacia la interfaz gráfica. se rechaza la llamada. justo al revés. Para resumir estos puntos. que tiene un aspecto como el que se ilustra en la figura 11. la información extendida que este evento acarrea es. el origen de la llamada (dirección SIP pública de la persona ó agente que nos llama). Todos los eventos de llamada tienen la posibilidad de llevar información adicional en forma de lista encadenada de cadenas de texto. con el fin de que el usuario decida si acepta o rechaza la llamada. En este caso concreto. Este evento es necesario porque inicialmente los hilos SIP creados desde del hilo generador de hilos adquieren como puntero a la ventana del front-end que recibe sus eventos. 49 . primero creará una ventana del tipo 'On_Call_Window' en la que se mostrará la identidad del llamante. Es sólo una regla de nomenclatura para facilitar el desarrollo y no perderse entre tanto evento). se llevarán a cabo estas acciones: • Se creará la ya conocida ventana dedicada al control de la evolución de llamada. únicamente se mandará un evento de control de llamada ('SUA_Call_Event') con el tipo 'W_Reject_Call'. Si en cambio. que indica que la llamada ha sido aceptada y que como tal se ha de completar la señalización SIP para el inicio de sesión.como ya comentamos. Si acepta la llamada. • Se mandará a la pila un evento 'SUA_ThMan_Event' del tipo 'TM_Update_Mw_Pointer' con el que el front-end especifica al hilo encargado de manejar esta llamada un puntero a la ventana 'principal'. para indicar a la pila que no complete la creación de la sesión. A efectos del front-end la llamada ha comenzado. El control de llamada recaerá sobre esta ventana recientemente visualizada. Se trata éste de un evento de llamada ('SUA_Call_Event') de tipo 'S_Incoming_Call' (NOTA: el lector avispado se habrá dado cuenta de que. Cuando la ventana principal de la aplicación recibe este evento. • Se visualiza la ventana 'Call_Run_Window' y se abandona la función encargada de la gestión de eventos de la ventana principal. la llamada no se ha realizado y no deberá recibir más eventos relacionados con ésta llamada en particular. si bien no tiene una cara pública sí realiza numerosas acciones para el funcionamiento del agente). sino a la ventana que manejará el discurrir de la llamada.

Esta ventana tendrá el aspecto de la figura 11. nos encontraremos delante de una ventana de tipo 'Call_Run_Window'. Finalizar una llamada de audio. a nivel SIP hay evidentemente grandísimas diferencias.3. Antes de pasar a comentar cómo realiza su tarea me gustaría comentar ciertos aspectos de esta ventana: en primer lugar. o si nos han llamado. porque en el primer caso estaremos actuando como un cliente y en el segundo como un servidor.Figura 15: Cuadro resumen de recepción de llamadas 2. en este momento nos encontraremos con que tenemos una llamada en curso. vemos que consta de tres espacios bien diferenciados: • En la parte superior cuenta con un recuadro dedicado a imprimir las cadenas de texto 50 . Si seguimos con el proceso lógico de la explicación. que manejará los aspectos relevantes al mantenimiento de la sesión. Sin embargo. A nivel de interfaz gráfica.4. da igual si esta llamada la hemos creado nosotros. En cualquiera de los dos casos.

Actualización del puntero al hilo hijo encargado de nuestra llamada: como hemos visto. que le permite guardar un puntero al hilo que maneja la señalización SIP. y para dar al usuario sensación de mayor dinamismo (las acciones SIP dan la impresión de ser casi inmediatas. Por este motivo. se ha decidido establecer que la GUI siempre suponga que el hilo ya no existe. Se ha sobrescrito el código de cierre de la ventana para que antes de terminar se lance un evento de control de llamada ('SUA_Call_Event') del tipo W_Hang_Call. pero usar la denominada 'señalización intermedia ó específica' para ordenar a la pila que lo haga puede ser un juego de niños. Este funcionamiento está encapsulado en el objeto 'SUA_AudioLed'..relacionadas con esta llamada que va recibiendo la interfaz gráfica desde la pila. ó con un número a determinar de recuadros de colores. porque se ha realizado de forma totalmente 'artesanal'. Esto no es cierto. la GUI considera que la sesión está terminada y que no se puede mandar más eventos al hilo que la controla porque estará muerto. Es un proceso lento y no sabremos exactamente cuándo acabará: pero para evitar complicar la 'señalización intermedia' exigiendo a la pila confirmación a la petición de cierre de la sesión. Veremos más adelante que este tipo de objetos funcionan porque se ha realizado un diseño para el audio que permite a la GUI acceder a las muestras de audio a bajo nivel: algo que complica el sistema pero que le da una enorme flexibilidad (desde filtrado previo a la transmisión hasta elección de un hilo como fuente de sonido. • La parte inferior de la ventana contiene un botón marcado con la cadena "Hang". Hablaremos del audio en la penúltima sección de este documento. Este segundo elemento es bastante interesante. después de lanzar el evento. en la parte inferior y señalada con un pequeño icono de un micrófono.. barras para representar con LEDs el volumen del audio. podremos indicar el dispositivo cuyo volumen vamos a alterar mediante las constantes 'VE_DSP' (reproducción) o 'VE_MIC' (grabación). que nos permitirá cortar la llamada. para por ejemplo. etc.. Finalizar una llamada: que es la tarea principal y la que da nombre a este apartado. porque antes habrá recibido un evento 'SUA_ThMan_Event' del tipo 'TM_Update_Th_Pointer'. en la parte superior y marcada con un triángulo verde.. tendremos dos opciones con el mismo resultado: cerrar la ventana ó pulsar sobre el botón "Hang".. la llamada se considera finalizada y el agente vuelve a su estado de reposo. los flujos RTP y el audio. una para la salida de audio (DSP). Esta ventana puede realizar varias tareas. Por la otra. Cada una de estas dos subpartes se divide en otras dos: una barra de desplazamiento (slider) para controlar el volumen de grabación ó de reproducción y un medidor y representador gráfico de la potencia de audio. esperar su confirmación. A partir de este momento. Este evento encierra dos valores: por una parte. mediante el cálculo de la potencia de la señal discreta que recibimos y su representación mediante un array de LEDs digitales de distintos colores. pero demos algunos apuntes: esta sección se subdivide en dos partes. En la figura 11 se visualiza la dirección SIP remota. la ventana sabe cómo mandar los eventos de volumen al hilo en cuestión. Finalizar una sesión a nivel SIP no es algo trivial. etc. ya que pasará un cierto periodo de tiempo antes de cerrarlo todo: finalizar la sesión enviando una petición BYE.. aunque se realicen en la sombra). podremos indicar el nivel deseado mediante un tanto por ciento del total al que queremos llegar. 51 • • . se acompaña de un juego bastante completo de clases para la representación de barras: barras para indicar cómo se encuentran los búfferes de audio. que se encienden y se apagan para dar la impresión de una barra que crece y decrece. Cuando seamos nosotros los que decidamos cortar la llamada. y otra para la entrada (MIC). liberar recursos. • En la parte media de la ventana se encuentra el control del audio. siempre mediante el uso de eventos: • Modificación del volumen tanto de grabación como de reproducción: cada vez que se tocan los 'slider' se ejecutará una función de la ventana que manda al hilo hijo encargado de la llamada un evento del tipo 'SUA_Volume_Event'. reproducir ficheros de audio).

4. Para notificar al usuario el hecho de que su llamada ha finalizado se usa una sencilla caja de texto emergente. A esta opción --rtp-test se le pasan cinco parámetros: los pares dirección IP y puerto UDP para cada extremo de la comunicación y el códec para el audio. a través de la opción -r ó --rtp-test. 2. pequeño estudio sobre la cantidad de audio a transportar en un paquete RTP. que no da como resultado la ejecución de un UA sino la de únicamente un test para RTP. tanto en calidad como en ahorro de ancho de banda. entre las que destacan: • Comprobar la probabilidad que tienen dichos flujos RTP para alcanzar un destino o para atravesar firewalls. • Afinar en la calidad del audio transmitido y recibido: eliminación de micro cortes. En la sección dedicada a la pila SIP trataremos mucho más en profundidad todas estas cuestiones. el proceso es también muy sencillo: la pila nos enviará un evento de control de llamada ('SUA_Call_Event') del tipo 'S_Finish_Bye_200' con el que nos da a entender que la sesión está correctamente finalizada. Esta opción. Cuando comentábamos las distintas opciones que se le podían pasar al agente por la línea de comandos. • Evaluar los resultados obtenidos al usar un nuevo códec de audio. que los flujos RTP para transportar información multimedia han sido detenidos y que los recursos del driver para manejar los dispositivos de audio han sido correctamente liberados. Probar los flujos RTP y los códecs de audio. PCM con ley mu y GSM. la hemos usado para varias tareas.. hay actualmente tres códecs soportados: PCM con ley A. Veamos en la siguiente figura el aspecto que presenta la aplicación al ejecutarla de esta manera: 52 . Como ya se ha mencionado.etc. tanto ésta como la ventana de control de llamada finalizarán y el agente volverá a su estado de reposo..En el caso de que sea el extremo remoto el encargado de colgar la llamada. Tras pulsar sobre el único botón presentado. veíamos la posibilidad de usar el UA únicamente para generar y recibir flujos multimedia usando RTP..4.

cada vez que queremos escribir datos en el buffer y no tenemos espacio porque hemos llegado al final. se copiarán los datos no leídos al principio del buffer. la velocidad con la que las muestras se van despachando. nos encontramos con el buffer de reproducción en el que se almacena el audio que recibimos antes de reproducirlo por la tarjeta de sonido. Por lo tanto. la diferencia entre ambos (representada en azul en la barra) indica el grado de ocupación del buffer. porque parecen no tener nunca final. donde se almacenará el audio leído desde el micrófono antes de ser enviado por RTP al otro extremo. el tema del manejo del audio corresponde a la pila SIP. En cada uno de los dos grupos tenemos dos barras: la de la izquierda representa el volumen de audio. agrupadas en dos grupos de dos que referencian a un buffer. Por otra. La barra de la derecha contiene una representación del buffer en cuestión (ya sea de grabación o de reproducción) que nos sirve para comprobar su grado de llenado. Realmente. calculando la potencia de una ventana de muestras discretas y relacionándola con la máxima potencia posible para una ventana de igual duración). y la proporción de buffer usado en cada operación de escritura de datos. se añadirán los nuevos y se recomputarán los punteros. Puntero de lectura en rojo. A pesar de esta introducción. Los búfferes presentan la abstracción de parecer infinitos o circulares.Figura 16: Ejecución del agente con la opciíon -rtp_test La información que presenta en pantalla es bastante importante y vamos a explicarla: como se puede comprobar. Esta representación de los búfferes se lleva a cabo por dos objetos del tipo 'SUA_BufferBar' que representan en los bordes de la barra los punteros de cada buffer: • • Puntero de escritura en verde. pero usando un objeto de tipo 'SUA_AudioBar'. y por lo tanto 53 . de forma parecida a como se veía con los LEDs (es decir. se trata de cuatro barras. El puntero de escritura siempre estará más avanzado que el de lectura (verde>rojo). Por una parte está el grupo del buffer de grabación. porque cada lectura vaciará el buffer de contenido nuevo e igualará los dos punteros.

Otras acciones posibles.4.13 User name: Ibon Gotxi Garcia Ip address: 192.13. Consultar la configuración del agente.4. Quedan por comentar otra serie de acciones que la GUI pone a nuestra disposición.com Contact Addresses: 1º: sip:suicide@192.168. ya sabemos cómo usar la interfaz gráfica propuesta para realizar y recibir llamadas de audio.lo veremos más adelante.5.168. Ya hemos hablado de este tema en la sección dedicada a la configuración del agente a través de un sistema de ventanas.168. Sin embargo.2.13 Media Parameters: Audio device: /dev/dsp Mixer device: /dev/mixer Rtp audio port: 34000 Proxy server: sip:sipcenter.13.com Supported codecs: ->GSM (3) (selected) ->PCM ulaw (0) ->PCM alaw (8) 2. Cuando la ventana esté visible aparecerá junto al menú una marca ó tick: 54 . que aunque no tienen la importancia de las ya vistas.5.13.4. Recapitulando un poco lo visto hasta ahora.13 Sip public address: sip:sipcenter416@sipcenter. a través del botón denominado "Show": los resultados serán expuestos a través de la ventana de depuración. Veamos un sencillo ejemplo que hemos copiado de la propia ventana de debug: Tabla 9: Consulta de la configuración del agente User Parameters: Sip address: sip:suicide@192. centrémonos en la consulta de la configuración.1. así como para preguntar sobre las capacidades. En la figura 2 se puede comprobar el prototipo de lo que tal vez algún día llegue a ser un sistema de configuración completo. 2.5. Visualizar u ocultar la ventana de depuración. Para poder examinar la ventana de debug simplemente hay que chequear el ítem referido como "Debug" en el menú emergente. merecen aunque sea una pequeña mención: 2.

5. la licencia usada. se ejecutará la función 'void Ksua_Window::On_About_SUA()' que simplemente creará un objeto del tipo 'KAboutApplication' y lo mostrará. Salir del agente El último ítem del menú emergente ("Quit") está asociado a la función 'close()' de la ventana principal. por lo que el siguiente paso es la ejecución del destructor del 55 .3.5. 2. El hecho de que en el entorno Qt/KDE se cierre la ventana principal implica necesariamente el fin de la aplicación.4.4.Figura 17: Visualización de la ventana de depuración 2. la versión. El resultado conseguido es el siguiente: Figura 18: Ventana "Acerca de KSua" Esta ventana que se muestra ahora con tan poco esfuerzo ha sido creada nada más arrancar la aplicación (es la primera operación en 'main'). siendo en ese momento cuando se le indica el nombre del proyecto. Mostrar la ventana "Acerca de KSua" Cuando pulsamos el ítem marcado con el título "About KSua" en el menú emergente.4. el nombre del autor y su dirección de correo electrónico.

Este destructor realiza las siguientes tareas: • Liberar los recursos asociados a la ventana principal. Se trata de la última función que realizará nuestro UA antes de salir. porque implica que se intentarán cerrar de forma ordenada antes de su destrucción. el motivo. 56 . • Liberar los recursos asociados a la ventana de depuración. Este proceso puede ser largo. es que está esperando a que todos y cada uno de los hilos finalice su ejecución.objeto de aplicación. por lo que tal vez en alguna ocasión se note que el UA tarda en cerrar. • Llamar a la función 'int SUA_Thread_Gen::Delete_All_Threads()' que destruirá todos los hilos hijo creados por el hilo generador de hilos. y es una función bloqueante.

pero no tendría porque serlo. y lo harán de maneras diversas en situaciones impredecibles. Por lo tanto. la mensajería instantánea y la localización de personas son un buen ejemplo: para crear un script que mande un mensaje a un amigo no se debería tener un conocimiento exhaustivo ni de SIP ni de esta pila en concreto). Posibilitar todas estas actuaciones y algunas más dificulta enormemente el diseño. • Disponer de una interfaz estable entre la pila y los posibles front-end: esta es una cuestión que de momento no se puede soportar. supone un pequeño problema de falta de libertad para tomar ciertas decisiones. Hemos estudiado cómo un usuario puede interactuar con ella para crear sesiones SIP en general. o cualquier otro tipo de información. deberemos disponer de una interfaz para interactuar con la pila muy bien estudiada (es lo que denominamos 'señalización intermedia o específica') que cumpla básicamente tres objetivos: • Ha de ser lo suficientemente sencilla de usar para que personas que no conozcan SIP con detalle puedan programar software que use algunas de sus características más simples (aunque todavía no se ha desarrollado el soporte para las peticiones MESSAGE. Podría tratarse perfectamente de un script ó de un programa que se relacionase con el usuario a través de la consola del sistema. 57 . Simplemente se quiere resaltar el hecho de que el trabajo 'duro' es realizado por la pila SIP y que la función principal de la interfaz es hacer la vida más fácil a los usuarios (sobre todo a los usuarios noveles). la posibilidad de que el trabajo con los contenidos multimedia sea realizado por los usuarios de la pila y no siempre por la pila en sí (porque se disponga de mejores sistema de codificación/decodificación que los implementados en la pila). Sin embargo. pero que ello no incurra en incompatibilidades: la salvaguarda consiste en disponer de una interfaz estable. es una interfaz gráfica. se realizasen numerosas mejoras a la pila y a los diferentes front-end. Además. Pero comencemos a hablar ya de la pila SIP. se trata de una implementación muy específica: de hecho.3. Muchas veces se suele decir que son tan importantes las formas como el fondo. esta división constituye en sí misma otro problema. Por eso se eligió KDE y se ha dedicado tanto tiempo a explicar cómo se ha logrado una aplicación amigable para el usuario. La idea detrás de crear una pila es evidentemente la de fragmentar un problema grande en varios más pequeños. Sin embargo. que demanden características más avanzadas: por ejemplo. El ideal sería que con el tiempo. Deberemos tener siempre en cuenta el hecho de que terceras personas desarrollarán software que use la pila. porque se tiene que tener muy presente qué partes son las específicas de SIP y cuáles las lleva a cabo la pila pero podrían realizarlas otros sistemas. y no precisamente pequeño. DISEÑO DE LA PILA SIP AMPLIADA Hasta ahora hemos visto la parte del agente de usuario más cercana al usuario: la interfaz gráfica o front-end. durante el desarrollo mantener estática la interfaz. que es el verdadero núcleo del sistema. cómo se debe configurar y cómo esa GUI se relaciona con la pila para cumplir los deseos de los usuarios. También puede darse el caso de que al iniciar una sesión SIP no deseemos mandar SDP como carga ('payload') sino 'SOAP'. y esta frase lapidaria se adapta perfectamente a nuestra situación. Con esto no se quiere en ningún caso quitar mérito a la interfaz. una buena interfaz sirve de tarjeta de presentación para todo el conjunto del software. debe ser capaz de satisfacer a los usuarios más exigentes. • Al mismo tiempo. por lo que nunca es un campo que no se deba tener en cuenta. Sin embargo.

podemos estar refrescando el registro con nuestro proxy. sobre todo porque se necesita mantener un estado muy definido. Nuestros hilos hijos SIP. Además. Sin embargo. que desembocará también en la emisión de eventos. y que se encuentre siempre saltando entre las rutinas de recepción de los eventos. Sin más que comentar. nos metemos ya de lleno en el estudio de la pila presentada comenzando con el hilo 'padre' de todos los demás hilos: el hilo generador de hilos. y el control del programa retornará automáticamente porque la ejecución de la rutina pedida la realizará otro hilo en paralelo. siempre nos interesará un comportamiento asíncrono. SIP es mucho más: se trata de un protocolo para la iniciación. pero nada más lejos de la realidad: los hilos son muy poco costosos desde el punto de vista computacional. por ejemplo. tenemos dos premisas fundamentales: • Debe tener la posibilidad de manejar múltiples sesiones de forma simultánea : generalmente. Cuando éste termine. SIP es muy útil para realizar trabajos de mensajería instantánea o incluso para servicios web usando 'SOAP'. en el sentido que se pasarán la mayor parte del tiempo ociosos a la espera de eventos: ejecutarán entonces cierto código. Ejemplos de esto son las consultas DNS (que además suelen darse varias de forma consecutiva) ó el soporte para las transacciones (una petición y todas las respuestas asociadas). de llamadas asíncronas o no bloqueantes: un hilo (en general cualquier hilo) 'llamará' a un procedimiento usando un evento. Frente a este sistema. se presenta otro. y no podremos tampoco bloquear la pila ya que siempre asumiremos que tenemos varias sesiones independientes ejecutándose a la vez. alguien nos puede estar llamando (y deberá recibir como respuesta un 'comunicando') ó podemos estar comenzando a recibir un mensaje. cuando pensamos en SIP lo asociamos con SDP y con la realización de teleconferencias y videoconferencias. la programación funcional o basada en procedimientos se basa en llamar a funciones que realizan un determinado trabajo y esperar su retorno cuando han conseguido finalizarlo. Evidentemente. Hace que nuestro código sea mucho menos 'lineal'.En cuanto a la forma general en la que la pila debe actuar. que nos permita saber que hemos pedido la realización de un trabajo y que cuando nos llegue cierto evento sepamos qué hacer con él. en algunos casos ciertos procedimientos pueden realizarse con la ayuda de hilos de apoyo para evitar que se conviertan en llamadas lentas y bloqueantes: como veremos ahora. Lo que se pone de manifiesto es que aunque una persona sólo pueda mantener una conversación cada vez (obviando el hecho de las multiconferencias) eso no implica que la pila sólo esté manejando una sesión: puede darse el caso de alguien esté preguntando a nuestro agente por sus capacidades. • El acceso a la pila debe realizarse de forma asíncrona: en general. que llevarán datos de distinta procedencia. sobre todo cuando el tiempo de ejecución de la función es alto. este modo de operación no es apto para ciertas circunstancias. El motivo por el que usamos tantos eventos en la pila SIP parece por fin claro: no deberemos bloquear el hilo de ejecución de la interfaz gráfica. Cuando manejamos interfaces gráficas esto conlleva el no repintar las ventanas. devolverá otro evento como respuesta. 58 . y volverán al estado de espera aguardando la confirmación o la recepción de nuevas órdenes en forma de eventos. se convertirán en hilos manejados por eventos. como mínimo. A alguien le puede llegar a parecer excesivo. perder eventos de ratón y teclado y desemboca en un servicio de baja calidad. mantenimiento y liberación de sesiones de cualquier tipo. porque se trata de llamadas bloqueantes. ampliamente usado en este proyecto. Sin embargo. Parece claro que deberemos diseñar un mecanismo para asignar un hilo de ejecución a cada sesión. es más complicado usar procedimientos no bloqueantes.

Se disponen de clases que encapsulan el funcionamiento de un mútex ('QMutex') ó de una condición ('QWaitCondition') que permite dormir o despertar la ejecución de un hilo. se ha puesto tanto hincapié en el sistema de depuración que ya hemos comentado al tratar la interfaz gráfica. Hay tres formas básicas en la que los hilos se pueden 59 . Introducción a la programación con hilos Un hilo. ya que tendremos numerosos hilos de ejecución a la vez. porque existe un problema que no se presenta en la programación tradicional. El tercer y último problema del manejo de hilos es la depuración del código. Existen también otros métodos para comunicar procesos. como el contador de programa y la pila de ejecución como contexto para su funcionamiento. y que nos hacen la vida mucho más fácil.1. la sincronización no constituye nunca un problema. ya que los hilos comparten todos ellos el espacio de memoria. Desde el punto de vista de un agente usando las librerías Qt. Hilo generador de hilos Este hilo es el hilo principal de la pila SIP. En GNU/Linux el soporte para hilos está proporcionado por la librería ‘pthread’ y presenta una implementación denominada “uno a uno”. pero es más sencillo de crear y destruir porque se necesita una gestión menor de los recursos. A veces se le denomina contexto de ejecución porque cada hilo debe tener sus propios recursos. todos los hilos de un programa comparten muchos recursos tales como la memoria o los descriptores de los ficheros abiertos. claro está. de los que ya hemos hablado. Hablemos un poco de los hilos y de la ejecución concurrente en general: 3. Por ello los hilos se denominan también procesos ligeros: es un único flujo de control como un proceso. que creemos una memoria compartida. Por este motivo. Sin embargo. Otro problema a la hora de tratar con hilos es la comunicación entre hilos. Además.3. hemos de decir que Qt proporciona eventos inter-hilo. es una tarea que hay que realizar. Se necesita por lo tanto mecanismos para controlar el acceso a las variables compartidas. frente a la sencillez que presentan los hilos en este sentido). ni mucho menos. pero no viene al caso mencionarlos aquí. marcando puntos de ruptura cuando queramos parar. aunque no es un proceso complicado. la comunicación entre hilos siempre será muchísimo más sencilla que entre procesos.1. Depurar programas multihilo no es sencillo. y uno de ellos podría sobrescribir los datos que otro acaba de escribir. ya que es el primero que se crea. hebra ó 'thread' es un flujo único de control dentro de un programa. porque no podremos usar el método tradicional de ejecución paso a paso. es posible para dos hilos acceder a la misma variable o ejecutar el mismo método del mismo objeto a la vez.1. y si no existe ninguna sesión activa será el único que contenga la pila. O un hilo podría trabajar sobre el resultado intermedio de otro hilo y anular la consistencia de los datos. Mucha gente comenta que programar con hilos es complicado. Es muy importante para este proyecto que todos los elementos sean lo suficientemente 'parlanchines' como para tenerlos controlados en todo momento. mientras que distintos procesos no comparten memoria (a menos. Los hilos pueden adelantarse los unos a los otros. Los problemas surgen cuando múltiples hilos acceden a los mismos datos de manera concurrente. que es el de la sincronización de los hilos: puesto que los hilos de un programa comparten el mismo espacio de memoria. Si bien es un aspecto que hay que evaluar con mucho cuidado.

1. como no maneja ninguna sesión. Se disponen de tres funciones de depuración y una de apoyo: 'void SUA_Thread::Debug_Plain(QString t. que es el que accede a la red de forma real.2. Cada método necesita una cantidad de apoyo del sistema operativo y tiene sus propios defectos y virtudes. 'void SUA_Thread::Debug_Error(QString t)' y 'void SUA_Thread::Debug_Warning(QString t)'.implementar en un sistema: de muchos a uno. Realmente no lo hacemos nosotros. que ya hemos comentado que sirve para que el usuario conozca a qué se dedica el hilo que manda la información. que será recibida a través del constructor. de uno a uno y de muchos a muchos. • Características avanzadas de depuración: todos los hilos cuentan con un puntero a la ventana de depuración que les permite mandarle eventos para que los imprima en la pantalla (este puntero se establece al crear el hilo. pero vamos a adelantar que los hilos reciben los paquetes con datos de la red a través de determinados eventos que genera el hilo generador de hilos. Además. usando esta función se encuentran las tres funciones de depuración que usarán todos los hilos: 'void SUA_Thread:: Debug_Info(QString t)'. • Posibilidad de acceso a red básico: este es un tema que trataremos un poco más tarde. Como decimos. para imprimir información general acerca de eventos relevantes. y cuanta más. que mandará a la ventana de depuración (usando el puntero almacenado) un evento con la cadena 't' y el formato 'type'. Si algún día nos decidimos por dejar de usar Qt en nuestro UA (posiblemente en un futuro lejano) deberemos encapsular nosotros mismos la clase. Como todos los hilos generarán información. encapsulamos el comportamiento del hilo en una clase. int type)' es la función genérica. evidentemente. • Posibilidad de almacenamiento de todos los parámetros definidos por el usuario para la aplicación mediante una estructura de tipo 'Parameters'. en el constructor). Es una funcionalidad enfocada más hacia el usuario. Se trata ésta de una librería que implementa hilos acordes al estándar POSIX y que se encuentra escrita en C. llamado 'id' (de 'identity') del thread. GNU/Linux usa una implementación uno a uno (que tiene como inconveniente la pérdida de rendimiento debido a numerosos cambios de contexto) a través de la librería de hilos ‘pthread’. • Soporte para asignar una cadena de texto a cada hilo (tipo) que permita reconocer fácilmente a qué se dedica. mejor. Los hilos ‘SUA_Thread’ Para usar hilos en nuestro proyecto. se ha decidido incluir esta característica en la clase base de la que se derivarán todos los hilos. Para la emisión de información ocurre lo mismo. Qt usa la librería 'pthread' para crear una clase de hilo usando las funciones en C suministradas. deseamos unas características adicionales para todos los hilos de nuestro proyecto: • Soporte para identificar el hilo por un número. Nos servirán. 3. Además del comportamiento por defecto. • Posibilidad de asociar un hilo a un Call-Leg concreto si fuese necesario: cada hilo hijo manejará una sesión con un Call-Leg específico. Los nombres de los métodos se refieren al número de hilos que 've' la aplicación en espacio de usuario comparado con el número de hilos (o procesos) que ve el sistema operativo en el espacio del kernel. Por otra parte. no se le asigna Call-Leg alguno). que servirá para direccionar la información que le va llegando (al hilo generador. cosa que adelanto no es demasiado difícil. sino que usamos la clase 'QThread' proporcionada por las librerías Qt: es decir. para que pueda controlar en cada momento todos los hilos en ejecución y su cometido. errores en la ejecución del protocolo SIP 60 . prepondrá a la información suministrada el 'tipo del hilo'.

Mediante los eventos de tipo 'SUA_ThMan_Event' que veremos más tarde. y que se deriva de 'QThread'. Creará la ventana de progreso que deberá recibir todos los eventos previos a la consolidación de la sesión. 3. Conforme comience la actividad y proliferen las sesiones. siempre en la última posición. Indicando el tipo de mensaje de control de llamada y una cadena de texto adicional. pero tampoco sea lo que se esperaba que fuese. la lista acaba ordenando inversamente los hilos por antigüedad. Este puntero nos permitirá mandar eventos a la interfaz gráfica a través de su ventana principal. una variable global será conocida por TODOS los hilos y por todas las clases en cualquier momento. tenemos la ventana de llamada que manda un evento para que comience la llamada. no a su clase 'real' 'KSua_Window'). cada sesión nueva llevará un hilo asociado. Por lo tanto.1. y que será siempre configurable y podrá cambiar durante la vida del hilo. Por lo tanto. Esta clase servirá de clase base para todos los hilos del agente. Esa variable global de la que hablamos será una lista del tipo 'SUA_Thread_List'. La lista de hilos ‘th_list’ Al hablar de la interfaz gráfica siempre dábamos por sentado el hecho de que conocíamos un puntero de tipo 'SUA_Thread*' al hilo generador de hilos. hecho que nunca a sido buscado pero que constituye un efecto colateral. la pregunta es: ¿Dónde se almacena ó cómo puede el front-end acceder a dicho puntero?. Y cuando la llamada esté en marcha. y que a través de éste. temporizadores.3. la clase de Qt que proporciona soporte para el manejo de filtros de eventos. sino que tras la inicialización del agente. no tiene que ser obligatorio que se trate tampoco de la ventana principal de la GUI. Cuando se habla de ventana principal. nunca se encontrará vacía. Además se adjunta una función que nos permite notificar eventos de llamada: void 'SUA_Thread::Notify_Main_Window(int code. la ventana de control de la llamada recibirá todos los eventos: tenemos tres ventanas que interactúan con un sólo hilo. tendrá un único elemento. De esta forma. y de esta forma el rutado de los eventos entre las ventanas será mínimo (es el típico caso de realización de una llamada: al principio. QString txt)'. podíamos comenzar los procedimientos con la pila. que usarán algunas de sus funcionalidades (debido a lo básico de sus funciones. derivada de la plantilla ('template') de listas de Qt: 'QList'. • Soporte para almacenar un puntero a la ventana principal de la aplicación (se trata de un puntero a su clase base 'QObject'. La respuesta es simple. El primer elemento de la lista es sin duda el más 61 . cuya primera posición estará siempre ocupada por el puntero al hilo generador de hilos. por lo que siempre se hacía fundamental el conocer el puntero al hilo generador para comenzar las operaciones. y dicho hilo tendrá una entrada en la lista de hilos. el front-end puede asignar una nueva ventana a un hilo. y lo harán 'registrando' su puntero a través de los eventos de gestión de hilos). que luego ésta será responsable de distribuir. señales y slots (hablaremos luego de las señales y de los slots).ó SDP y advertencias en el caso de que algo no esté mal. la mayoría de los hilos acabará usando todas las características). Aquí tenemos el primer ejemplo de las ventajas que supone el usar hilos: como el espacio de direccionamiento es único. y todos comparten la memoria. a través de una variable global. la propia pila nos notificaría punteros a otros hilos para que nosotros mandásemos allí nuestros eventos (a través de eventos de gestión de hilos del tipo 'SUA_ThMan_Event'). Se trata más bien de la ventana que deberá recibir los eventos de este hilo en particular. Estas siete características que hemos comentado se implementan en un objeto denominado 'SUA_Thread'. Se trata de una lista encadenada de punteros a hilo. Cuando dichos procedimientos avanzasen. la clase de Qt que encapsula el funcionamiento de los hilos y de 'QObject'. construiremos un evento de llamada y lo mandaremos a la ventana principal.

Esta lista se denomina 'th_list' ó lista de hilos. El constructor del objeto de aplicación creará el hilo generador de hilos y lo colocará en la primera posición. Se mencionaba también. el hilo generador se encargará de pararlos. 'QList' es un template que tiene un miembro llamado 'QList<type> QList::at(int)' que devuelve el elemento en la posición especificada. A una petición se le responde con una o varias respuestas (two way handshake). por lo tanto.at(0). borrar ó añadir elementos nunca fue tan fácil como con 'QList' de Qt. Antes de quedarnos bloqueados accediendo al mútex tendremos la posibilidad de llamar a su método 'bool Qmutex::isblocked()' para evitar un bloqueo indefinido. Porque todavía no se ha introducido el concepto de transacción. Conforme se vayan añadiendo hilos. Para ello se asocia a la lista 'th_list' un mútex (un objeto 'QMutex') llamado 'th_list_mutex' que nos permite bloquear su acceso. la versión SIP que acababa de salir era la versión 2. se prevén nuevos protocolos sobre los que transportar SIP.4. no se prevé obligar a los agentes a que lo soporten (entre otras cosas porque muchos sistemas operativos todavía no lo implementan). Nuestro agente únicamente trabaja sobre UDP. 3. Posteriormente. De esta forma. ¿Por qué esta ligadura?. y el propio diseño de la pila está fuertemente ligado al uso de UDP. algo muy útil si accedemos a la lista desde el front-end. 'QList' dispone de numerosos métodos que hacen del manejo de lista encadenadas un juego de niños: insertar. debido a su novedad. TCP y UDP se hacen obligatorios para que todo agente que se ajuste al estándar. porque se crea al arrancar y nunca se destruye. Veamos en qué consisten los dos: • Concepto transacción: una transacción es un conjunto petición/respuesta (request/response) ó en plural. que a su vez tal vez deban ser confirmadas con otra petición sin respuesta (three way handshake. aunque. liberar sus recursos y borrarlos de la lista eliminando su 'nodo'.0 bis 3. deberemos detenernos en su estudio.viejo. en el caso de peticiones INVITE+ACK). todos avanzarán una posición hacia el hilo generador. la cual especificaba que el protocolo de transporte sobre el que debía funcionar era UDP. La transacción se concibe como una unidad atómica e independiente. TCP también era un protocolo válido. debido a que la parte del proyecto dedicada a la codificación acabó antes de que TCP fuese obligatorio. En el momento de escribir estas líneas. accederemos a él de la siguiente forma: SUA_Thread *gen_th = th_list. únicamente el concepto de diálogo. y según vayan terminando de realizar sus tareas. Acceso a la red En un agente de usuario SIP. Cuando este agente comenzó. se colocarán en la última posición (el hilo generador los crea y los coloca. y por lo tanto el hilo más viejo será el más próximo al generador. al eliminar un hilo. que aunque no se recomendaba su uso. Es más. como más tarde veremos). el acceso a la red es fundamental. y a partir de la sexta revisión. como SCTP ('Stream Control Transmission Protocol'). Un detalle importante que se nos ha olvidado comentar es que la lista de hilos es una variable compartida (es una variable global) y por lo tanto deberá protegerse de accesos simultáneos (accederán a ella principalmente el hilo generador de hilos para su mantenimiento y el hilo de la interfaz gráfica para tareas de depuración). que acabará con un valor 62 .1.

Vamos a estudiar concretamente el estado del acceso a red en el UA actual para luego ver cómo sería el modelo mejorado basado en transacciones. un diálogo se compone de transacciones que deberán ir todas sobre el mismo protocolo de transporte: la elección de TCP. Concretamente un Call-Leg consta de una copia de las cabeceras "To". creada por el emisor de la petición (cliente) y aceptada o rechazada por el emisor de las respuestas (servidor). La tercera es la que permite la existencia de múltiples diálogos simultáneos entre dos entidades SIP. deberá ser común a todas las transacciones en un mismo diálogo. que sería un hilo de ejecución en paralelo. UDP ó SCTP para una sesión dependerá de la dirección de transporte expresada en la dirección SIP de la persona a la que queremos llamar. Además. Por defecto se usará UDP. aunque se realicen dentro del mismo diálogo. Esta atomicidad se expresa en el hecho de que si el protocolo de transporte es TCP. La posibilidad de cancelar transacciones debe poder realizarse en cualquier momento y para todas las peticiones. Se trataría de una forma de operar un poco más complicada que la forma tradicional basada en funciones bloqueantes y requeriría del uso de un hilo de apoyo. • Concepto de diálogo: un diálogo (o también denominado sesión) es un conjunto de transacciones relacionadas con un mismo Call-Leg (estructura formada por varios valores únicos en la red). las dos primeras asocian el diálogo a dos extremos SIP únicos.afirmativo si las respuestas a la petición son afirmativas ó negativo si así lo son las respuestas. basado en el paso de mensajes. TCP). Para solucionar este problema el acceso a red debería implementarse por cada transacción. Para soportarlo. no se puede dar soporte actualmente a otros protocolos de transporte (el más interesante sería. ya que asociamos a cada sesión un hilo SIP hijo. controlado por este hilo generador de hilos y almacenado en la lista global de hilos. sería muy interesante para las transacciones un modelo asíncrono. El servidor deberá parar de realizar la tarea asociada a la petición original y confirmar la cancelación. y no dedicarse a enviar paquetes discretos como hace ahora. De no hacerlo de manera asíncrona. porque el hilo que acceda a la red debería mantener un estado para conocer si el paquete que debe enviar debe ir sobre UDP ó TCP. porque no se establece ninguna relación entre las transacciones y el nivel de transporte. Veamos un cuadro resumen y estudiémoslo desde ahí: 63 . El concepto de diálogo se encuentra actualmente totalmente soportado en la pila. pero sea cual sea el protocolo que usemos. Como consecuencia de estas características comentadas. cada hilo SIP (hilo trabajador ó de apoyo) debería crear transacciones a la hora de acceder a la red. cancelar una transacción sería difícil. Pero debido a que no hemos dispuesto de tiempo para incluir el concepto de transacción. evidentemente. a través del hilo generador. pero por una parte no bloquearía al llamante y por otra permitiría su cancelación simplemente mediante la emisión de un evento. El hilo generador de hilos asociará un Call-Leg a cada hilo que maneja una sesión. Transacciones distintas implican conexiones TCP distintas. No se trata de una cuestión tanto de acceso a la red como de relacionar los paquetes SIP. Como decimos. En el caso de trabajar sobre UDP es todo más sencillo. Otra característica importante de las transacciones es que deben poder cancelarse antes de su finalización: esta acción se realiza enviando al servidor una petición de tipo CANCEL asociada a la petición original. "From" y "Call-Id". en el caso de ir sobre TCP debería conocer si va sobre una nueva conexión TCP o sobre una existente. cada transacción irá sobre una conexión TCP.

64 . como veremos luego.Figura 19: Sistema de acceso a la red para leer En este cuadro se representa únicamente la recepción de paquetes SIP. El hilo generador de hilos consiste básicamente en una función que se ejecuta en paralelo y realiza dos tareas: • Procesado de los eventos entrantes. la emisión es independiente por utilizarse sólo UPD. y la comentaremos luego. la escritura también corre a cargo de este hilo pero se realiza a través de los hilos de apoyo SIP ó workers. • Lectura de la red.

Retornando con FALSE del envío del paquete. deberemos comprobar si se trata de una petición ó de una respuesta. Iteraremos sobre la lista de hilos 'th_list' hasta conseguir una coincidencia entre el Call-Leg entrante y el Call-Leg asociado a cada hilo (que representa una sesión). dispondremos de una cadena de texto normal y corriente. Deberemos por lo tanto crear con esa cadena de texto dos objetos: petición y respuesta.1. porque o bien se trata de una petición no soportada (caso de peticiones REFER ó INFO) o bien porque no tiene sentido enviar dicha petición fuera de una sesión (es el caso de ACK. y también se creará un hilo asociado. Si nos encontramos con una petición. El hecho de centralizar la distribución de paquetes desde el hilo generador permite realizar una única implementación del sistema y por lo tanto es una técnica menos propensa a errores.1. después de despachar el mensaje ó de generar el aviso ó error correspondiente. Cuando recibamos un paquete por la red. porque las respuestas solitarias e independientes no tienen sentido en SIP. Le extraeremos su Call-Leg mediante el método estático 'Call_Leg SUA_Body::Get_Body_Call_Leg_From_String(QString)'. generaremos un evento 'SUA_String_Event'. que nos devolverá un objeto Call-Leg con el Call-Leg del paquete (inicialmente una cadena). Como inicialmente se ha supuesto el trabajar sobre UDP. 3. La idea es sencilla: la cadena de texto recibida será un petición ó una respuesta (ya lo hemos comprobado). se lo enviaremos al hilo y retornaremos TRUE. Una petición OPTIONS creará otra sesión. Si el paquete recibido es una respuesta y no pertenece a ninguna sesión activa en ese momento. Si el Call-Leg del paquete recibido no se corresponde a ningún Call-Leg asociado tal vez se trate de un paquete que contenga una petición de inicio de sesión. Es muy sencillo. Si el paquete recibido no es sintácticamente ni una petición ni una respuesta (preguntaremos para ello a los métodos 'bool SUA_Response::Is_Response_Ok()' y 'bool SUA_Request::Is_Request_Ok()') se habrá producido un error y tendremos una cadena mal formada. cada hilo hijo tendría que realizar esta operación. En cualquier caso. ésta de petición de características. Esta sencilla forma es la que se implementa para que sea el hilo generador el que lea de la red.Durante la mayor parte del tiempo este hilo se encontrará en espera de recibir datos por la red y será interrumpido únicamente para manejar los eventos recibidos. BYE y CANCEL) o bien porque dicha petición no se soporta como servidor sino únicamente como cliente (el ejemplo más claro es REGISTER). por lo tanto. ¿Por qué este sistema?.4. estudiaremos su tipo: una petición INVITE generará una sesión SIP y el hilo generador creará un hilo worker para atenderla. se retornará a la función 'int SUA_Thread_Gen::Wait_For_Data()' para que el hilo se quede bloqueado a la espera de nuevos datos por la red ó de la llegada de eventos que le saquen del bloqueo. La espera se realiza en la función 'int SUA_Thread_Gen::Wait_For_Data()' que es una función bloqueante que aguarda a la recepción de un paquete SIP completo. Si existe tal coincidencia. Envío de información 65 . podría leer paquetes que no corresponden a la sesión a la que representa. Se sale de dicha función o bien cuando debemos procesar un paquete o un evento ó cuando se sale de la aplicación y se cierra el hilo generador. En caso de tratarse de otro tipo de petición. se descartará y se generará un aviso. pues SIP no usa ningún tipo de codificación binaria. haremos que el hilo generador de hilos llame a su método 'bool SUA_Thread_Gen::Send_To_Thread(QString)' para despachar la información hacia el hilo que ostente la representación del diálogo dentro del cual se enmarca ese paquete. y que no lo haga cada 'worker'. siempre se generará un aviso y se desechará. si cada hilo leyese de la red. Si no hay error.

Cuando definíamos el objeto que encapsula el funcionamiento de los hilos en la pila ('SUA_Thread') veíamos las dos funciones siguientes: 'bool SUA_Thread::Send_String(QString. Ambas toman como parámetros la cadena de texto. la dirección IP del destinatario (encapsulada en un objeto 'QHostAddress') y el puerto UPD de destino (el de origen se configura a través de la dirección SIP actual. SIP soluciona este problema mediante la repetición a tiempo exponencial de ciertas peticiones (mecanismo de retransmisión). tal y como vimos en el apartado de configuración). int)' y 'bool SUA_Thread::Resend_String (QString. pero la emisión la realiza cada hilo a través del hilo generador: estudiemos este concepto. SIP es independiente del protocolo de transporte inferior). El estándar de SIP recomienda el uso de UDP como transporte para las peticiones y las respuestas (aunque como ya hemos visto. Es más.QHostAddress. Accede a él a través del puntero a dicho hilo que se encuentra en la lista global 'th_list'. en la posición cero (como recordamos. La primera sirve para mandar por UDP una cadena de texto (generalmente un objeto 'SUA_Request' ó 'SUA_Response' serializado) una única vez y la segunda para mandarlo hasta siete veces separadas por un tiempo exponencial.Para estudiar la forma en la que la pila envía los paquetes SIP seguiremos con el modelo sencillo. La forma que tiene la función 'Send_String' de mandar los datos es sencilla: lo hace a través del socket único que crea el hilo generador de eventos. La función 'Resend_String' tiene un comportamiento algo más complejo. pero como también sabemos. la primera entrada corresponde al hilo generador de hilos). que soporta únicamente UDP y no está basado en transacciones. La recepción de información de la red corre a cargo del hilo generador. Veamos en el siguiente gráfico cómo funciona: 66 .int)'. la segunda función usa la primera para realizar cada uno de los posibles siete envíos. QHostAddress. UDP no es confiable y puede haber pérdida de datos.

y para cada tipo de transacción deberemos estudiar si es la petición la que tenemos que transmitir. generalmente. a lo que nosotros responderemos mandando otra vez (un mensaje de respuesta por petición) una copia de la respuesta. En primer lugar llamaremos a la función 'bool SUA_Thread::Resend_String(QString. generalmente seremos nosotros. En cualquier caso. El control del hilo retornará a las diversas funciones que debe realizar para manejar la sesión. se vuelve a retransmitir (llamando. desearemos mandar un determinado paquete al extremo remoto de la sesión. significa que el paquete ha viajado siete veces (una 67 . como por ejemplo atender a los eventos recibidos.int)' a la que pasaremos la cadena de texto a enviar y la dirección de transporte de destino (dirección IP y puerto UDP). hasta que uno de esos eventos sea un evento de temporizador ('QTimerEvent') momento en el cual ejecutará 'void SUA_Thread::On_Resend_Timer_Event(void)' para atenderlo. Si todavía no se ha llegado a retransmitir el paquete por sexta vez. como es natural. y si no llega.Figura 20: Módulo de reenvío de mensajes En primer lugar.QHostAddress. será el cliente (el otro extremo) el que repita la petición. y si se pierde por el camino. sobre UDP. si se trata de una respuesta la mandaremos una única vez. ya sea petición ó respuesta. Esta función mandará la cadena una vez y creará un temporizador para que salte dentro de 'RS_T1' segundos (generalmente se escoge RS_T1 = 2 segundos). el cliente. Si se trata de una petición. retransmitiremos la petición. veamos cómo se maneja la retransmisión de un determinado paquete SIP. Si ya hemos realizado la sexta retransmisión. o si por el contrario son las respuestas las que necesitan retransmisión. y eso lo haremos esperando la respuesta asociada. siendo 'n' el número de veces que hemos transmitido este paquete en total. a 'Resend_String') y se rearma el temporizador haciéndole que salte dentro de 'n*RS_T1' segundos. De cualquier modo esto nunca es una regla. los responsables de que llegue correctamente.

Normalmente.1. Posiblemente retornará abundante información acerca de la respuesta que ha dado el otro extremo SIP. • Transacción finalizada sin éxito: desde el objeto de transacción hacia la pila. 3. La idea consiste en crear una serie de clases que implementen el concepto de transacción: cada clase realizará una transacción sobre un determinado protocolo de transporte. Por ejemplo tendremos una clase para realizar el registro sobre UDP.4. • Cuando recibamos desde el hilo generador de hilos un evento con una respuesta a nuestra petición y la aceptemos. La forma de manejar las retransmisiones desde los hilos de trabajo es la siguiente: • Llamaremos a 'Resend_String' para iniciar el envío de la información. Después de crear el objeto que representa la transacción (por ejemplo perteneciente a la clase 'MESSAGE sobre TCP') este evento pondrá a actuar el hilo que se encarga de realizar las operaciones pertinentes. Informa a la pila que todo ha finalizado correctamente. podría implementarse como una clase abstracta). Se pretende que el manejo de transacciones sea asíncrono a través de unos cuantos eventos que se definirían a tal efecto: • Iniciar transacción: desde la pila hacia el objeto transacción. 68 . Así mismo ocurrirá para las transacciones asociadas a las peticiones OPTIONS ó BYE. Informará del error que el extremo contrario ha reportado. Después de pasar '7*RS_T1' se llamará a la función 'Resend_String_Timeout' que deberá contener el código para manejar la situación correctamente. • Si no aceptamos la respuesta ó no existe dicha respuesta.2. esta función es reimplementada en cada hilo hijo (ó worker) porque en la clase 'SUA_Thread' no tiene encomendada ninguna función (ya que se trata de una clase que no se debe instanciar. llegará la sexta retransmisión de la información. ya que tendríamos que establecer un complejo sistema de variables compartidas que sería necesario chequear en todo momento. Como ya sabemos. • Transacción cancelada: desde la transacción hacia la pila. librando al extremo remoto de la carga que supone procesar una petición en su totalidad. Para dejar de mandar más veces el mismo paquete se llama a la función 'void SUA_Thread::Cancel_Resend_String()' que libera recursos y desarma el temporizador y posteriormente a 'void SUA_Thread::Resend_String_Timeout()' que deberá contener el código necesario para actuar cuando no se recibe respuesta. • Transacción finalizada con éxito: desde el hilo contenido en el objeto de transacción hacia la pila.transmisión y seis retransmisiones). Modelo basado en transacciones Vamos a comentar en unas pocas líneas cómo se espera que sea el soporte para red en las futuras versiones de la pila. otra para realizarlo sobre TCP y otra para realizarlo sobre TCP dejando la conexión TCP abierta (para saltarse NATs). por lo que. por cierto. Supone la confirmación del evento anterior. ésta sea cancelada. por lo que si todavía no hemos recibido respuesta es muy probable que el extremo remoto no exista o no quiera contestarnos. deberemos cancelar la retransmisión mediante 'Cancel_Resend_String'. • Cancelar transacción: desde la pila hacia el objeto transacción. El hecho de usar transacciones asíncronas posibilita que antes de finalizar la misma. Si el modelo de transacción usado fuese síncrono (mediante el uso de funciones bloqueantes) resultaría muy difícil su cancelación. toda petición se puede cancelar con el método CANCEL.

INVITE. Por último es necesario comentar el caso de la petición más famosa. esta transacción además de tener dos peticiones. Sin embargo. Gestión de los hilos SIP de apoyo El manejo de las sesiones SIP se lleva a cabo por los hilos hijos SIP. el protocolo de transporte y el puerto). OPTIONS ó MESSAGE. paquetes que serán leídos y analizados. Además da una mayor sensación de orden y el código resultante será más autocontenido y sobre todo. que presenta el comportamiento más complicado para ser encorsetado en una transacción. desde un principio únicamente estaba pensado dar soporte para UDP. nos damos cuenta de que la petición y las respuestas asociadas viajan todas entre las mismas direcciones de transporte (tripleta formada por la dirección IP. si deseamos sesiones complejas y soporte para otros protocolos de transporte serán imprescindibles las transacciones. y del él se reciben las respuestas 100 y 200 (si la llamada tiene éxito).El hecho de usar transacciones ofrece ventajas sobre no usarlas sobre todo cuando tenemos sesiones complejas formadas por múltiples transacciones. cómo se gestionan y cómo se destruyen. el desarrollo de los hilos de transacción será atómico. Se basa como ya se ha comentado en mandar eventos Qt.1. Consistirán en una manera inmejorable para ahorrar código. tal y como es nuestro caso. pero más complicado si usamos TCP). usaremos dos ó tres transacciones por sesión. el INVITE se manda al proxy local (si es que hay alguno especificado). En el tipo de sesiones que hemos implementado en nuestro agente. sea demasiado complicado para nuestros requerimientos. En esta sección vamos a especificar cómo se crean estos hilos. REGISTER. concretamente del tipo de gestión de llamada ('SUA_Call_Event') directamente al hilo generador de hilos a través del puntero localizado en la primera posición de la lista global de hilos activos ('SUA_Thread_List th_list'). modular. Pero existe una tercera petición. 69 . Creación de hilos como cliente por medio de eventos Este sistema para crear sesiones a través de eventos será evidentemente usado desde la interfaz gráfica o front-end para 'mandar' a la pila que realice una determinada tarea. Para ello haremos referencia tanto a eventos mandados desde el front-end hacia la pila como a paquetes enviados desde un extremo remoto SIP hasta nuestra posición. ya que si una transacción se usa dos veces en una sesión sólo se requiere escribir el código una vez. Además. 3. ACK (para implementar el 'three way handshake'. Comencemos primero por ver cómo crea la pila los hilos para atender a las sesiones a las que representan: 3. que requiere de un hilo en paralelo para mandar y recibir los mensajes. tal y como hemos comentado varias veces.5.1. Especificando bien la información que se le deberá suministrar a cada transacción y la que ésta devolverá. en el sentido que no modificará en nada el resto de la pila. por lo que tal vez el sistema de transacciones asíncrono. Sin embargo.1. que de momento se reserva para las transacciones de inicio de sesión) que irá desde el cliente hasta la dirección de transporte del servidor especificada en la cabecera de contacto de la respuesta 200 que hemos recibido desde el servidor proxy. Cuando vemos los modos de operación de BYE.5. mientras que si usamos la emisión de paquetes discretos deberemos repetir ó tal vez tripitir el código. Por lo tanto. requiere el cambio de destino (algo muy sencillo de asumir si usamos UDP.

particularizando para cada caso concreto. El tema del puntero a la ventana que recibe los eventos es curioso. esa cadena de texto contendrá la dirección SIP del destino al que queremos llamar. almacenada en una cadena de texto. La pila deberá notificárselo de alguna manera: ahí es donde entran en juego los eventos 'SUA_ThMan_Event'. un puntero a la ventana que recibirá los eventos de respuesta (forma de acceder a la interfaz gráfica) y 70 . el Call-Leg del que se le hace responsable. por ejemplo la evolución de la llamada. Todo ello se realiza usando la función estática de la clase SUA_Body (de la que hablaremos al comentar el párser) 'Call_Leg SUA_Body:: New_Body_Call_Leg (SUA_Parameters. de un valor aleatorio y de la dirección IP de la máquina en la que se ejecuta el agente (cabecera Call-ID). por último. en este caso un hilo cliente ('SUA_Thread_Client') a cuyo constructor se le pasa: la dirección SIP del destino. esta ventana puede ser destruida para reflejar ciertas cosas. La función encargada de crear el hilo cliente para realizar llamadas realizará los siguientes pasos: • Creará un nuevo objeto Call-Leg a partir de la dirección SIP pública que configuramos a través del front-end. De esta forma creamos la asociación entre un diálogo y un hilo de ejecución. En sentido contrario también se aplica esta premisa: a la hora de pedir la creación de una sesión la GUI no sabrá qué hilo la va a atender y no sabrá cómo acceder a él (por eso emplea el hilo generador). A partir de este momento. El primero de ellos. Es el 'identifier' ('id'). que se usa siempre para múltiples propósitos.El hilo generador aceptará tres tipos de eventos de llamada: • • • W_Make_Call W_Query_Options W_Register El lector avispado se habrá dado cuenta de que todos los subtipos mencionados aparecen precedidos de la letra "W" (uve doble mayúscula) lo que indica que se trata de eventos generados desde la GUI (desde las ventanas ó 'Windows') hacia la pila. del que comentaremos algo más adelante) que se ha creado usando la cadena de texto contenida en el evento y un puntero a la ventana emisora de dicho evento. Todos lo eventos de llamada poseen un campo de información general. los parámetros de configuración del agente. como ya se explicó en el primer punto de este diseño de alto nivel (cabecera From). QObject*)' al que se le pasará un objeto de dirección SIP ('SUA_Address'. • Se genera un número que identifique el hilo y que no esté siendo usado en este momento. 'W_Make_Call'. El hilo generador ejecutará su método 'void SUA_Thread_Gen::Create_Client_Thread(SUA_Address. En éste. También a partir de la dirección SIP del destino que se comportará como servidor (cabecera To) y a partir. por lo que deberemos contar con mecanismos para indicarle a la pila el nuevo destino de los eventos de respuesta. todos los mensajes SIP que contengan este Call-Leg serán tratados como pertenecientes a este diálogo y despachados hacia el hilo que vamos a crear. el identificador de hilo. y representará la posición del puntero al hilo hijo en la lista de hilos activos. se usa para crear sesiones orientadas hacia el establecimiento de teleconferencias ó llamadas de audio (sesiones en general. Se realiza esta operación mediante 'int SUA_Thread_Gen:: Get_Next_Available_Id (void)'. • Se crea el hilo en sí. y enlaza con el de los eventos del tipo 'SUA_ThMan_Event': si bien tendremos un puntero a la ventana que en un principio deberá recibir los eventos de respuesta. pero que al tratarse nuestro UA de un soft-phone se particulariza para este caso). SUA_Address)' a la que pasamos el objeto que contiene los parámetros configurados y la dirección SIP de destino.

Como se puede comprobar. • Reserva de un identificador para el hilo. • Ejecución del hilo. A nivel de hilo generador el modo de operación es muy similar a los dos anteriores. QObject*)' a la que pasamos.5. • Creación del objeto que implementa el hilo. En un modo de operación normal no es necesario del usuario ningún tipo de información adicional dada en tiempo de ejecución. leemos cadenas de texto sin formato. Quizás se trate de la operación a nivel SIP que más información requiera. • Se añade dicho hilo a la lista global de hilos activos. Como veíamos antes. del funcionamiento del servidor (en modo proxy ó redirector) y de los demás parámetros. cuando leemos de la red. como en el caso anterior la dirección SIP de destino y el puntero a la ventana emisora del evento (como sistema para poder devolver la señalización). de las direcciones de contacto escogidas. y por lo tanto su éxito depende en gran medida de la forma en la que hemos configurado la dirección SIP actual.puntero a la ventana de depuración para manejar la información de debug. Pretenderemos de esta forma el crear hilos SIP servidores que atiendan a las demandas de conexión de extremos remotos en principio desconocidos para nosotros. Después de analizarlas sintácticamente y saber que se trata de peticiones ó respuestas y después de comprobar 71 . La función que realiza el procedimiento de creación será 'void SUA_Thread_Gen:: Create_Register_Thread(QObject*)' y el tipo del hilo creado para manejar la sesión de registro será 'SUA_Thread_CReg'. En este caso se trata de un hilo para preguntar las capacidades en modo cliente. • Se pone a correr dicho hilo hijo SIP. La recepción de este evento provocará una llamada a la función miembro 'void SUA_Thread_Gen ::Create_Client_Options_Thread (SUA_Address.1. 3. El segundo subtipo de eventos de llamada que acepta la pila es 'W_Query_Options' que permite crear una sesión para pedir a un agente remoto información sobre sus capacidades y más datos adicionales (como por ejemplo información acerca de la versión y el tipo del software utilizado). Vale con la suministrada en tiempo de configuración. en la posición designada por su identificador de hilo. Tal vez merezca la pena comentar que el campo de información del evento de llamada puede ir perfectamente vacío ya que toda la información requerida se encontrará en la estructura 'Parameters' almacenada en este hilo. Este evento se mandará cuando deseemos registrarnos (ó actualizar nuestro registro) contra nuestro servidor SIP de acuerdo a lo establecido a la hora de configurar el agente de usuario. por lo que no nos detendremos demasiado en su estudio. • Adhesión de un puntero al hilo en la lista global de hilos activos. del tipo 'SUA_Thread_COpt'.2. Creación de hilos como servidor leyendo de la red Este segundo método para crear hilos se basa en la recepción de peticiones a través de la red. El último subtipo de eventos de llamada que acepta este hilo para generar sesiones en modo cliente es 'W_Register'. a nivel del hilo generador es un proceso muy parecido al de crear un hilo encargado de una llamada. Esta función creará físicamente el hilo encargado de preguntar las capacidades mediante los siguientes cinco pasos: • Creación de un nuevo Call-Leg para este diálogo.

REGISTER tampoco se acepta en modo servidor.que ningún hilo de la lista global de hilos activos responde por su Call-Leg. que se denominan de la misma forma que la repuesta que generará el agente en sentido contrario para notificar este mismo estado: • Estado 200: la petición recibida es totalmente correcta y estamos en disposición de recibir la llamada. Si la petición que recibimos es sintácticamente correcta y del tipo INVITE. Las multiconferencias son bastante más complejas). La cancelación de la transacción INVITE por este motivo implica que no hay forma de que los dos extremos entiendan su forma de codificar el audio. evidentemente lo descartaremos. ya que siempre será atendida independientemente del número de sesiones presentes en el agente. Si se trata efectivamente de una petición. la llamada podría realizarse usando. La función encargada de ello será 'void SUA_Thread_Gen:: Create_Server_Options_Thread(QString)' que funcionará de manera idéntica a todas las vistas anteriormente. si no lo es. no hacia servidores redirectores. a la que pasaremos la cadena de texto (petición en formato plano o 'raw') que hemos recibido. Implica que no hay otra llamada activa (por el momento sólo se concibe soportar llamadas 'uno a uno'. Para crearlo usaremos 'void SUA_Thread_Gen:: Create_Server_Thread(QString)'. a través del miembro 'int SUA_Thread_Gen:: Get_UA_State(QString)'. por lo que no nos extenderemos en explicaciones. Los demás métodos (BYE. ese códec. Si bien esta pila soporta múltiples sesiones SIP en paralelo. mediante la cual un extremo nos preguntará acerca de nuestras características. estudiaremos si dicho paquete es una petición. proxy ó gateways. • Estado 415 ('Unsupported Media Format'): se usa este estado para indicar que no se pueden soportar ninguno de los códecs de audio propuestos por el extremo SIP. para atender a otra sin necesidad de colgar. El otro caso de una petición recibida correctamente a través de la red es el caso de la petición OPTIONS. Para solucionar este tema en un futuro (posiblemente lejano) se debería dar soporte para multiconferencias y soporte para dejar una llamada temporalmente en suspenso. La primera actuación será determinar el estado de la pila en función de la petición recibida. supondrá la creación de un hilo hijo para manejar la sesión en modo servidor. porque no tienen sentido las respuestas fuera de un diálogo establecido. deberemos analizar su tipo. Implica que en el UA remoto aparezca una señalización de 'comunicando'. Simplemente 72 . se le mandará un evento del tipo 'SUA_String_Event' con la petición INVITE inicial. • Estado 486 ('Busy Here'): este estado supone también la cancelación de la petición INVITE recibida debido a que la pila ya está soportando otra sesión de llamada. lo cual nunca será permisible porque estamos creando una pila SIP enfocada hacia los agentes de usuario (principalmente funcionando como soft-phone). Para soportar dicha llamada se creará un hilo del tipo 'SUA_Thread_Server' en la misma forma ya vista para crear los hilos que respondían a eventos y después de que este hilo esté ejecutándose. o bien la máquina en la que estamos soporta varios agentes para distintos usuarios y la petición ha aterrizado en el UA equivocado (posiblemente debido a una mala elección del puerto). Con esta petición no existe el problema del estado. precisamente. ACK. Sólo los métodos INVITE y OPTIONS generan diálogos en este agente en modo servidor. porque los recursos de audio del sistema son limitados y porque una persona no puede estar físicamente hablando con otras dos a la vez. BYE y CANCEL) se considerarán siempre dentro de un diálogo para que tengan repercusión. Hay que dejar claro que si alguno de estos códecs fuese común a los dos UA. ya que supondría que algún agente está intentando registrarse con nosotros. sólo una de ellas podrá estar realizando una teleconferencia. • Estado 404 ('Not Found'): supone la cancelación de la transacción iniciada por el extremo remoto debido a que el campo de usuario de la cabecera To no se corresponde con el usuario por el que responde este agente ('Not Found'). O bien el extremo remoto ha errado a la hora de elegir nuestra dirección SIP. Existen los siguiente estados.

Gestión de hilos (eventos ‘SUA_ThMan_Event’) Los eventos del tipo 'SUA_ThMan_Event' son los encargados de realizar el trabajo de mantenimiento y gestión de todos los hilos que van siendo creados por el hilo generador de hilos. al crear un hilo worker se le pasa un puntero a la ventana que inicialmente será la encargada de recibir los eventos que genere. • TM_Exit_No_Conf: este evento es el contrario del anterior. para que éste active un flag (presente en todos los hilos de este agente) de forma que al terminar la ejecución. Se enviará al objeto especificado en el evento que activa la solicitud de confirmación. • TM_Exit_Confirmation: este evento es. Uno de los casos típicos de su uso es la notificación de los puntos de entrada para la señalización específica entre la pila y el front-end: cuando una ventana manda un evento para crear una sesión al hilo generador. • TM_Update_Th_Pointer: este tipo de eventos serán enviados desde los hilos worker SIP a la interfaz gráfica para informarle de quién está manejando su sesión. el hilo que le dará soporte todavía no habrá sido creado. El objeto que contiene la implementación del evento puede llegar a contener los siguientes miembros: • • • • Un código que indica su finalidad (lo vemos a continuación). es muy probable que sea destruida antes de la muerte del hilo. • TM_Set_Confirm_Flag: este evento lo lanza el hilo generador ó la interfaz gráfica hacia un hilo worker. por lo que desconocerá cómo acceder a él. la propia confirmación que envía un hilo hijo SIP si su flag de confirmación ha sido activado. la pila puede indicar a la ventana cuándo ha terminado para que sea esta misma la que se cierre automáticamente. Por otra parte.comentar que se creará un hilo del tipo 'SUA_Thread_SOpt': hilo hijo para soportar la petición de opciones en modo servidor. este evento viaja desde la GUI hacia la pila para enviar al hilo encargado de la sesión un puntero a la ventana que deberá 73 .5.1. liberar su identificador y destruir el objeto que encapsula el funcionamiento del hilo. Un puntero a un hilo. opcional. y de cómo acceder a dicho hilo a través de su puntero. pero dicha ventana no tiene porque tener la misma duración en el tiempo que el hilo. Tenemos los siguientes códigos: • TM_Kill_Sender: este evento es lanzado desde un hilo worker SIP que ha terminado su ejecución al hilo generador de eventos para que destruya todo rastro de dicho hilo. de carácter obligatorio. como ya hemos visto. Es una técnica muy útil para destruir ventanas que representan sesiones que ya no están activas: en lugar de posicionar un botón para que el usuario cierre la ventana. también opcional. limpia el flag que exige la confirmación de la finalización. Un puntero a una ventana. Un puntero a la ventana ó hilo emisor.3. envíe un evento de gestión del tipo 'TM_Exit_Confirmation' al objeto especificado para que se conozca de primera mano cuándo ha terminado la sesión. y necesitaremos algún mecanismo para indicarle al hilo el nuevo 'sitio' al que tiene que mandar la señalización. y antes de mandar el 'TM_Kill_Sender'. entre las tareas a realizar destacan la de eliminar la entrada que apunta al hilo en la lista global de hilos activos. Para realizar estas tareas y alguna más se crean dichos eventos. es más. Generalmente sólo es necesaria la emisión de un evento de este tipo cuando se crea el hilo que atiende a una determinada sesión. • TM_Update_Mw_Pointer: semejante al anterior. 3. El sentido de este tipo de eventos en cada momento viene dado por su código.

2. deberemos disponer de un método para dirigir hacia una concreta y en cada momento toda la señalización (concretamente se puede pensar en la cantidad de ventanas que intervienen en la creación de una llamada de audio). 3. los hilos de apoyo se centran única y exclusivamente en gestionar sesiones independientes. bien al proxy local. Hasta ahora no se han implementado más de estos hilos porque los nuevos métodos todavía están en estudio y porque con los ya soportados se cumplían las expectativas del proyecto.2. Funcionamos como cliente cuando somos nosotros los responsables del inicio de la sesión a través de una petición INVITE que mandaremos. y su relación con el front-end: 74 . 3. que mientras el hilo generador está mucho más relacionado con la arquitectura que este agente implementa para cumplir ciertos requerimientos. Por lo tanto. Con esto me gustaría resaltar el hecho de que para crear un buen hilo de apoyo únicamente hay que seguir de cerca el estándar SIP: no es necesaria ni la imaginación ni la creatividad. porque muchas veces ésta se podría parecer más a un repaso del estándar que al estudio de una implementación a alto nivel.recibir sus notificaciones. La figura que presentamos a continuación resume el proceso de inicialización de una llamada que realiza el hilo 'SUA_Thread_Client'. Queda claro.1. no nos centraremos en exceso en la descripción de los cinco tipos de hilos presentados. Por otra parte. el hilo generador de hilos se encarga de recibir eventos de la GUI ó paquetes de la red para comenzar la ejecución de hilos de apoyo que serán los que efectivamente se encarguen del manejo de las sesiones. Creación de sesiones como cliente (‘SUA_Thread_Client’) Los objetos de la clase 'SUA_Thread_Client' son los encargados de implementar el funcionamiento de un cliente SIP en la creación de una sesión. bien al proxy remoto ó incluso al propio agente de destino de forma directa. hay que mencionar que el desarrollo de estos hilos es independiente: con ciertas nociones de cómo funciona la pila en general (saber que cuando el hilo comienza es necesario enviar un puntero al hilo en sí a la GUI y que al acabar hay que notificárselo al hilo generador para que lo destruya) es suficiente para crear nuevos hilos que soporten diversos métodos. tanto en modo cliente como en modo servidor. Hilos SIP de apoyo Como hemos visto hasta ahora. También será el responsable de eliminar sus referencias y de destruir los objetos que utilicen una vez hayan finalizado en sus funciones. Pasemos pues al estudio de estos cinco hilos de servicio. Debido a que durante la vida de una sesión en la pila pueden aparecer múltiples ventanas en la GUI.

para poder ser accedido directamente. la primera tarea del hilo hijo consiste en notificar a la GUI un puntero a él mismo. La forma 75 . La segunda tarea es determinar la dirección de transporte de destino a la que tenemos que mandar la petición INVITE para iniciar la sesión. Si no hemos especificado ningún servidor SIP. Se hará enviando un evento 'SUA_ThMan_Event' del tipo 'TM_Update_Th_Pointer' al objeto (en este caso ventana) emisor del evento de llamada que desemboca en la creación de este hilo. y será éste el encargado de rutarla hacia su destino. la petición INVITE la enviaremos a nuestro proxy local. realizaremos una consulta DNS para averiguar a dónde debemos mandarla. En este punto entra en juego la configuración del agente: si hemos indicado que usaremos un servidor proxy con el que supuestamente ya nos hemos registrado en los primeros momentos de vida de este agente.Figura 21: Inicio de sesión como clientes (SUA_Thread_Client) Como ya se ha comentado justo en la pregunta anterior.

sonado) y 200 (Ok. hagamos una excepción y comentemos estos dos casos: si tenemos un servidor proxy configurado desde el inicio. llamada aceptada). Hablaremos de los casos más comunes y sobre todo. representativos. terminaremos la ejecución del hilo y esperaremos a que el hilo generador borre todo rastro de esta sesión (borrando la entrada en la lista de hilos activos y destruyendo el objeto que soporta el hilo). Generalmente tanto las cabeceras To como From son direcciones SIP asociadas a una localización física. Si existe alguna coincidencia entre los códecs de audio soportados por ambos extremos. Si los hemos mandado al proxy del extremo remoto. Las respuestas 100 y 180 no encierran ningún misterio. • Se almacena la respuesta para su posterior estudio. las recibiremos desde aquel. De cualquier modo. las recibiremos de ése. • Se comparan las cabeceras SDP de nuestro INVITE y de la respuesta 200. Pero supongamos que todo funciona correctamente. • Se completa el Call-Leg de esta sesión con el 'tag' del campo To de la respuesta. ni el hecho de que tal vez no llegue ninguna respuesta al INVITE. y por lo tanto necesitará de un mayor estudio. ejecutaremos la función 'void SUA_Thread_Client:: Resend_String_Timeout()' que genera un evento de control de llamada 'C_Invite_No_Response' y lo enviará hacia la interfaz gráfica para señalizarle que la llamada no se ha llegado a producir. Una vez conocida la dirección de transporte a la que mandar la petición INVITE comenzaremos su transmisión/retransmisión (hasta un máximo de siete veces ó hasta recibir una respuesta). con cada repuesta generaremos una cadena de texto que enviaremos a la ventana delegada para recibir los eventos de la pila. devolveremos a la interfaz gráfica un evento de control de llamada del tipo 'C_Refuse_Call' para indicar que no podemos realizar la llamada. Si no somos capaces de encontrar ninguna de éstas direcciones. Y si el/los INVITE han ido hasta el UA remoto directamente. y se lanzarán los hilos de audio y 76 . de forma directa al UA del otro usuario). En cualquier caso. Para más información se aconseja al lector examinar el propio código fuente. Sinceramente no creo que sea posible el pararnos a comentar todas las posibilidades. 180 ('Ringing'. La inclusión de un número suficientemente diverso como el 'tag' permite especificar qué agente ó qué hilo de un determinado agente está atendiendo esta sesión. A continuación el hilo finalizará su ejecución y el hilo generador de hilos lo destruirá. buscaremos la dirección IP que hay detrás del campo 'hostport' de la dirección SIP a la que estamos llamando (que al final acabará siendo ó la dirección IP del agente ó la de su servidor proxy).de realizar consultas DNS y cómo se da soporte para ellas es un tema que trataremos más adelante en esta misma memoria. Este tratamiento especial se le da en la función 'void SUA_Thread_Client::On_200_Response_To_Invite(SUA_Response)' en la que: • Se cancela la emisión de peticiones INVITE. con el fin de que se muestren en pantalla si el front-end así lo considerase oportuno. la búsqueda DNS se centrará en encontrar la dirección IP de dicho servidor. Si hemos mandado el/los INVITE a nuestro proxy local recibiremos las tres respuestas de éste. Si no lo tenemos. se creará una petición de tipo ACK que se mandará al lugar especificado en la cabecera de contacto contenida en la respuesta 200 (generalmente. y que recibimos tres respuestas en este orden: 100 ('Trying'. pero la respuesta 200 indica que el extremo SIP acepta la sesión. Si por el contrario el problema que se presenta consiste en que no recibimos respuesta a nuestra petición INVITE (que habremos retransmitido convenientemente por si tal vez se hubiese perdido). intentando). En la figura superior únicamente hemos considerado el modo de funcionamiento óptimo: no se tiene en cuenta el hecho de que las búsquedas DNS sean fallidas. la llamada prosperará: se mandará a la GUI un evento de control de llamada del tipo 'C_Running_Call' para indicar que la llamada continúa viento en popa.

lo curioso de este comportamiento es que constituye un ejemplo de que al funcionar como servidor. Queda demostrado de esta manera que el procedimiento de retransmisiones es particular para cada transacción. De esto último trataremos más adelante. Existen naturalmente dos posibilidades: que la llamada la liberemos nosotros (el front-end generará un evento y se lo mandará al hilo de apoyo) ó que sea el otro extremo el que decida liberar la llamada (recibiremos una petición BYE por la red). Si la petición ACK. no tendremos más que repetir la petición ACK hasta que llegue correctamente a su destino. Por otra parte. Pero. se perdiese. En tales circunstancias se creará un evento de control de llamada del tipo 'C_No_Media_Mach' y se finalizará el hilo. y no se puede generalizar diciendo que siempre y sólo se retransmiten las peticiones. y si todo ha ido correctamente tendremos una llamada de audio en curso. puede darse el caso de que los dos extremos no compartan ningún códec de audio. Sin embargo. Hasta el momento hemos creado una sesión como clientes. por lo que nunca podrá darse una sesión entre ambos. ¿cómo procederemos para finalizarla?. Analicemos el caso en el que somos nosotros los que colgamos la llamada con la ayuda de la siguiente figura: 77 .el hilo RTP para que se encarguen de manejar los flujos multimedia. las respuestas 200 deben ser retransmitidas y no las peticiones ACK. volveríamos a recibir una respuesta 200 idéntica a la que hemos analizado. que se envía para implementar una confirmación en tres pasos y que se emplea únicamente en conjunción con INVITE.

no a su proxy ni tampoco al nuestro. siempre mandaremos las peticiones BYE directamente al UA con el que tenemos establecida la sesión. la pila actuará de la siguiente manera: • Generará una petición de tipo BYE y comenzará su transmisión/retransmisión hacia la dirección de transporte indicada en la cabecera de contacto de la respuesta 200. en el hilo encargado de gestionar la llamada. el hilo abandonará directamente la ejecución y aguardará a ser eliminado. es decir.Figura 22: Fin de sesión como clientes Cuando en un determinado momento de la comunicación el usuario decida cortar la llamada. comprobará el estado de su flag de confirmación: si está activado. • Esperará la recepción de una confirmación a nuestro BYE en forma de respuesta de tipo 200. deberá notificar a la interfaz que el hilo está siendo 78 . A partir de ese momento. Si no llegase esta confirmación positiva. • Una vez llegada la confirmación positiva a la petición BYE. realizará las acciones pertinentes en la interfaz gráfica que desembocarán en la recepción. de un evento de control de llamada del tipo 'W_Hang_Call'.

La idea detrás de este sistema consiste en que la propia pila notifique a la GUI cuándo las sesiones finalizan. se detendrán los flujos RTP que se han estado mandando. El otro caso que se puede producir a la hora de cerrar la sesión que contiene la llamada es que sea el extremo remoto el que decida cortar. Como podemos comprobar en la siguiente figura. Si el flag se encuentra desactivado. • El hilo finalizará su ejecución no si antes mandar al hilo generador de hilos un mensaje de gestión del tipo 'TM_Kill_Sender'. sus recursos deben ser liberados y cualquier rastro suyo debe ser borrado (eliminación de su entrada en la lista global de hilos activos). indicándole que físicamente el emisor del evento es ya un hilo parado y que debe ser destruido. permanecerá en silencio. se trata de un caso realmente similar al anterior: 79 . principalmente para que vaya tomando acciones encaminadas a la liberación de recursos y actualización de la puesta en escena del software. mediante la destrucción del hilo RTP y de los dos hilos de audio. • Independientemente de emitir o no una confirmación. empezando él la transacción BYE.detenido mediante un evento de gestión del tipo 'TM_Exit_Confirmation'. al hablar del audio. En esto nos detendremos más adelante.

Este hecho tan trivial hace que la necesidad de confirmar la parada del hilo a través del evento de gestión 'TM_Exit_Confirmation' sea innecesaria. y el segundo carecería de contenido semántico: con el primero la GUI ya se da por enterada de que la ejecución del hilo de servicio finalizará pronto. ahora será la pila la que emita el evento 'C_Finish_Bye_200' en sentido pila-GUI. Sesiones como servidor (‘SUA_Thread_Server’) Cuando en esta pregunta nos referimos a sesiones como servidor. es posible que ya esté parado para cuando llegue.Figura 23: Fin de sesión como servidores Únicamente comentaremos la diferencia de que mientras que al cerrar nosotros la sesión. ya que ambos eventos llegarían en cualquier caso muy seguidos. No nos 80 .2. la interfaz gráfica debería emitir un evento de control de llamada del tipo 'W_Hang_Call'. y que si le mandásemos un nuevo evento al hilo. 3.2. queremos decir que vamos a estudiar cómo se comporta la pila frente a sesiones iniciadas por un usuario remoto.

tanto las que hemos implementado como las que no. aunque en ambas tareas también tendríamos que adoptar el papel de servidor. se puede considerar que es un poquito más complicada. pero a nivel SIP realiza el trabajo simétrico. y el leitmotiv de SIP es precisamente ése. porque se requiere la participación del usuario para su correcto funcionamiento: así. Sin embargo. La clase que se encarga de proporcionar a la pila el comportamiento adecuado para actuar como un servidor es 'SUA_Thread_Server'. de forma que creamos llamadas de audio. él ha introducido los datos y ha pulsado el botón correspondiente). cuando realizamos una llamada como cliente siempre confiamos en que el usuario quiere esa llamada (parece claro. El resto de sesiones que vayamos viendo. Nos permiten además asociar flujos multimedia a dichas sesiones. ese mismo usuario puede decidir aceptar ó no cierta sesión de determinado extremo SIP: es por lo tanto imprescindible presentarle una buena información sobre la llamada y esperar su decisión. porque. Vamos a comenzar el análisis del comportamiento de este tipo de hilos basándonos en la siguiente figura: 81 . En muchos aspectos se puede decir que es arquitectónicamente similar a la clase encargada de la gestión de sesiones cliente.estamos refiriendo al hecho de que nos pregunten por nuestras capacidades ó de que intenten registrarse contra nosotros. Además. El manejo de sesiones SIP tanto en modo cliente (como hemos visto en el punto anterior) como en modo servidor (como vamos a ver en éste) constituye el eje central de la pila. no dejan de ser acompañantes de estos dos tipos básicos. no conducen a la creación de sesiones SIP. ya que crean sesiones.

Las primeras operaciones conciernen al hilo generador de hilos. que como ya hemos visto. para comprobar que.Figura 24: Inicio de sesión como servidores (SUA_Thread_Server) El origen de una sesión como servidor hay que buscarla en un método INVITE enviado desde un determinado extremo SIP hasta nuestro agente. llevará a cabo las siguientes tareas: • Análisis sintáctico de la petición. se trata 82 . por una parte.

• Si no se produce una coincidencia en dicha comparación. Y si las cosas se tuercen. Pero es sencillo hacerlo de esta manera ya que si todo sale bien. aunque evidentemente se disponen de métodos para manejar situaciones excepcionales. la GUI deberá desplegar las ventanas que crea necesarias para hacer ver al usuario que tiene una llamada entrante. esto no es así. Además del evento de llamada mencionado el front-end deberá suministrar un puntero a la ventana que quiere que reciba los eventos de esta llamada en el futuro. 83 . mediante una consulta a la lista de hilos actuales. le manda al hilo encargado de manejar la sesión un evento de llamada del tipo 'W_Accept_Call' y a partir de este momento la GUI asumirá que la llamada está en curso. Para notificárselo a la pila. La pila tendrá en este momento vía libre para finalizar el inicio de sesión. No nos ocuparemos tanto de las comprobaciones como de las actuaciones globales: por lo tanto. volvemos a referirnos a los casos más directos. tendremos un método rápido y eficaz. tal y como vimos con el hilo anterior. como por ejemplo. En la figura superior hemos asumido que el usuario acepta la llamada (más abajo veremos qué pasa cuando la rechaza). Tal vez deba comenzar a reproducir algún tipo de sonido para alertarle. Se hace.efectivamente de una petición y por otra para comprobar que es una petición INVITE válida y bien formada. redireccionar la llamada hacia otro destino ó mantenerla en espera indefinidamente. • El hilo servidor esperará hasta que el hilo generador le mande un evento con la petición INVITE leída de la red. estrictamente hablando. enviaremos un evento a la GUI indicando que el otro extremo ha cancelado la llamada (cuando. confirmación positiva al INVITE) con una carga SDP en la que se relatarán las coincidencias entre los códecs (en nuestro caso. Se trata de comportamientos más complejos que se dejan para futuras versiones de la pila). Deberá transmitir/retransmitir (hasta un máximo de siete veces) una respuesta de tipo 200 (Ok. Deberemos asegurarnos que no existe ninguna otra llamada de audio en curso (actualmente la pila no soporta varias llamadas de voz de forma simultánea). pero el hilo servidor deberá analizar fundamentalmente el campo SDP (la carga ó payload de SIP) para asegurarse de que los códecs propuestos son manejables por nuestra pila. Cuando el nuevo hilo reciba el INVITE comenzará la verdadera actividad del hilo servidor. se tratará del inicio de una nueva sesión. El hecho es que la pila se quedará en un estado de espera activa hasta que recibamos respuesta desde el front-end. asumiremos que la carga SDP es admisible y mandaremos al usuario un evento de control de llamada del tipo 'S_Incoming_Call': de esta forma. y lo pondremos a correr. Generalmente la respuesta 200 no tiene problemas de aceptación por el extremo remoto. sólo de audio) entre los dos agentes de usuario. Para no perdernos en el funcionamiento. Después estudiará si ésta es aceptable: el hilo generador analiza que se trata de una petición correcta a nivel SIP. a través de un evento de gestión de hilos del tipo 'TM_Update_MW_Pointer'. pero evidentemente eso no nos atañe a nivel de pila SIP. Tenemos dos opciones: aceptar la llamada ó rechazarla (idealmente sería deseable disponer de otros comportamientos. tal vez no la haya cancelado sino que no se ha podido finalizar con éxito el inicio de llamada). porque todavía es necesario enviar una respuesta y esperar otra petición para confirmarla. En primer lugar notificará a la interfaz gráfica que es él que va a manejar la petición de sesión entrante. Crearemos el hilo servidor (el objeto 'SUA_Thread_Server'). Dicho esto. tal y como ya hemos visto. • Extracción de su Call-Leg y comparación con los Call-Leg de los diálogos existentes en dicho momento. aquí nos centraremos en cómo actúa el hilo en los casos más sencillo. Registrará en la lista global de hilos una nueva entrada para este hilo con su Call-Leg asociado. ya que supone la aceptación de su sesión y no se propondrán nuevos formatos multimedia.

Deberemos esperar hasta que el extremo remoto nos confirme nuestra respuesta 200 con una petición ACK, un método un tanto extraño, ya que sólo se usa relacionado con otro método INVITE previo. Nunca llevará carga SDP, porque la negociación de los formatos multimedia se realiza entre el INVITE y el 200. Se produce por lo tanto un inicio de sesión en tres pasos (INVITE/200/ACK) con una gestión de capacidades (como se denomina en el mundo H.323) en dos pasos (INVITE/200). Se lanzarán los hilos multimedia (audio y RTP) de acuerdo con la negociación SDP y la llamada se considerará oficialmente en curso (nótese que la llamada empieza realmente ahora, y no cuando le hemos dicho a la GUI que empieza). Sin embargo, existe otra posibilidad: que el usuario rechace la llamada cuando se le da opción a ello. Veamos un pequeño esquema de qué es lo que pasa en este caso:

Figura 25: Rechazo de llamada como servidores

Después de mandarle a la GUI el evento de control de llamada 'S_Incoming_Call', el usuario responderá con un 'W_Reject_Call', con el que ordena a la pila que no siga con la negociación para el inicio de la sesión. El front-end toma el camino más corto, como sucede también en el caso de 84

que se acepte la llamada, y considera que esa sesión ya no existe y se olvida de ella. Realmente la sesión sigue estando ahí, a medio inicializar, sostenida por un hilo asociado y ocupando unos recursos globales en el hilo generador de hilos. Por lo tanto, deberemos limpiar esta sesión fallida a espaldas de la GUI. Lo haremos respondiendo con un 486 ('Busy Here') para indicar al otro agente SIP que ahora mismo no es posible aceptar la llamada (en lugar de mandar un 200 para aceptarla). Una respuesta 486 carece de campo SDP y deberemos esperar hasta que sea confirmada con un ACK. En ese momento la sesión está finalizada a nivel SIP; queda limpiar los recursos saliendo del hilo y enviando al hilo generador un evento de gestión de hilos del tipo 'TM_Kill_Sender' para que se borre físicamente el objeto que mantiene la sesión y se borre su entrada en la lista global de hilos activos. Básicamente, de esta forma hemos explicado cómo se maneja una sesión entrante en el caso de que todo marche de forma correcta: pero se pueden producir numerosos fallos, y en la capacidad para manejarlos residirá la potencia de nuestra pila. Todos los fallos deberán ser detectados antes de enviar a la GUI el evento de control de llamada 'S_Incoming_Call': no podremos dar a elegir al usuario entre aceptar la llamada o no si el método INVITE recibido contiene errores ó los formatos multimedia propuestos no están soportados. En esos casos, se responderá directamente con una respuesta de error (404 si el usuario destinatario no se encuentra en esta máquina, 415 si los flujos multimedia son desconocidos,... etc.), se esperará la confirmación por parte del otro extremo con un ACK y se liberarán los recursos: una forma de operación muy parecida al caso de rechazar la llamada, salvo que la decisión de no proseguir con la sesión no la toma el usuario sino la pila, debido a un error en la operación. Para terminar con el manejo de sesiones en modo servidor debemos comentar cómo se cierran dichas sesiones cuando somos el servidor. La respuesta es muy sencilla y fácil: de manera idéntica a cuando somos clientes, tanto si la sesión la cerramos nosotros como si es el otro extremo quien la cierra. La razón es sencilla: el concepto de cliente/servidor sólo se aplica a la iniciación de la llamada, ya que una vez establecida ambos extremos son idempotentes y operan de la misma forma. Nos ahorramos por lo tanto volver a comentar los mismos procedimientos y referimos al lector al final de la pregunta anterior donde se explicaban con todo lujo de detalles los pasos a dar.

3.2.3. Registro con un servidor (‘SUA_Thread_CReg’) El papel del registro en SIP es fundamental. Ya hemos hablado en varias ocasiones de él, pero debemos entender que la pareja formada por los servidores SIP (proxy ó redirectores) y el registro para configurar el 'location service' de dichos servidores proporciona el concepto de direccionamiento en SIP, uno de sus puntos más fuertes. Gracias a este sistema, los usuarios pueden ser localizados mediante direcciones SIP globales, se pueden configurar varias de éstas direcciones para cada agente y se proporciona a SIP de un carácter global. Además, actualmente existen varios grupos del IETF (organismo encargado de publicar SIP) trabajando conjuntamente para añadir a las capacidades de localización de SIP, posibilidades para detectar de una manera eficiente la presencia, por lo que tal vez en un futuro no muy lejano, SIP se haya convertido en un sistema completo de mensajería instantánea, además de ser ya, de por si mismo, un excelente sistema de gestión de sesiones (localización + presencia = pilares de la mensajería instantánea). De cualquier modo, asumimos que se tienen claros los conceptos de dirección SIP pública, dirección SIP actual, 'location service',... etc. Y si no es así, remitimos al lector al pequeño tutorial de SIP adjunto (Anexo del documento nº1 MEMORIA). Dicho esto, pasemos a estudiar cómo realiza nuestra pila SIP este trabajo. Como de costumbre, nos basaremos en un pequeño resumen en forma de figura:

85

Figura 26: Resumen del proceso de registro

El proceso de registro comienza cuando desde la interfaz gráfica se envía un evento de llamada del tipo 'W_Register' al hilo generador de hilos en la pila. Recordando un poco lo visto acerca de la GUI, este proceso se realizará siempre al iniciarse el agente y cuando el usuario lo requiera mediante el menú de preferencias. Pero claro, a nivel de pila esta información no es relevante. 86

Con la dirección IP y el puerto que obtengamos (por imperativos del diseño. Pero como ya hemos repetido. Servirá para que la GUI mande eventos al hilo si fuera necesario. Si dicha petición REGISTER es ahora aceptada y el servidor SIP decide modificar su 'location service'. Guardaremos esa lista. y generaremos un evento de control de llamada del tipo "C_Register_Sucessfull" para indicarle al front-end que el proceso de registro ha terminado de forma exitosa. sin autenticación. Siempre puede darse el caso de que una de las dos tareas anteriores fallase. sino también a métodos INVITE. y desembocarán en el reenvío de la petición REGISTER anterior más una cabecera de autenticación. Pero para saber más acerca de este tema se remite al lector al código fuente del hilo. El servidor nos responderá con una respuesta que puede ser de uno de los dos siguientes tipos: • 401 ('Unauthorized'): implica que para poder acceder ó modificar la base de datos del 'location service' el usuario debe autenticarse contra el servidor proxy. un puntero al propio hilo emisor del evento. no nos vamos a centrar en el control de los errores sino en intentar explicar el funcionamiento global del hilo. • 407 ('Proxy Authentication Required'): se puede considerar como una extensión de la anterior. Requiere insertar una cabecera de autenticación 'Proxy-Authorization'. Dependiendo de cómo haya lanzado dicho front-end el hilo. sin ningún tipo de intervención del usuario. la dirección SIP pública que queremos dar al agente y el par nombre de usuario y password para el proceso de autenticación. pero cabe comentar que se emplea siempre el mismo procedimiento en SIP. se usará la salida confirmada. se mandaría a la interfaz un evento de llamada "C_Register_Unsucessfull" y se acabaría el hilo. Una vez realizadas las dos tareas básicas anteriores se procede a una búsqueda DNS para localizar a nuestro servidor SIP. en este caso. por ejemplo. si queremos usar dicho servidor como proxy ó como redirector. Si fallase. encargado de manejar la sesión de registro. los pasos a seguir en ambos casos son muy parecidos. debido a que la recepción del evento de gestión de hilos "TM_Exit_Confirmation" provoca el cierre de dicha ventana y el arranque del agente en sí.El hecho es que el hilo generador creará un objeto de tipo 'SUA_Thread_CReg'. podremos tener o no levantado el flag de salida confirmada: • Si el proceso de registro se lanza desde la ventana de inicio. por lo que se generaliza el uso de la autenticación para el acceso a los servidores. y lo dará de alta en la lista de hilos. será siempre un puerto UDP) realizaremos el primer paso en el registro: el desafío al servidor ('challenge') mediante una petición REGISTER simple. como la dirección SIP del servidor. Trataremos las búsquedas más adelante. • Notificación a la GUI de un puntero al hilo que se encarga de gestionar el proceso de registro. En general. 87 . porque implica que todo aquel método que pase el proxy deberá ir autenticado: no se limita a métodos REGISTER. Las operaciones que realiza este hilo nada más arrancar son las típicas que ya hemos visto: • Comprobación de la viabilidad del registro: fundamentalmente se trata de ver que efectivamente se dispone de todos los parámetros necesarios para realizar el registro. por lo que utilizaremos los mismos objetos que ya usáramos en el hilo de sesiones cliente. Requiere insertar una cabecera de autenticación 'Authorization'. se nos devolverá una respuesta afirmativa (200. Ok) con una lista de las cabeceras de contacto que figuran en nuestra entrada de la base de datos del servidor.

Se trata por tanto de un método para conocer un poco mejor con quién estamos tratando. Básicamente se trata de enviar una petición de tipo OPTIONS y esperar la respuesta que contendrá una lista de los métodos soportados.2. por lo que no tiene sentido la salida confirmada. borre nuestro objeto y limpie nuestra entrada en la lista de hilos.• Si el proceso de registro es lanzado desde la ventana de preferencias no se necesitará el cierre automático de la ventana que muestra todos los mensajes de información (recibidos a través de eventos de texto mandados desde el hilo a la GUI). nunca recibiéramos una respuesta del tipo 200 (Ok) y continuásemos recibiendo respuestas 401 ó 409 significaría que el servidor no nos está autenticando: nuestra pareja nombre de usuario y password será errónea. Con el evento de llamada "C_Register_Sucessfull" será a todas luces suficiente. Si somos preguntados. En cualquiera de los dos casos anteriores (acabando tanto con éxito como sin él) la forma de acabar el hilo es igual a la expresada para los dos hilos anteriormente vistos: mandar un evento de gestión de hilos "TM_Kill_Sender" al hilo generador para que pare nuestro hilo.4. un 'payload' SDP con los flujos multimedia que implementa (si es que implementa alguno) y diversas informaciones adicionales. el evento de llamada que devolveremos a la GUI será "C_Register_Unsucessfull" para indicar que no nos hemos podido registrar con éxito. Además de confirmar ó no la salida como hemos visto antes. seremos los servidores (ver la siguiente pregunta). Pregunta de capacidades como cliente (‘SUA_Thread_COpt’) Ya se ha explicado en esta memoria en qué consiste el preguntar a un 'ente' SIP (agente de usuario ó servidor) por sus capacidades. Comencemos con la figura resumen: 88 . 3. y una forma de informarnos de posibles objetivos para nuestras sesiones. En ambos casos se trata de métodos más sencillos de implementar que los vistos hasta ahora. Si por el contrario. lo haremos en modo cliente. Si preguntamos acerca de las capacidades de otro extremo SIP.

Se trata de una opción muy interesante para ser realizada previamente a un inicio de sesión enfocado hacia soportar una llamada de audio. podremos conocer qué flujos multimedia es capaz de recibir. Consiste en transmitir/retransmitir (hasta en un máximo de siete ocasiones ó hasta recibir respuesta) una petición de tipo OPTIONS al extremo SIP de destino. También puede estar presente una cabecera de tipo "Server" con información acerca del software que implementa el proceso. Además. si la respuesta cuenta con 'payload' y éste es SDP. En la ó las cabeceras "Allow" de dicha respuesta se relatarán los métodos que el agente ó servidor receptor soporta. la GUI le ordena a la pila que lance el proceso de petición de capacidades. Se trata de un proceso muy sencillo. • 406 ("Not Acceptable"): se produce en los casos en los que. por supuesto. al preguntar por las 89 . y supondrá una transacción exitosa. basado únicamente en una transacción. al hilo generador. La respuesta a esa petición puede llegar a ser de varios tipos: • 200 (OK): es el caso más típico.Figura 27: Pregunta de capacidades como cliente Mediante un evento de llamada 'W_Query_Options' que llegará.

aunque tengamos muy claro qué es lo que deseamos que haga nuestro párser. podemos decir que el párser de la pila nos permite realizar tres funciones básicas: • • • Comprobación de la corrección de los mensajes SIP Análisis y extracción de la información que contienen Generación de mensajes SIP correctos Sin embargo. Provocará la salida inmediata del hilo y su destrucción. Deberá comprobar la corrección de los mensajes. Cuando se reciba una petición de tipo OPTIONS. Provocará la terminación del hilo. sino únicamente de SIP) enviemos un método OPTIONS con una carga SDP. Si la petición es correcta se devolverá una respuesta 200 con la información de nuestro agente. tanto en recepción como en emisión. las 90 . Párser SIP/SDP El párser de la pila SIP ampliada es el elemento que se encarga de analizar la información recibida por la red y de generar los mensajes que vamos a mandar.2. los resultados se devolverán a la GUI en forma de cadenas de texto en eventos del tipo 'SUA_String_Event'. el hilo generador creará un hilo 'SUA_Thread_SOpt' que la atenderá: conceptualmente no es más que manejar una transacción. pero es interesante comentarlo aquí. y lo hace de forma silenciosa. El hilo se acabará y el hilo generador se encargará de destruirlo y limpiar los recursos que ocupa. hemos de mencionar un detalle que no se representa en la figura superior: el hilo puede ser cancelado desde la interfaz gráfica mediante el envío de un evento de control de llamada del tipo 'W_Cancel_Options'.5.3. Se recibe dicha respuesta cuando el extremo no soporta la petición de capacidades. En cualquier caso. ya que no se relaciona con la interfaz gráfica. existen infinidad de respuestas posibles. 3. donde sí aparecerá quién ha pedido información acerca de nuestras capacidades y cómo se le ha respondido. La forma en la que queda constancia de esta transacción es mediante el análisis de la ventana de depuración. 3.capacidades a un servidor que no tiene soporte para SDP (porque un servidor nunca realizará llamadas y no entiende de formatos multimedia. perdiéndose cualquier paquete relacionado con este hilo que pueda llegar más tarde. Pregunta de capacidades como servidor (‘SUA_Thread_SOpt’) Se trata sin duda del hilo de apoyo más sencillo. Antes de finalizar con la petición de opciones en modo cliente. La interfaz gráfica no deberá notificar de forma explícita al usuario la recepción de un método OPTIONS. Cuando la transacción finalice (tanto de forma exitosa como si no). En general. deberá ser capaz de extraer cabeceras de las peticiones y de las respuestas y ser capaz también de analizar adecuadamente dichas cabeceras para leer sus campos. Cumple el papel de responder a las peticiones de características del resto de agentes SIP. Si hay algún fallo se devolverá la respuesta correspondiente y se saldrá en silencio. • 501 ("Not Implemented"): es un caso menos habitual. por lo que no será necesario el paso de eventos de control de llamada.

con el tiempo que ello acarrea. Básicamente. y por otra el uso de herramientas diseñadas específicamente para crear pársers (bison. Párser SIP/SDP a medida (custom) En la pila del agente hemos decidido implementar un párser hecho a mano de forma específica para este proyecto. así como la comprobación de su rendimiento y su robustez en la operación. pero hacerlo usando una herramienta para generar pársers implica primero el estudio previo del manejo de dicha herramienta y segundo. el análisis del código del párser generado. existen dos grandes tendencias: por una parte. por evidentes limitaciones de tiempo: escribir un párser a medida es una tarea lenta y propensa a errores. Así mismo. • Se necesitaba un desarrollo rápido del párser. mediante el uso de un párser SIP/SDP basado en la siguiente arquitectura: Figura 28: Diseño del párser SIP/SDP Vamos a analizar de forma independiente cada una de las funciones suministradas: 91 . Las funcionalidades que hemos descrito antes las vamos a proporcionar en nuestro caso. y su elección bastante complicada. trataremos sobre esto al comentar los otros modelos de pársers.formas de implementarlo pueden llegar a ser muy diversas. basada en un párser a medida ó custom. 3.3.1. no existe ninguna herramienta mágica para escribir pársers. De cualquier modo. y que por lo tanto no necesita de todas las funcionalidades. la escritura manual del párser. por lo que se imponía un proceso previo de selección. Vamos a comenzar el estudio con la implementación que hemos desarrollado. que no implementará alguno de los métodos propuestos. ya que hemos valorado dos hechos muy concretos relativos a este agente: • No se buscaba implementar el soporte para todas las cabeceras SIP: tratamos con un agente de usuario de nivel básico/intermedio. flex y ANTLr).

presente en determinadas cabeceras: su funcionamiento se encapsula en la clase ‘SUA_Address’.3. From y Call-ID. • Corrección Léxica: si bien la sintaxis se refiere a la forma en las cabeceras se expresan. Dichas funciones son heredadas respectivamente de las clases ‘SUA_Body’. manejo de tags (en las cabeceras To y From) y extracción de información puntual. y de las que un sistema compatible no se puede salir. Existen sin embargo dos casos especiales de tokens (en las definiciones ABNF. Comprobación de la corrección de los mensajes Básicamente.3. pero la corrección léxica se encarga de asegurar que los dos campos son lo que tienen que ser: direcciones. 3. El resto de tokens servirán de elemento constructor de otros tokens y estarán a su vez formados por tokens más pequeños) que se han tratado a parte: • Campo SIP-URI (o más comúnmente.. Generalmente dispondremos de funciones para crear peticiones a partir de datos discretos pedidos al hilo que requiere la petición.3. la comprobación se realiza a dos niveles: • Corrección Sintáctica: se refiere a que el mensaje SIP con un posible payload SDP se ajusta a la sintaxis SIP expresada a través de su definición ABNF. Los tokens finales son los que no sirven de token para otras agrupaciones de orden superior. todas las agrupaciones son tokens.. de lo que se encarga la sintaxis. que están específicamente normalizadas.’. nombres de dominio. comparación. nuestro párser deberá ser capaz de determinar si la estructura de construcción de una cabecera es correcta ó no. Generación de mensajes SIP La generación de las peticiones y de las respuestas se hace también a través de las clases ‘SUA_Request’ y ‘SUA_Response’.1. Por lo tanto.3.1. para las cabeceras SIP y ‘SUA_Sdp’ para las cabeceras SDP. 3. etc. sobre la base de clases C++). la corrección léxica se refiere al contenido de cada campo de la cabecera. . Tabla 10: Cabeceras SIP soportadas por el párser Tipo Cabecera Soporte 92 . Extracción de información de las cabeceras SIP/SDP Mediante el uso de las clases de petición y respuesta (‘SUA_Request’ y ‘SUA_Response’) accederemos a un montón de funciones que nos proporcionan todas las cabeceras SIP disponibles (ver tabla 10) y SDP (ver tabla 11). Cada cabecera podrá construirse de una determinada serie de formas.3.2. Mediante la clase ‘Call_Leg’ se proporcionan funciones para la asignación. unidad léxica formada por las cabeceras To. y para crear respuestas basándose en peticiones recibidas anteriormente. dirección SIP). • Call-Leg. se presentan estas dos tablas en las que se indica qué cabecera está soportada en cada caso para SIP y para SDP. Como resumen del estado del desarrollo del párser SIP/SDP que se ha implementado a medida (de forma manual.1. en el caso de SIP son las cabeceras.1. Una cosa es que se compruebe que la separación entre dos campos deba ser un espacio y un carácter ‘.

general-header (14) entity-header (3) request-header (13) response-header (7) TOTAL Accept Accept-Encodign Accept-Language Call-ID Contact Cseq Date Encryption Expires From Record-Route Timestamp To Via Content-Encoding Content-Length Content-Type Authorization Contact Hide Max-Forwards Organization Priority Proxy-Authorization Proxy-Require Route Require Response-Key Subject User-Agent Allow Proxy-Authenticate Retry-After Server Unsupported Warning WWW-Authenticate 37 Cabeceras SIP Sí No No Sí Sí Sí Sí No No Sí No No Sí Sí No Sí Sí Sí Sí No No Sí No Sí Sí No No No Sí Sí Sí Sí No Sí No No Sí 56.75% soportadas Tabla 11: Cabeceras SDP soportadas por el párser Tipo Cabeceras SDP (20) Cabecera v= version o= ownwer s= session name i=*session info 1 u=*uri descriptor e=*email address p=*phone number c=*connection info b=*bandwith info t= time descriptor r=*repeat time z=*time zone k=*encryption key a=*attribute Soporte Sí Sí Sí No No No No Sí No Sí No No No No 93 .

carente de un vínculo común). Mientras que para estos casos ha demostrado ser un sistema muy eficiente. SIP tiene grandes esperanzas depositadas en el campo de la mensajería instantánea. el soporte para que el código producido sea C++ y no JAVA se encuentra todavía en desarrollo. Concretamente tenemos: • Párser basado en bison/flex: estas dos herramientas se han usado mucho para la creación de compiladores ó de intérpretes de scripts.2.TOTAL m= media descriptor i=*media title c=*connection info b=*bandwith info k=*encryption key a=*attribute 20 Cabeceras SDP Sí No No No No No 30% soportadas 3. por lo que tal vez haya que esperar para usar ANTLr. Aunque no se hayan usado en este proyecto hasta ahora.3. Si bien parece la solución más plausible cuando se abandone el método manual de creación del párser. bison/flex requieren una sintaxis BNF autocontenida (la definición de todos los tokens se basa en última instancia en elementos atómicos. basándose en ABNF) produce una serie de clases JAVA capaces de analizar cualquier mensaje SIP. mantenimiento y liberación de sesiones. SIP no cumple esta última característica. enfocados hacia lenguajes que se pudiesen definir mediante BNF (C. como caracteres ó dígitos. para lo que usa sus capacidades de localización/presencia. que mediante la creación de una definición intermedia (que es la parte que toca al programador. precisamente eso.4. no ha sido lo mismo con los protocolos de red (el modo de funcionamiento es un poco diferente). concretamente en el estándar de HTTP. concretamente. pero en los círculos de desarrollo suena con fuerza SOAP para proporcionar servicios Web orientados a sesión (SOAP sobre HTTP es incapaz de mantener un estado acerca de las peticiones: una sucesión de llamadas SOAP es. • 3. Herramientas para la generación de pársers Existen dos tipos de herramientas que nos permiten realizar pársers para una sintaxis ABNF (la forma en la que SIP se define). al establecimiento. la señalización de flujos de audio y/o vídeo. SIP sirve para muchas cosas (o servirá cuando consiga un mayor nivel de implantación) y da la casualidad de que una de ellas es. lo que se pretende dejar claro es que la asociación existente entre el protocolo SIP y el uso que se le da para controlar flujos multimedia es arbitraria. y no en otros tokens pertenecientes a sintaxis BNF externas). ya que algunos de sus tokens se definen fuera del estándar SIP. Párser usando ANTLr: se trata de una herramienta más nueva. como ya hemos mencionado. Se ha estado viendo el caso concreto de SDP para describir sesiones multimedia. Perl y muchos otros). una sucesión inconexa. Hasta ahora hemos estudiado la pila que implementa SIP y nos hemos dado cuenta de su carácter general: los mensajes SIP pueden transportar como carga cualquier protocolo. Por lo tanto. Esto no quiere nunca decir que haya 94 . se han estudiado para ver la posibilidad de su inclusión en un futuro. Arquitectura multimedia El proyecto que estamos tratando versa sobre SIP y SIP es un protocolo que básicamente se dedica. Además. Por otra parte.

sino leerlo desde un fichero *. no para modificar comportamientos). No debemos forzar al usuario de la pila a usar los drivers OSS. La conjunción de todas estas necesidades conforman un desarrollo muy completo en el que además de requerir una implementación de SIP exhausta y compleja. ya que el modo en el que el UA opera con el audio es muy predecible: se tratará de conseguir emitir un flujo RTP y ser capaz de recibir otro usando únicamente cinco parámetros que nos solucionan todos los posibles problemas de configuración. Además de recibir y emitir los flujos RTP deberemos ser capaces de reproducir y grabar su contenido a través de una tarjeta de sonido. Dicho esto y recalcada la generalidad de SIP. A saber: direcciones IP y puertos UDP origen y destino y códec de audio a usar para codificar/decodificar. Para pasar a temas más concretos. Nada más lejos de la realidad. lo que se pretende es decir que SIP realiza muy bien el trabajo que requiere y exige el concepto general de voz sobre redes IP. deberíamos permitirle elegir usarlos (y todo será mucho más sencillo y rápido) o no. sería muy aconsejable permitir a la GUI suministrar las muestras de audio al sistema de sonido. no será necesario el concepto de 'señalización específica'. pero todo el sistema de audio es un compartimento estanco. el sistema de sonido no se comunica con nadie: dispondrá de un método global para ser arrancado (cuando la llamada esté establecida y debamos oír y ser oídos) y de otro para ser parado. No nos vamos a encontrar con el concepto de evento (sólo con los de depuración. De hecho. A la hora de diseñar este soft-phone.una ligadura entre SIP y lo que se ha venido llamando VoIP. ni vamos a ver los complejos gráficos anteriores con cientos de flechas representando qué eventos va a generar la GUI y cómo la pila los confirmará ó devolverá información. Pero solamente eso. pero su dificultad no es ni remotamente comparable a la del diseño de la arquitectura de la pila SIP. En un agente de usuario que realiza llamadas de audio se emplean gran cantidad de las posibilidades que SIP ofrece. cuyo funcionamiento es conceptualmente transparente ya que sólo se usan para 'imprimir' información.mp3 ó *. ya que debe ser un sistema muy general y casi independiente del mismo. También es cierto que para el futuro se está ya considerando un modelo menos estricto: en nuestro afán por dar generalidad a la implementación. Sin embargo.ogg. además de requerir la implementación de otros protocolos. Supongamos también otro caso en el que un desarrollador amante del análisis de señal decide usar nuestra pila para transmitir audio previamente filtrado ó analizado. que tiene poca relación con el exterior. comencemos a plantear los distintos puntos en los que hemos diseccionado el estudio del sistema de audio. y delegar la responsabilidad en el suministro de las muestras al frontend. cuando la llamada finalice. Supongamos el caso de que queremos reproducir una grabación: no buscamos grabar audio desde el micrófono. Dado que el sistema de audio no se relaciona con la GUI. Como guía para la explicación se presenta la siguiente 95 . como SDP ó RTP y el acceso a recursos a bajo nivel del sistema como son los drivers del audio. hay que decir que programar un soft-phone es una de las formas más bonitas de llegar a estudiar SIP con profundidad. Tal vez conseguir una calidad de audio excelente haya podido ser costoso: los drivers OSS que usamos para acceder a los recursos de sonido del sistema no permiten la lectura y la escritura de forma simultánea. exige un buen diseño para que el código generado sea lo suficientemente manejable como para llegar a cubrir todos objetivos. A simple vista puede parecer una tarea compleja. Y esta premisa simplifica considerablemente el diseño. este concepto está más cerca de una ensoñación deseable que de un código real listo para ser distribuido. la arquitectura de todo el conjunto de sistemas relacionados con el audio (que hemos decidido llamar de forma genérica "arquitectura multimedia") juega un papel importantísimo. diseñar un mecanismo para que la pila RTP se reenganche a un número de secuencia después de perder algunos paquetes y evitando que el remedio sea peor que la propia pérdida puede llevar cierto esfuerzo. Este precepto no es excesivamente difícil de conseguir.

4.1. Estos diseños 'monolíticos' consistirían en un único hilo encargado de realizar estas tres tareas de forma secuencial. con esfuerzo. no es una tarea imposible. cuando se trabaja con el sistema de audio para depurarlo. aunque más complejos. cuyo acceso se encuentra implementado a través de un objeto del tipo 'SUA_Buffer'. pero sin necesidad de grandes alardes. La otra razón para oponerse al uso de búfferes es una teórica disminución en el rendimiento del sistema: tratar de hacerlo para el audio puede ser factible. La existencia de estos búfferes de audio constituye la parte fundamental de nuestra arquitectura de audio.. mejorarlo.. y nunca mejor dicho. Una de ellas es la complejidad que este tipo de diseños supondría frente a otro más sencillo basado en el modelo leer_de_red->decodificar->reproducir y grabar->codificar->mandar.. Existen a mi modo de ver. uno se da cuenta de que generalmente. pero soñar con un modelo similar para manejar vídeo sería una pérdida de tiempo. Búfferes de audio Cuando hablamos de búfferes de audio nos estamos refiriendo a unas memorias compartidas circulares. Conseguiremos con este diseño un sistema más equilibrado y más independiente de la red.figura. Frente a esta opinión habría que decir que si bien es cierto que un sistema con búfferes intermedios es más complejo y que la complejidad conduce siempre a errores. que éstas sí. son fáciles de resolver. dos únicas razones para pensar que el uso de búfferes intermedios en el sistema de audio no sería recomendable. consiguiendo una calidad en el sonido no necesariamente mala. los diseños modulares son siempre más elegantes. Para rebatir este argumento diremos que. Podríamos haber diseñado perfectamente un sistema de audio carente de dichos búfferes. primero en sentido de red a agente y luego de forma inversa. aunque no se han realizado 96 . que resume la arquitectura general del sistema multimedia: Figura 29: Arquitectura general del sistema multimedia 3. testarlo. sin embargo. que se sitúan entre los hilos encargados de la lectura/escritura del audio del driver y el hilo RTP encargado de enviar/recibir paquetes por la red. ya que simplifican el problema dividiéndolo en pequeñas partes independientes.

con el vídeo. Debemos añadir algo más. de nuestro sistema de audio. Es muchísimo mejor realizar pocos accesos y manejar más datos. ya que serán implementados por hilos diferentes que no tendrán ninguna relación en común. el tema del acople de las velocidades puede llegar a suponer grandes dolores de cabeza por dos circunstancias: por una parte. El problema de rendimiento aumentaría. evidentemente. nunca tendrá acceso de ninguna manera a las muestras de sonido. este sistema realiza la misma tarea que los amortiguadores de un coche: previene a los ocupantes de tener que soportar los 'baches' que de vez en cuando se encuentran en la carretera. tenemos un gran número de beneficios en el uso de estas memorias intermedias. una lectura no destructiva del buffer.pruebas de rendimiento. por tanto. • Mejora en las posibilidades de acceso: si concebimos un sistema de sonido compuesto por un único hilo que va accediendo al driver. El buffer intermedio actúa como un colchón frente a estas variaciones en la red y aunque los drivers también cuentan con pequeños búfferes. Ambos hilos no se ocupan el uno del otro. Realmente uno se da cuenta de la maravilla que esto supone cuando se pone a depurar los hilos consiguiendo velocidad y máxima calidad en el sonido. a continuación. pero ese es un tema sobre el que no hemos tenido tiempo de trabajar. ni recursos del sistema. Las ventajas de un hecho semejante son evidentemente que los problemas en un hilo no afectan al otro. • Acople de velocidades: decir que un diseño modular es mejor y que separar problemas grandes en otros más pequeños contribuye a una mejor solución es muy bonito pero no justifica una actuación por sí sola de forma razonable. nosotros en nuestra memoria podremos usar búfferes tan largos como deseemos. Aunque digan que las comparaciones son odiosas. codificando/decodificando y accediendo a la red de forma cíclica y secuencial. el manejo de audio hoy en día es algo totalmente superado y que muy mal código se tendría que escribir para notar bajo rendimiento al tratarlo. En un diseño basado en un sólo hilo que realiza secuencialmente una lista de tareas. Se consigue una división quirúrgica de un problema importante en dos más sencillos y otro añadido: el propio diseño de los búfferes. el usuario de nuestra pila SIP y por ende. llegarnos todos de golpe. Vamos a comentarlos: • Conseguimos independencia entre el acceso al driver y la pila RTP : conseguimos que ambos conceptos sean independientes lógica y físicamente. Cada hilo deberá conocer cómo se accede a los datos de los búfferes y nada más. Y ese algo es el acople de velocidades entre la lectura/escritura del audio desde el driver y la emisión/recepción de paquetes RTP desde la red. de un sistema rápido de acceso. Por otra parte. Usando un buffer intermedio se pueden implementar ciertos sistemas. De cualquier modo hay que recordar que trabajamos con hilos. Se trata. el driver de audio necesita que se le suministre las muestras de audio en una cantidad relativamente abundante (para bloquear lo menos posible el driver y permitir una reproducción sin cortes. No sabrá si esos datos que él escribe serán leídos y realmente no le importa. Frente a estos dos pequeños inconvenientes. Esto 97 . Se iniciarán y pararán por separado. que muchos pequeños accesos para pequeñas cosas) y de forma religiosamente constante. como veremos más adelante. para permitir por ejemplo. y ninguno de los dos (en realidad son tres) tendrá constancia de la presencia del otro. la red es aleatoria y nunca deberemos fiarnos de ella. y que al no estar relacionados no se crearán conflictos entre ambos. y que las memorias compartidas de las que hablamos es espacio de direccionamiento común en memoria: no es un fichero compartido. ya que son comunes los momentos de silencio en los que no nos llegará ningún dato para.

si así lo creyésemos oportuno en cierto momento. Si algún día decidimos dar el paso. independientemente de que los drivers ALSA requieran más cantidad de audio ó menos para cada acceso al hardware.se puede hacer con algo tan sencillo como un puntero de lectura auxiliar. Nosotros sólo hemos implementado como muestra las barras de nivel de audio. De la misma forma. que ya hemos comentado al hablar de la interfaz gráfica. Un ejemplo de porqué querríamos hacer esto es para usar drivers ALSA en lugar de OSS. las posibilidades son mucho más interesantes disponiendo de un sistema de búfferes intermedios. tratando ó incluso almacenando ó representando en pantalla. le dará igual. Eso. • Mayor facilidad para realizar mejoras: al dividir el problema y acordar una interfaz común (el acceso a los búfferes) es sencillo eliminar una parte y sustituirla por otra. y que atestiguan que desde la GUI hemos accedido a las muestras de audio del sistema de sonido. Como se puede apreciar. al hilo RTP. el diseño de los hilos de audio y RTP es genérico con un buffer accedido a través de un objeto 'SUA_Buffer': nada impide que se creen dos parejas de búfferes independientes y dejar al usuario la responsabilidad de pasar de una a otra filtrando. no necesitaremos tocar el más mínimo detalle en la pila RTP. ya que funcionará igual: leerá de la red lo que pueda y cuando pueda. podremos cambiar el protocolo de transporte de los flujos (RTP) por otro. Por otra parte. Vamos a estudiar de forma detallada cómo se han implementado estos dos búfferes (de lectura y de escritura ó búfferes de driver a red y de red a driver) partiendo de la siguiente figura: Figura 30: Funcionamiento de los búfferes de audio 98 . sin tener que alterar una sola línea en las clases que proporcionan el acceso a los recursos de audio de la máquina.

y en condiciones normales siempre el índice de lectura será menor que el de escritura. cuando ambos sean iguales querrá decir que no hay datos para leer. pero otras longitudes también serían perfectamente válidas) la impresión lógica es la de un buffer infinito. Hay que mencionar que la escritura realiza la simulación de búffer circular mediante el procedimiento que se ve en la siguiente figura: 99 . lo haremos a través de un objeto 'SUA_Buffer' que encapsula su funcionamiento. Físicamente los búfferes de audio están compuestos por una serie de variables globales (aquí presentamos las variables de cada búffer. 'SUA_Buffer' nos permite realizar una operación de escritura y tres tipos diferentes de operaciones de lectura. • Un entero largo sin signo que representa el índice de lectura. • Un entero largo sin signo que representa el índice de escritura. aunque dispongamos físicamente de un array lineal con una longitud grande (se suele escoger 8K. Sin embargo. A la hora trabajar con los búfferes. de RTP ó desde las barras de volumen pretendamos acceder a los búfferes de audio. ya que se ha realizado una implementación circular. siendo la diferencia la cantidad de información no leída hasta el momento. • Un objeto 'QMutex' que controla el acceso a todo el búffer lógico. Por lo tanto estarán duplicadas): • Un entero largo sin signo que almacena la longitud del búffer. Nos vamos a basar en un precepto: nunca el índice de lectura puede ser mayor al de escritura.Cuando desde los hilos de audio. • Un array estático de enteros cortos con signo de longitud igual al tamaño del búffer lógico.

Al método que implementa esta primitiva le pasaremos un array temporal de una determinada longitud y tratará de leer todo lo disponible hasta rellenar el array. Es utilizada por el hilo de audio que reproduce el sonido recibido del otro extremo: las escrituras en el driver OSS deben contener la mayor cantidad de datos posibles. Si no lo hay. y si no esperaremos a que estén disponibles. con el puntero de lectura en la posición 0. ni ha alterado los punteros). Es una función que tratará de realizar la lectura más grande posible.Figura 31: Búfferes de audio como búfferes circulares • Escritura: añade datos al final del buffer si hay espacio para ello. • Lectura más grande posible: esta operación lee y borra del búffer tantos datos como haya disponibles. • Lectura no instrusiva: se trata de un modo de lectura que no modifica el puntero de lectura y trata siempre de realizar la lectura más grande posible. porque siempre buscaremos paquetes con la misma cantidad de audio. realiza la simulación de búffer circular copiando los datos no leídos al inicio del búffer y añadiendo los nuevos tras de éstos. Si lo hay. Es una operación muy útil para el hilo RTP. aparecerán N+M datos al principio. • Lectura de longitud fija: esta primitiva. La impresión global es que si había N datos sin leer y se escriben M al final (y no hay espacio). o lee (y borra) la cantidad de datos suficientes para llenar el búffer entero ó devuelve un valor negativo indicando que no ha tocado el búffer (ni ha leído. los escribe y avanza el puntero de escritura hasta la siguiente posición vacía. ni ha escrito. Si hay más datos que capacidad tiene el búffer pasado como parámetro lo llenará y devolverá el tamaño del búffer. por lo que si tenemos almacenadas suficientes muestras como para rellenar un paquete éste se generará. Se usa concretamente para 100 . el último dato válido en la posición N+M-1 y el puntero de escritura en la posición N+M. devolviendo además el número de muestras leídas (0 ni no hay ninguna muestra a leer). dejando un residuo para futuras lecturas.

ya que no deben interferir en el normal desarrollo de la transmisión del sonido (si avanzamos el puntero de lectura 'borramos' los datos leídos). cuando los datos almacenados más los que se pretenden escribir superen el tamaño del búffer. Por una parte está la congestión del búffer. porque cuando el búffer experimente congestión no sabremos si es una situación crónica y podemos cortar sin miedo ó si se trata de una situación puntual. pero presenta dos problemas. y el oyente acabará notando un chasquido en la reproducción. Como el búffer se considera grande. El búffer se irá llenando hasta el final y llegará un momento en el que no quedará espacio al final de búffer para escribir nuevas muestras. Como compromiso. esto supone la pérdida de audio muy retardado (un par de segundos). El problema sucede cuando los datos no leídos comienzan ya al principio: no queda físicamente espacio para nada más. pero es una situación perfectamente posible y nada deseable: supongamos que sólo escribimos y no leemos. ¿qué ocurriría si por algún tipo de problema el buffer se desbordara?.los widgets que implementan las barras de nivel de audio. por lo que realizaremos una copia de todos los datos leídos hasta el principio para añadirles detrás los nuevos. La única solución disponible es la pérdida selectiva de los datos más antiguos. Este modelo de búffer circular es muy sencillo e intuitivo. algo siempre 'éticamente' complicado. se decidirá eliminar la mitad del mismo más antigua. La siguiente figura expresa este concepto: Figura 32: Situación de congestión en los búfferes de audio El segundo problema con el que se presenta este diseño de búffer es el rendimiento que se le da a 101 . Realmente parece extraño que un buffer circular se desborde.

no serían usados y se pasaría a recolocar las muestras no leídas al principio. ¿Cómo sabremos que se ha producido un doblamiento y no es que estemos en la situación 'normal'?. raramente el mismo. Como se comprueba. pero presentan su punto fuerte en el fácil manejo. en mayor o menor medida (de forma cíclica).la memoria reservada. la pregunta clave de esta sección es: ¿por qué dos hilos concurrentes que lean y escriban en el driver a la vez y no uno sólo que primero lea y luego escriba?.4. Sin embargo. sin esperar a que las muestras sean reproducidas totalmente. 102 . porque los datos no leídos empiezan al final del búffer y terminan al comienzo. No es que sea bajo. Puede ser cualquiera. en el caso de que queden al final 255 bytes libres. el problema de la grabación y reproducción simultáneas viene del uso de llamadas de lectura/escritura bloqueantes. la lectura es más compleja. Supongamos que las escrituras típicas sean de 256 bytes. 3. se ha fijado al formato en el que se lee el audio del driver (el hilo RTP deberá realizar las conversiones pertinentes si así lo considera necesario): muestras de 16 bits con signo y en ordenación 'little endian'. lo que nos da cierto margen para ir suministrando datos que serán reproducidos de forma constante. Este modelo es más eficiente pero mucho más complejo. para que cuando lleguemos la final de búffer aprovechemos hasta el último byte. El caso de la escritura no es demasiado preocupante. porque ¿qué ocurre si experimentamos congestión y el índice de escritura dobla al de lectura?. En general. Simplemente es necesario abrir un fichero (el dispositivo) y realizar sobre él como mínimo tres llamadas de control de entrada/salida ('ioctl') para poder ser usados: establecer el número de canales de audio (1 para sonido monofónico). Si no escogiésemos un múltiplo entero siempre nos quedarían al final bytes no utilizados. Hilos de audio: lectura/escritura de un driver OSS Para el manejo del audio tenemos dos hilos muy sencillos: 'SUA_Audio_Rec' que graba muestras desde el micrófono y las escribe en el búffer de emisión y 'SUA_Audio_Play' que reproduce el audio que lee desde el búffer de recepción. se trata de un modelo más complicado y propenso a errores. pero depende del tamaño de los datos que se escriban.2. Ahora bien. tiene que ser un formato fijo y conocido tanto por los hilos de audio como por el hilo RTP. el modelo es muy sencillo pero no destaca por su eficiencia. como 2k bytes. porque se puede configurar el driver para que acepte las muestras de audio y retorne la ejecución de forma directa. y cuando leamos de la tarjeta de sonido habrá más o menos audio. ya que le pasamos a la llamada al sistema un puntero a un array en el montículo que la llamada intentará llenar. Esto es debido a que se implementa una especie de búffer interno entre el driver y la tarjeta. Y de momento. únicamente se permite sonido monofónico. Para mejorarlo existe otro modelo en el que el puntero de lectura puede estar más adelantado que el de escritura. pero para no tener que perder el tiempo convirtiendo el audio dos veces. Por último hablemos del formato del audio almacenado en estos búfferes. fijar la frecuencia de muestreo (8kHz) y concretar el formato de las muestras (16 bits con signo en 'little endian'). Pero el problema es que nunca podemos asegurar a priori el tamaño de las escrituras: los paquetes RTP no tienen porqué tener siempre el mismo tamaño. El problema se agrava si escribimos cantidades de datos mayores. bloqueando el acceso al dispositivo hasta que no se finalice la grabación. Se podría pensar en reservar una cantidad de memoria múltiplo entera del tamaño de los datos que escribimos. Como se puede comprobar. La respuesta es debido a las escasas prestaciones que presentan los drivers OSS para la grabación y reproducción de forma simultánea. La calidad de estos drivers no es excesiva. En ese caso el espacio desaprovechado será mayor.

Los dos objetos que implementan los hilos tienen una serie de puntos en común. Este RFC proporciona una interpretación de los campos genéricos de RTP enfocándola hacia conferencias de 103 . en la que se codifican varias funciones elementales. sobre servicios multicast ó unicast. como audio. Estos objetivos se encuentran cubiertos con la designación de uno de los dos hilos como maestro ('master') y otro como esclavo ('slave'). sin esperar a que sea generada. deberíamos implementar algún mecanismo de compartición de dicho descriptor y asegurarnos que el dispositivo de audio se abra y se cierre una única vez. vídeo ó datos de simulación. 3. el mismo que lo abrió y lo realizará en su destructor. mencionábamos una fundamental: todos los hilos comparten los descriptores de fichero. abriremos y compartiremos el descriptor. pero esto tiene el enorme inconveniente de que si no hacemos suficientes llamadas acabaremos perdiendo datos. será tarea del hilo 'master'.4. A la hora de cerrar el dispositivo. y a su constructor le pasaremos el descriptor de fichero que devuelve el 'master' para que pueda acceder a los drivers OSS. podremos leer en cada momento sólo la cantidad de información que ya esté presente. Usamos para ello el RFC 1890 "Perfil RTP para Conferencias de Audio y Vídeo con Control Mínimo". De esta sencilla forma. Es un hecho de vital importancia para nuestro diseño. Proporciona funciones para el transporte extremo a extremo aptas para transmitir datos en tiempo real.Para rodear el problema podríamos intentar usar un array temporal muy pequeño. pero tiene implicaciones relevantes: cuando relatábamos las características de los hilos. En ese mismo documento se le describe como: "RTP es un protocolo de transporte en tiempo real. para que no bloquease demasiado tiempo. porque al ir la grabación y la reproducción en hilos separados y tener que usar llamadas de lectura y escritura sobre un descriptor de fichero común. tal vez este hecho pueda considerarse de poca importancia al hablar de un diseño de alto nivel. debemos 'completar' su definición con un perfil que de valor a sus distintos campos. La mejor solución se da mediante una llamada de control de terminales ('ioctl') del tipo SNDCTL_DSP_GETIPTR. por lo que se ha decidido heredarlos de una clase base denominada 'SUA_Audio' a secas. que devuelve el número de bytes disponibles en el búffer interno del dispositivo de grabación. RTP está definido de forma general en el RFC 1889 "RTP: Un Protocolo de Transporte para Aplicaciones en Tiempo Real". debemos tener muy claro que el hilo 'slave' está parado (se produciría un error de protección si pretendiésemos leer de un descriptor inexistente). Pila RTP y codificación/decodificación RTP es el protocolo que usamos para transportar el audio codificado entre los extremos de nivel de transporte que se han negociado a través de SDP en el inicio de llamada con SIP. lo que desemboca en un menor tiempo de bloqueo del driver. El primero en crearse será el 'master' (se le pasará al constructor un valor '-1' donde debiera ir un descriptor de fichero) y abrirá el dispositivo. Para poder usar RTP con los flujos que a nosotros nos interesan. pues se desbordarán los búfferes internos del driver por no 'evacuar' el audio generado. Los dos hilos ('SUA_Audio_Rec' y 'SUA_Audio_Play') pueden comportarse como maestros y como esclavos y la determinación de los roles se realiza cuando se crean los objetos que representan los hilos. mientras que el segundo en aparecer será el 'slave'. Por lo tanto hay que tener un poco de cuidado porque para cuando llamemos al destructor del hilo 'master'. es decir. RTP no realiza reserva de recursos y no garantiza la calidad del servicio para los flujos que transporta". Sin embargo.3. audio codificado. De esta sencilla forma.

En este caso no es necesaria la separación del proceso en dos hilos. y si no se puede hacer con calidad. En nuestro proyecto. Hilo RTP El hilo RTP está soportado por el objeto "SUA_Rtp". se iniciará un bucle semi-infinito ('idle loop' ó bucle ocioso) controlado por una variable de salida. En concreto hace referencia a los números asociados a las distintas codificaciones que pueden ir como carga ó 'payload'. Para explicar mejor este concepto se presenta la siguiente figura: 104 . Identificador del códec de audio a usar (0 = PCM ley mu.1. De todas formas. Además. que ya hemos comentado: • • • a). lo que provocará o bien la lectura de la red y las tareas asociadas o bien la escritura y todo lo que conlleva.3. creará el socket para el acceso a la red y reseteará las variables que realizan el control de secuencia de los paquetes RTP.vídeo y audio. Al constructor del objeto que implementa el hilo le pasamos cinco parámetros básicos.4. su aportación no es tan determinante como los ya vistos. Cuando el hilo comience su ejecución. Dirección IP remota y puerto UDP remoto. Dirección IP local y puerto UDP local. se puede decir que el hilo RTP no hace nada salvo atender a los eventos que generan los temporizadores que se han configurado. se trata de un hilo que aunque tiene mucho que ver con la calidad del sonido que conseguimos al final. todo el esfuerzo se verá arruinado. que no realizará ninguna tarea de forma directa. el soporte para RTP está proporcionado por un hilo del tipo "SUA_Rtp" y una clase de apoyo que funciona como párser: "SUA_Rtp_Dgram". en el esquema general del sistema de audio tendremos que ser muy cuidadosos porque el oído humano es en muchas ocasiones más sensible de lo que creemos (y de lo que deseamos) y lo que podemos considerar como pequeños fallos resultan en un servicio defectuoso que degrada la imagen general de la aplicación: no en vano estamos programando un software para hablar. ya que no tenemos el problema de las llamadas bloqueantes y la continuidad del audio que veíamos en los hilos de audio. se trata de un único hilo que realiza tanto la emisión como la recepción de datagramas RTP. Estudiemos ambos por separado: 3. Por lo tanto. simplemente estará esperando a que salte uno de los dos temporizadores configurados. 3 = GSM y 8 = PCM ley Este mismo constructor configurará los objetos del tipo 'SUA_Buffer' para asegurarse su acceso a los búfferes intermedios.

• Por último crearemos un paquete RTP cuya carga será el audio que acabamos de codificar y lo enviaremos por la red hasta su destino.. etc. 105 . Y si esto no puede ser así. Crearemos un array temporal con una longitud igual al tamaño de la carga que queramos para el datagrama RTP y realizamos la lectura. • Si hemos obtenido datos. por lo que será necesario suministrar a su constructor el número de secuencia del último paquete enviado. el estado de los relojes. Nosotros suministraremos un array sin codificar con una longitud y esperaremos otro array de vuelta con otra longitud que a priori no conoceremos pero que generalmente será más pequeña. deberán ser de la longitud solicitada. deberemos codificarlos de acuerdo con el códec que se le ha especificado al hilo RTP. Este paquete no es una unidad atómica. ya que básicamente realiza estos tres pasos: • Lectura de longitud fija del búffer intermedio: este es el punto donde nos interesa este tipo de llamada que vimos en la sección anterior. no se devolverá ningún dato. por efecto de la compresión.Figura 33: Esquema funcional del hilo RTP Este hilo realiza de forma alternativa dos tareas: enviar datos y recibirlos.. Si se devuelven datos. El proceso de emisión es quizás el más sencillo. sino que pertenece a un flujo.. por lo que no proseguiremos y volveremos al bucle ocioso hasta que salte el temporizador la próxima vez: tal vez entonces haya más datos de audio en el búffer.

Pero hacer esta suposición es muy arriesgado: tal vez los paquetes intermedios lleguen más tarde por otra ruta ó el paquete adelantado puede no ser tal. Si lo es. pero con el audio en secuencia (porque por diseño hemos establecido que en los búfferes de audio éste vaya de forma consecutiva). Como consecuencia. como RTP viaja sobre UDP podremos recibir datagramas duplicados. • Decodificación del audio conforme al tipo de códec que maneja el hilo. Para aislar estos problemas. no se puede tomar una decisión importante basándonos en un único dato. Como antes. Supongamos que esperamos recibir el paquete cuyo número de secuencia sea N. la función de lectura de la red retorna como si no se hubiese leído nada. La solución tomada se basa en disponer de un nuevo búffer con capacidad para R paquetes. Posteriormente estudiaremos cómo se implementa esta función. pero recibimos uno con secuencia P. la función de lectura devolverá un array con audio codificado y una longitud: si ese audio corresponde a un único paquete que llega de forma correcta y en secuencia ó a varios porque alguno ha llegado retardado y ha sido necesario esperar hasta conseguir un flujo ordenado nos es indiferente. La función de lectura de la red devolverá su carga. La idea consiste en no desechar paquetes porque lleguen un par de posiciones adelantadas y hacer el hilo más fuerte para que no pierda la secuencia al no llegar un paquete a tiempo. El proceso de lectura de la red puede considerarse complejo. Al llamar a esta función recibiremos muestras de audio en secuencia y codificadas de una longitud variable ó no recibiremos nada.El proceso de recepción es muy parecido. sino un paquete erróneo que de una manera ú otra ha acabado en nuestro socket. pero un poco más complejo. por lo que deberíamos reengancharnos con este paquete. Puede ser que los paquetes intermedios entre el esperado y el recibido se hayan perdido. donde P>N. Entonces: • Si el número de secuencia del paquete es menor al número de secuencia que esperamos recibir. A un array de audio entrante devolverá un array saliente decodificado. desecharemos el paquete por considerarlo 'retrasado' ó tal vez duplicado. porque nos interesará conseguir un array de muestras de audio codificado más o menos grande. que por cierto. 106 . y ver si se trata de un paquete válido. este paquete habrá llegado en orden y será aceptado. de generalmente longitud mayor. • Escritura en los búfferes de audio intermedios. adelantados. se puede considerar de relativa dificultad. extraeremos su número de secuencia y su carga (el audio codificado). dejando el audio dispuesto para ser reproducido por los hilos de audio cuando le llegue el momento. • Si el número de secuencia del paquete es mayor del esperado. Por lo tanto. entonces nos encontramos con un paquete 'adelantado' y es aquí donde empieza el meollo de la cuestión. retardados. Veamos su parte más amable: • Lectura de la red: disponemos de un método en este hilo que realiza una lectura 'inteligente' de la red. La función de lectura de la red retorna como si no se hubiese leído nada. debemos implementar un 'segundo procedimiento de buffering' para la recepción del flujo de audio. Lo primero que haremos cuando leamos de la red un paquete RTP es controlar su estructura. • Si el número de secuencia es igual al que esperamos recibir. malformados ó incluso podemos no recibir cierto datagrama. cuando ya sabemos que el orden de llegada de los paquetes RTP (y la posibilidad de que efectivamente lleguen o no) es a priori desconocida.

Pero supongamos que se da el caso de que van llegando paquetes con número de secuencia P+1. pero se ha decidido que "hasta que la mitad final del búffer esté llena de paquetes en secuencia". devolveremos el audio del N+3 hasta el N+R. P+3. No tiene que ser así. sólo que entre el paquete que esperamos recibir y el paquete suelto que tenemos hay una posición menos. actualizaremos el número de secuencia esperado hasta N+1 y haremos al paquete que tenemos 'colgando' en el búffer retroceder una posición. recibimos el paquete N. y no actualizaremos N. ¿hasta cuándo debemos esperar?. porque nos pueden hacer perder la secuencia de paquetes). En ese momento se considerará que tenemos suficiente información para determinar que el flujo de paquetes sigue en orden. lo conservaremos en el búffer en la posición P-N. actualizaremos el número de secuencia del siguiente paquete esperado a N+R+1. Además. el paquete P+1 irá en el búffer en la posición PN+1. Retornaremos de la función de lectura hasta que vuelva a saltar el temporizador.. Pero. Esta decisión es terriblemente subjetiva.. de forma que la situación queda igual. si se ha perdido el paquete N+2. pero tiene toda la pinta. sino de toda la ristra consecutiva que llegue hasta el final. P+2. Si en la siguiente lectura de la red. En ese caso.. etc. La función de lectura de la red devolverá un array de audio con las muestras de los paquetes consecutivos hasta el final: no únicamente de la mitad final del buffer. En la siguiente figura se presentan los casos de R par (se espera a tener la mitad de las muestras) y R impar (esperamos a tener la mitad más una): 107 ..Si P ≥ (N+R). consideramos el paquete demasiado adelantado y lo desechamos sin más (aquí se puede observar que valores de R pequeños son peligrosos. el P+2 en la P-N+2 y así sucesivamente. lo que nos da una idea de que los paquetes entre N y P se han perdido. hasta la P-N-1. Si (N+R) > P. pero que se ha perdido uno ó unos cuantos cuyo número de secuencia estará entre N y N+ (R/2).

y nos quedaríamos bloqueados esperando el N+(R/2)+1 indefinidamente. ¿y si se han perdido el N+2 y el N+(R/2)+1?. Nunca podríamos vaciar el búffer (porque la segunda mitad nunca se llenaría). pero la ocupación del búffer supera el 80%. En la siguiente figura se presenta el caso con R=12 y un búffer lleno al 83. y se actualizará el número de espera al número de secuencia del último paquete almacenado en el búffer más uno. se devolverá desde el último paquete del búffer (no tiene porqué estar en la última posición) hacia atrás todos los paquetes en secuencia.Figura 34: Búffer de recepción de paquetes RTP adelantados: detección de secuencia Pero. Por eso se toma una segunda decisión: si no hay una secuencia consecutiva formada por los paquetes de la última mitad del búffer.33%: Figura 35: Búffer de recepción de paquetes RTP adelantados: vaciado por congestión 108 .

puede ser audio 'antiguo'. Si devolvemos audio constantemente. cuando el extremo remoto puede estar ya mandando paquetes con números de secuencia Z. Para recurperarnos de la catastrófica situación de pérdida de secuencia. Y desde ahí seguiremos con el procedimiento normal. sea cual sea. el campo de versión del protocolo deberá tener un valor fijo de '2').2. nunca saltará ese temporizador y no habrá mayor problema. aunque evidentemente mejorable. Este temporizador indicará la máxima latencia posible de los paquetes RTP en el búffer de paquetes adelantados. habilitamos un temporizador. podrán producirse cortes en la reproducción porque el búffer intermedio de audio se quedará vacío. el sistema podrá reengancharse sin dificultades. y en ellas es más importante la sensación de fluidez que la posibilidad de no perder datos. lo cual tiene. Otros deberán poseer un valor acorde con el tipo de flujo que estamos manejando (si enviamos y recibimos audio codificado en GSM el campo 'Payload Type' ó tipo de carga deberá tener un valor de '3'). y por lo tanto. si se ha perdido una secuencia larga de paquetes. • Si R es pequeño. Vaciaremos el búffer y nos engancharemos de forma aleatoria con el siguiente paquete que llegue. que será reseteado cada vez que la lectura de la red devuelva audio en secuencia. y aunque se supone que el búffer intermedio es siempre muy grande y que no tendrá problemas para almacenarlo. siendo Z>>>(N+R). implicará que hace mucho tiempo (tal vez se pueda poner el temporizador a un par de segundos) que no se reciben datos: el búffer de paquetes adelantados estará bloqueado esperando que se llene al 80% ó que la mitad final se llene de forma consecutiva pero con paquetes del rango de N a N+R. La robustez del sistema se basa totalmente en la elección de R (tamaño del búffer de recepción de paquetes adelantados): • Si R es grande. El problema de un R grande es que se tardará mucho en llenar la mitad ó el 80% del buffer. se realiza mediante las funciones miembro de la clase que implementa el comportamiento de un 109 . 3. estamos trabajando con aplicaciones en tiempo real. evidentemente. Cuando volvamos al estado normal. retornaremos mucho audio de un sólo golpe. la latencia de los paquetes será menor. no se trata en ningún caso de una cuestión sencilla y la solución dada es bastante aceptable. y si esto tarda mucho.3.Con estas dos medidas (mitad final en secuencia y 80% del búffer lleno) se consigue un buen comportamiento frente a pérdida y desorden en los paquetes.4. Párser RTP El párser RTP se encarga de las siguientes funciones: • Analizar la corrección de estructural y semántica de un paquete: éste deberá contar con los campos obligatorios y alguno de ellos deberán tener un valor fijo (por ejemplo. • Extracción de todos y cada uno de sus campos de forma independiente. consecuencias desastrosas. Pero corremos el riesgo de que si se pierde una secuencia larga de paquetes no podamos reengancharnos al hilo. por lo que la latencia de los paquetes crecerá muchísimo: como sabemos. podremos recibir paquetes muy adelantados. Si salta ese temporizador la catástrofe está servida. Pero si salta. y el hilo suministrará datos de forma constante y rápida. Como se puede comprobar. no se devuelven datos hasta que no sepamos exactamente si nos hemos vuelto a enganchar a la secuencia.

estas direcciones poseen un campo denominado 'hostport' que nos informa acerca del lugar al que tenemos que mandar las peticiones y las respuestas. un examen cuidadoso de su dirección y un par de búsquedas DNS desembocarán en la localización de una dirección IP y un puerto: no sabremos si se trata de la dirección IP de su servidor proxy ó si directamente es la de su agente. accederá a los miembros del hilo de audio 'master' asignados para establecer un nuevo volumen. El párser RTP se implementa mediante la clase "SUA_Rtp_Dgram" que encapsula un paquete RTP. que como ya dijimos son conceptualmente transparentes) no comentamos un pequeño problema que queda colgando: el control del volumen de grabación y de reproducción. Por ejemplo. Para más información sobre esta clase referimos al lector hasta el diseño de bajo nivel.4.5. Tampoco nos importa porque el modo de operación es el mismo. • Creación y ensamblado de paquetes RTP a través de ciertos parámetros suministrados por el usuario y de otros globales para todo el flujo con el que estamos tratando. Búsquedas DNS en SIP SIP es un protocolo que maneja direcciones SIP (aunque suene redundante) para localizar servidores. un puerto y un protocolo de nivel de transporte a partir de una dirección SIP. Parece claro que desde la GUI deberemos disponer de mecanismos para acceder al sistema multimedia y para establecer el volumen. Únicamente vamos a relatar cómo conseguir una dirección IP. según sea cada caso. Es importante el hecho de que será el hilo maestro el que acceda a los dispositivos en todas las situaciones: también a la hora de fijar el volumen. 3. como ya hemos visto. Cuando la pila SIP reciba éstos eventos. si conociésemos la dirección SIP de un usuario y quisiéramos iniciar una sesión contra su agente. 3.4. disponemos de dos subeventos pertenecientes al tipo de eventos "SUA_Volume_Event": se trata de "VE_DSP" para controlar la reproducción y "VE_MIX" para controlar el mixer y el nivel de grabación. En esta pregunta no vamos a entrar en decidir qué paquete tiene que ir a qué sitio en cada momento. La descripción ABNF de dicho campo 'hostport' de una SIP-URI (ó dirección SIP) es la siguiente: Tabla 12: Descripción ABNF de una SIP-URI hostport = host [ ":" port ] host = hostname | Ipv4address | Ipv6reference port = 1*DIGIT 110 . La solución que se ha tomado ha sido muy sencilla y se realiza a través de la pila SIP. servicios y usuarios.paquete RTP. Gestión del volumen Cuando hablábamos al principio de que el sistema de audio/RTP es estanco y que no se comunica con el exterior (salvo a través de los eventos de depuración. La interfaz gráfica dispone de barras de control del volumen que generarán dichos eventos cuando así lo solicite el usuario y se los mandarán al hilo encargado de gestionar la sesión.

Como hemos asumido que 'host' es una dirección IP." ] domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum toplabel = alpha | alpha *( alphanum | "-" ) alphanum Ipv4address = 1*3DIGIT ".. Si el puerto no se especifica." 1*3DIGIT Ipv6reference = "[" IPv6address "]" Ipv6address = hexpart [ ":" IPv4address ] hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] hexseq = hex4 *( ":" hex4) hex4 = 1*4HEX Como se puede observar. Debemos buscar en los registros SRV del servicio "_sip" para UDP._tcp") obtendremos el nombre de la máquina encargada y un puerto. SCTP.hostname = *( domainlabel ". se deberá usar ése. Para estas búsquedas DNS se usan los registros SRV. se usará el 5060. Por defecto se asume UDP. Si el campo 'host' es una dirección IP numérica (ya sea IPv4 ó IPv6). primero se deberá probar con UDP.. Si el campo 'host' fuese una dirección Ipv4 no sería necesaria ninguna búsqueda DNS porque ya tendríamos el protocolo de transporte. etc. realizaremos una búsqueda DNS (preguntando por los registros SRV asociados al campo 'hostname' para el servicio "_sip") por cada protocolo de transporte soportado por nuestro agente. Generalmente las respuestas a las peticiones por los registros SRV contienen nombres de máquina que es necesario traducir._udp" ó "_sip. Si no se concreta ninguno. el identificador de servicio para dichos registros referidos a SIP es "_sip". Para ello pasaremos al tercer punto.." ) toplabel [ ". Sólo si en la SIPURI no hay especificado un puerto usaremos éste. el campo 'hostport' estará compuesto de un campo 'host' obligatorio y un campo 'port' opcional. Es decir. y obtendremos la dirección de la máquina que atiende a las peticiones SIP en el dominio especificado para cada protocolo de transporte. asumiremos el 5060 para el protocolo de transporte que se especifique en el campo 'transport-param' de la misma SIP-URI (UDP." 1*3DIGIT ". pues ya tendríamos la terna IP. la forma en la que se prueben éstos estará sujeta únicamente a la consideración del usuario. saltaríamos los restantes pasos. El algoritmo completo que se aplica es el siguiente: 1. Nuestro agente únicamente funciona sobre Ipv4. Si la SIP-URI especifica un protocolo de transporte. El campo 'host' podrá ser un nombre de máquina. Si no se especifica ninguno. SCTP. TCP. TLS ú otro definido por el usuario). Si el campo 'host' no es una IP. puerto y protocolo. Ordenando las respuestas y seleccionando el protocolo (por ejemplo "_sip. TCP. 2. Concretamente. También pasaremos 111 . el cliente accederá al servidor mediante dicha dirección y a través del puerto especificado en la SIPURI. Si nuestro agente no soportase UDP ó soportase otros protocolos de transporte adicionalmente pero no hay respuesta a la transacción en UDP. Las búsquedas DNS entran en juego cuando el campo 'host' es un 'hostname'. por lo que la última posibilidad no se contempla. una dirección Ipv4 ó una dirección Ipv6. la dirección IP y el puerto. nosotros contamos con la cadena 'hostname' que queremos traducir a una dirección IP." 1*3DIGIT ".

Sin embargo.hostname"). para que se ejecute el 'slot' previamente configurado. Con este último paso conseguiremos al final traducir el campo 'hostname' a una dirección IP. deberemos traducir esa cadena a una dirección IP mediante una consulta al DNS usando los registros A (para IPv4) ó AAAA (para Ipv6). los cuales sólo pueden obtener traducciones asociadas a UDP como protocolo de transporte. el servidor aparece referenciado con un nombre de máquina. La forma que tienen para devolver los valores obtenidos (entero con el puerto y objeto 'QHostAddress' con la dirección) es mediante el sistema 'signal/slot' de Qt. El control retorna inmediatamente al código llamante. Como parámetros para la búsqueda únicamente deberemos pasarle un objeto del tipo 'SUA_Address' que encapsula el comportamiento de una dirección SIP ó SIP-URI. En nuestro proyecto las búsquedas DNS las realizan objetos de la clase 'SUA_Dns'. incluso de decenas de segundos._udp. implementadas como funciones miembro estáticas que pueden ser llamadas sin tener que instanciar un objeto 'SUA_Dns'. Hacen uso de la librería libresolv. La desventaja que presentan es que su uso no es tan directo como las síncronas.si no hay ningún registro SRV asociado.so como las anteriores. la particularidad de los objetos 'SUA_Dns' es que pueden realizar búsquedas tanto síncronas como asíncronas: • Búsquedas Síncronas: son búsquedas bloqueantes. Si no existe ningún registro SRV para el campo 'hostname' ó existiéndolo. El resultado obtenido será un objeto 'QHostAddress' que representa una dirección IPv4 y un entero con el puerto. y el código pierde linealidad. por lo que son tremendamente útiles para aplicaciones gráficas o cuando no podemos tener un hilo bloqueado durante mucho tiempo. • Búsquedas Asíncronas: se trata de búsquedas no bloqueantes. Su desventaja: bloquean al hilo llamante durante un periodo de tiempo que puede llegar a ser muy largo. porque es necesario configurar el slot. 3. 112 . pero que sólo funciona en un mismo hilo. que posibilita la generación de una señal Qt por el objeto que ha terminado la búsqueda. por lo que habrá que extremar las precauciones.so y tienen la ventaja de su fácil manejo y de la relativa linealidad que infieren en el código llamante. Para realizar las búsquedas en sí no usan libresolv. sino objetos proporcionados por las librerías Qt. Siempre asumiremos en nuestro caso UDP como protocolo de transporte. ya que nuestro agente únicamente usa UDP (por lo tanto únicamente realiza búsquedas "_sip. Se trata de un mecanismo similar al de los eventos. que son configuradas primero mediante una SIP-URI y que posteriormente puestas a trabajar.

desde cualquier parte del agente hacia la ventana de depuración. Como ya se ha comentado. está compuesta por todos aquellos métodos necesarios para el transporte de información desde la pila hasta la interfaz y desde la interfaz a la pila. consideramos que son de respuesta. La forma en la que se implementa el retorno de los datos a la función llamante una vez que la llamada finalice es también a través de eventos: unos eventos que nosotros.2. son los encargados del transporte de los mensajes SIP entrantes de la red desde el hilo generador hasta el hilo que represente a la sesión a la que pertenece el mensaje. Eventos de gestión de hilos Ya hemos comentado en varias ocasiones la necesidad de gestionar los hilos que vamos generando. que transportan una cadena de texto plano junto a un puntero al emisor del evento. Para ello usamos los eventos del tipo SUA_ThMan_Event (373737) que transportarán los siguientes campos: 113 . como ya vimos en el punto “2.4. Concretamente se le han dado dos usos a estos eventos: por una parte. la señalización intermedia ó metaseñalización constituye el tercer y último módulo del presente agente de usuario SIP. 4. junto con un tipo asociado. físicamente empleamos eventos Qt. En principio. se usa casi exclusivamente para el transporte de cadenas de texto. por ejemplo al concluir positivamente una petición de propiedades ó durante el proceso de registro. Eventos de texto Dentro de los eventos de texto consideramos a todos aquellos cuya función sea el transmitir cadenas de texto. que será única y que forma la parte más importante del diseño. que aunque aquí hayamos diseñado una basada en ventanas. • SUA_String_Event (393939): se trata este tipo de un conjunto de eventos más genéricos. Con esta sencilla introducción (y todos los comentarios relativos al paso de eventos vistos ya en el presente documento) estamos listos para estudiar los cuatro tipos de eventos que la metaseñalización pone a nuestra disposición: 4. Mientras que conceptualmente la metaseñalización usa “métodos”. Concretamente contamos con dos tipos (el número que aparece entre paréntesis es su indicativo de evento): • SUA_Text_Event (363636): este tipo de eventos. puede ser de cualquier tipo. y la GUI. de forma arbitraria. son los que llevan la información SIP desde la pila hacia la GUI. Se trata por lo tanto del “contrato” entre la pila. estos eventos son formas de notificar la ejecución de funciones de forma asíncrona y thread-safe: asíncrona porque el control de la función llamante retorna antes de que la función llamada haya finalizado y thread-safe porque ni el objeto llamante ni el llamado tienen porqué estar en el mismo hilo de ejecución.3 Depuración” del presente documento. Y por otra. DISEÑO DE LA METASEÑALIZACIÓN Como ya hemos comentado en varias ocasiones.1. El tipo asociado a la cadena indicará al objeto encargado de visualizar el texto el formato que se le deberá adjudicar.

• • • •

Un código que indica su finalidad (lo vemos a continuación). Un puntero a la ventana ó hilo emisor, de carácter obligatorio. Un puntero a un hilo, opcional. Un puntero a una ventana, también opcional.

En función de qué actuación queremos realizar de forma concreta, disponemos de hasta seis subtipos de eventos de gestión distintos: • TM_Kill_Sender (1): este evento es lanzado desde un hilo worker SIP que ha terminado su ejecución al hilo generador de eventos para que destruya todo rastro de dicho hilo; entre las tareas a realizar destacan la de eliminar la entrada que apunta al hilo en la lista global de hilos activos, liberar su identificador y destruir el objeto que encapsula el funcionamiento del hilo. • TM_Set_Confirm_Flag (2): este evento lo lanza el hilo generador ó la interfaz gráfica hacia un hilo worker, para que éste active un flag (presente en todos los hilos de este agente) de forma que al terminar la ejecución, y antes de mandar el 'TM_Kill_Sender', envíe un evento de gestión del tipo 'TM_Exit_Confirmation' al objeto especificado para que se conozca de primera mano cuándo ha terminado la sesión. Es una técnica muy útil para destruir ventanas que representan sesiones que ya no están activas: en lugar de posicionar un botón para que el usuario cierre la ventana, la pila puede indicar a la ventana cuándo ha terminado para que sea esta misma la que se cierre automáticamente. • TM_Exit_No_Conf (6): este evento es el contrario del anterior, limpia el flag que exige la confirmación de la finalización. • TM_Exit_Confirmation (3): este evento es, como ya hemos visto, la propia confirmación que envía un hilo hijo SIP si su flag de confirmación ha sido activado. Se enviará al objeto especificado en el evento que activa la solicitud de confirmación. • TM_Update_Th_Pointer (4): este tipo de eventos serán enviados desde los hilos worker SIP a la interfaz gráfica para informarle de quién está manejando su sesión, y de cómo acceder a dicho hilo a través de su puntero. Generalmente sólo es necesaria la emisión de un evento de este tipo cuando se crea el hilo que atiende a una determinada sesión. • TM_Update_Mw_Pointer (5): semejante al anterior, este evento viaja desde la GUI hacia la pila para enviar al hilo encargado de la sesión un puntero a la ventana que deberá recibir sus notificaciones. Debido a que durante la vida de una sesión en la pila pueden aparecer múltiples ventanas en la GUI, deberemos disponer de un método para dirigir hacia una concreta y en cada momento toda la señalización (concretamente se puede pensar en la cantidad de ventanas que intervienen en la creación de una llamada de audio).

4.3.

Eventos de control de llamada

Los eventos de llamada, encarnados por la clase SUA_Call_Event (383838), constituyen el núcleo central de la metaseñalización, porque implementan una señalización intermedia que se asemeja mucho a la señalización de los terminales telefónicos tradicionales, ocultando así gran parte de la complejidad de SIP. Estos eventos son los que realmente dan sentido a la metaseñalización, porque permiten realizar a la GUI del soft-phone todo aquello que el usuario demanda de forma explícita. Existen numerosos subtipos de eventos de llamada, divididos en dos grandes conjuntos: por una parte los eventos que viajan desde la GUI hacia la pila y por otra los que van en sentido contrario.

114

4.3.1. Eventos de llamada en sentido de la GUI hacia la pila Tenemos los siguientes: • W_Accept_Call (1): indica a la pila que acepte la llamada entrante. • W_Reject_Call (2): expresa que el usuario no quiere aceptar la llamada. • W_Hang_Call (5): este evento se usa cuando es nuestro usuario quien desea cancelar la llamada, por lo que deberemos iniciar el proceso de cierre de la sesión como clientes. • W_Make_Call (6): se lanza este evento cuando el usuario quiere llamar a alguien. Lleva consigo una cadena de texto con la dirección SIP del extremo llamado. • W_Query_Options (41): se utiliza para comenzar una transacción de petición remota de características a través del método OPTIONS. Se deberá incluir la dirección SIP de destino. • W_Cancel_Options (45): este método cancela la petición comenzada con el evento anterior. • W_Register (50): inicia el proceso de registro contra nuestro proxy. • W_Cancel_Register_No_Conf (53): sirve para cancelar el proceso comenzado con el evento anterior.

4.3.2. Eventos de llamada en sentido de la pila hacia la GUI Dentro de este segundo grupo existen a su vez tres subgrupos de eventos que viajan desde la pila hacia la interfaz gráfica: por una lado están los relacionados con una llamada entrante, por otro, aquellos que manejan llamadas salientes y por último los relacionados con el proceso de registro, que no se enmarcan en uno ni en otro. 4.3.2.1. Eventos relacionados con una llamada entrante

Son tres. En concreto: • S_Incoming_Call (0): este evento es notificado por la pila a la interfaz para que le indique al usuario que hay una llamada entrante que podría ser aceptada. • S_Sending_Bye (23): evento que indica a la interfaz del hecho de que una petición BYE está siendo enviada al extremo opuesto. • S_Finish_Bye_200 (28): este evento da la llamada por finalizada porque la petición de cierre de la sesión ha sido confirmada positivamente. 4.3.2.2. Eventos relacionados con una llamada saliente

Destacan los siguientes: • C_Running_Call (9): este evento lo mana la pila para indicar que los flujos multimedia ya están en marcha y que la comunicación ha comenzado. • C_Invite_No_Response (11): informa al usuario acerca del error ocurrido al no poder contactar con nadie en la dirección SIP de destino previamente especificada. • C_Finish_Bye_200 (30): similar al evento nº 28, pero funcionando con llamadas salientes. 115

• C_Received_404 (37): se recibe este evento cuando con la dirección SIP proporcionada no se puede encontrar el usuario buscado en la máquina seleccionada. • C_No_Media_Mach (54): se genera este evento cuando no existe un códec común de audio entre aquellos propuestos por el cliente y por el servidor. 4.3.2.3. Destacan dos: • W_Register_Successfull (51): este evento indica a la GUI de que el registro ha concluido exitoso. • W_Register_unSuccessfull (52): este evento señaliza el fracaso del proceso de registro. Eventos de registro

4.4.

Eventos de volumen

Son aquellos eventos que lanza la GUI hacia el hilo de apoyo que gestiona su llamada para aumentar ó disminuir tanto el volumen de grabación como el de reproducción. Se basan en una clase SUA_Volume_Event (404040) a la que se le pasa tanto el dispositivo sobre el que queremos actuar (VE_DSP para el volumen de reproducción y VE_MIX para el de grabación) como el nivel que queremos establecer.

116

Ibon Marzo 2002 Profesor Cátedra Profesor Ponente Curso Académico 2001/2002 117 .ESCUELA SUPERIOR DE INGENIEROS DE BILBAO PROYECTO DE Diseño e Implementación de un agente de usuario SIP Parte nº 1 – Diseño de Alto Nivel Alumno Fecha Firma Gotxi García.

Sign up to vote on this title
UsefulNot useful