5 Endpoint H.323 (Extremo)
OpenH323 implementa la funcionalidad de un endpoint H.323 en una clase denominada simplemente H323EndPoint. La clase tiene diversos métodos virtuales que le permiten al programador poder personalizar la funcionalidad del extremo. Los métodos virtuales también sirven como una interface de comunicación por la cual OpenH323 notifica a la aplicación acerca de eventos importantes - la llegada de una nueva llamada, el final de una llamada, etc.
La tarea del desarrollador es la de especializar una nueva clase a partir de H323EndPoint y re-escribir (sobreescribir) código para solapar (override) la funcionalidad de varios de sus métodos virtuales. A continuación, una pequeña información para aquellos que son novatos en el polimorfismo: Si deseas solapar la funcionalidad de un método, el prototipo (declaración) del método en la clase descendiente debe ser exactamente la misma que en la clase padre. Si cometes un error en este proceso (digamos, los parámetros difieren) el significado del método es diferente, ya que representaría una sobrecarga (overload) del mismo (lo cual no es lo mismo que solapar o hacer una "override" el método).
Un aspecto importante de recordar acerca del uso de métodos virtuales en OpenH323 es que el código de los métodos no debería tardar demasiado en ejecutarse. Tienes que considerar precauciones acerca de los timeouts (tiempos de espera) en la negociación de la conexión H.323. La parte a la que llamas podría cancelar la llamada si tu endpoint no puede responder en cierto tiempo, debido a un retardo en cierto método virtual. El método virtual, usualmente, debería solamente informar sobre un evento a la aplicación y retornar. Un buen ejemplo sería que nunca deberías esperar por una interacción con el usuario dentro del código de OnAnswerCall().
5.1 Declaración
Nuestra aplicación tutorial declara al descendiente de la clase H323Endpoint en el archivo ep.h. La nueva clase se denomina MyEndPoint (archivo ep.h, línea 31). MyEndPoint necesita tener acceso a la configuración de la aplicación - p.e. para conocer le número de puerto de escucha o el alias de usuario. Por tanto, MyEndPoint tiene una referencia a la clase ProgConf (archivo ep.h, línea 34).
La tarea del método Init() en MyEndPoint method Init() (línea 38) es la de inicializar el endpoint. Esta inicialización significa, entre otras cosas, que el endpoint empiece a escuchar conexiones entrantes y que (opcionalmente) se registre en un gatekeeper.
Las Líneas 39-52 listan los métodos que MyEndPoint necesita solapar (override). Los nombres de los métodos son medianamente auto descriptivos. OnConnectionEstablished() es llamado cada vez que una nueva conexión ha sido exitosamente establecida. OnConnectionCleared() es llamado cuando una conexión ha sido cerrada. OpenAudioChannel() es llamado cuando los stacks de OpenH323 necesitan abrir (y posiblemente crear) un nuevo canal de audio. El método OnAnswerCall() es llamado cuando el stack necesita decidir si es que debería aceptar la llamada (esta explicación es algo simplificada, volveremos sobre el tema posteriormente). Finalmente, OnStartLogicalChannel() es llamado cada vez que un canal lógico ha sido iniciado. Nótese que existen varios otros métodos virtuales definidos en H323EndPoint, sin embargo nuestra aplicación no necesitar sobreescribir código para todos ellos.
5.2 Implementación
5.2.1 Init()
Describamos ahora los métodos en el archivo ep.cxx. Primero nos enfocaremos en el método Init() de MyEndPoint (líneas 76 - 138). El ojetivo de este método es el de inicializar el extremo H.323 , p.e. para configurar el alias de usuario, configurar codecs, empezar a escuchar por conexiones entrantes, y opcionalmente registrarse con un gatekeeper. Describiremos los pasos individuales a continuación.
El código en las líneas 79-84 incialmente comprueba si el arreglo de cadenas progConfig.userAliases no está vacío, si es así , inserta los alias del extremo. Si no se configuraron los alias, el alias por defecto es el login de usuario actual..
Al utilizar SetLocalUserName(str), es importante que sepas que primero vacía la lista de alias del extremo, y luego define a str como el primer (y único) alias. Si necesitas dar al endpoint varios alias, utiliza SetLocalUserName() para el primer alias y luego AddAliasName() para los subsiguientes..
La clase endpoint almacena los alias como una lista de cadenas. Cuando genera mensajes H.225, las cadenas de alias son insertadas como tipo AliasAddress - ya sea como dialedDigits (si la cadena sólo contiene caracteres "0123456789*#") o como h323-ID.
Las líneas comentadas 86-88 muestran como deshabilitar diversas características opcionales de H.323 , tal como el procedimiento Fast Connect , H.245 Tunneling y "H.245 in Setup".
Las líneas 91-99 indican a MyEndPoint que codecs debería utilizar. El orden de las invocaciones a SetCapability() definen la prioridad de los codecs. Nuestra aplicación tendrá el codec Speex codec a 8000 bps (descrito como SpeexNarrow3) como el codec de preferencia, siguiéndole de segundo GSM 06.10, el codec G.711 uLaw de tercero, etc. También configuramos el número de tramas (frames) del codec en los paquetes RTP salientes para los codecs Speex y GSM. Casualmente, los dos codecs utilizan tramas del mismo tamaño, 20 milisegundos. Entonces, si un canal lógico utiliza GSM 06.10, un paquete RTP que se transmite contendría 80 milisegundos de audio (cuatro tramas). Con Speex, el paquete contendrá 100 milisegundos de audio. El sitio de OpenH323 tiene un cuadro con información sobre tamaños de trama, duración de trama y ancho de banda para diversos codecs.
La línea 101 inserta dentro de la tabla de capacidades del endpoint la lista de todas las capacidades DTMF. Si la aplicación se ejecuta con un nivel de tracing 1 o mayor, la línea 103 envía la tabla de capacidades del extremo al reporte del tracing (mensajes de depuración).
Las líneas106-115 crean e inicializan un objeto listener instanciado de la clase H323ListenerTCP. El objetivo de este objeto es el de escuchar por conexiones entrantes de tipo H.323. El constructor acepta tres parámetros - el primero es una referencia a un H323EndPoint (o su descendiente), el segundo parámetro es la dirección de la interfaz de red sobre la cual deseamos "escuchar". El tercer parámetro es el número de puerto de "escucha" (se utiliza el valor almacenado en progConf.port). El valor INADDR_ANY que se tiene en la variable addr significa que queremos "escuchar" en todas las interfaces disponibles. Nótese que el desarrollador es responsable de la liberación del objeto listener si es que el objeto no puede iniciarse correctamente (p.e. StartListener() returna FALSE).
Las líneas 118-135
tratan con el registro del endpoint con un gatekeeper. Si
no deseamos registrarnos, simplemente ponemos un flag (bandera)
indicando que el registro fue satisfactorio, y continuamos
(ver case en la línea 121).
Si necesitamos realizar el registro, invocamos al método
UseGatekeeper() de H323EndPoint. Este método
invoca a uno de otros cuatro métodos, denominados DiscoverGatekeeper(),
LocateGatekeeper(), SetGatekeeper(), y SetGatekeeperZone()
según el criterio indicado en la siguiente tabla:
Dirección de GK Disponible | Id de Gatekeeper Id disponible ( Zone Name) |
Método Invocado |
---|---|---|
No | No | DiscoverGatekeeper() |
No | Sí | LocateGatekeeper() |
Sí | No | SetGatekeeper() |
Sí | Sí | SetGatekeeperZone() |
El primer paquete que es enviado por el endpoint durante el
proceso de registro lleva el mensaje H.225 RAS Gatekeeper
Request (GRQ). Si la dirección del gatekeeper es desconocida
(p.e. con DiscoverGatekeeper() y LocateGatekeeper(),
respectivamente), el mensaje GRQ es enviado utilizando multicast.
La dirección multicast asignada al registro del gatekeeper
es 224.0.1.41. Si la zone name (nombre de zona) está disponible
(LocateGatekeeper() y SetGatekeeperZone(), respectivamente),
el endpoint la pone dentro del campo opcional de GRQ denominado
gatekeeperIdentifier. Si el registro falla, la línea
133
genera un mensaje de error simple e Init() retorna
con FALSE. En caso contrario, el endpoint ha sido inicializado
exitosamente e Init() retorna TRUE.
5.2.2 Métodos Virtuales
Ahora nos concetraremos en las implementaciones de los métodos virtuales de MyEndPoint.
OnConnectionEstablished()
El método virtual MyEndPoint::OnConnectionEstablished() (archivotcz_tt1( ep.cxx), líneas 45-49) es invocado cuando la conexión H.323 ha sido establecida con éxito. Nuestra aplicación tutorial solo utiliza este método para realizar el tracing, pero probablemente, te imaginarás que OnConnectionEstablished() es muy importante para aplicaciones del "mundo real". Como un ejemplo, si tu necesitarías algún tipo de facturación, esta invocación denotaría el comienzo de la duración de la llamada a ser facturada.
OnConnectionEstablished() tiene dos parámetros. El primero es una referencia a un objeto H323Connection (el objeto esta protegido, tiene acceso exclusivo a él). El segundo parámetro (const PString & token) es una referencia a una cadena que identifíca de manera única a una conexión particular dentro del endpoint OpenH323. Es importante no confundir tokens de llamada (call tokens) OpenH323 con identificadores de llamada (call identifiers) H.323 . Los call tokens son utilizados internamente en la librería OpenH323 y no aparecen en ningún mensaje enviado por el endpoint. Los call tokens son construídos utilizando nombres de host y números de puertos, siendo de esta manera legibles para el desarrollador - esto puede ayudarte al depurar (debug) tu programa. Cada vez que tu aplicación necesite asociar sus datos con llamadas H.323 individuales, los call tokens son el enlace natural.
OnConnectionCleared()
El método virtual MyEndPoint::OnConnectionCleared() (archivo ep.cxx, lines 54-58) es invocado cuando una conexión ha sido cerrada. Sus parámetros son los mismos que los de OnConnectionEstablished(). Si deseas saber la razón por la cual una llamada ha finalizado, la puedes obtener invocando a connection.GetCallEndReason(). El tipo de data retornado por este método es CallEndReason (enumerado) - por favor consultar openh323/include/h323con.h para todos los posibles valores.
OnAnswerCall()
El tercer método virtual que trataremos es MyEndPoint::OnAnswerCall() (archivo ep.cxx, líneas 63-71). Esta invocación es el lugar donde el desarrollador decide si el stack OpenH323 debería aceptar o rechazar una llamada entrante. El método tiene cuatro parámetros. El primer parámetro es una referencia a un objeto H323Connection, mientras que el segundo es un PString indicando el nombre (descripción textual) de la parte que inicia la comunicación (caller). El tercer parámetro es una referencia a un constante sobre el PDU "Q.931/H.225 Setup" que el endpoint local recibió de la parte que inicia la comunicación . El cuarto parámetro es una referencia al PDU Connect que de hecho podría ser envíado si la llamada es aceptada.
OnAnswerCall() no es el único método que es utilizado al decidir acerca de la aceptación o rechazo de una llamada entrante. La clase H323EndPoint tiene otro método virtual , OnIncomingCall() (revisar openh323/include/h323ep.h para ver el prototipo exacto). Cuando un endpoint remoto desea establece una conexión H.323, primero envía un PDU Setup . El stack OpenH323 local responde con un PDU Call Proceeding y luego invoca a OnIncomingCall(). Si OnIncomingCall() retorna TRUE, el endpoint local envía el PDU Alerting y luego llama a OnAnswerCall(). Entonces, si se diera el caso de que en el momento en que OnIncomingCall() es invocado tu aplicación no puede aceptar la llamada (p.e. tu aplicación no tiene recursos suficientes), tu podrías rechazar la llamada retornando FALSE. De otra manera, no necesitas solapar OnIncomingCall() - por defecto, retorna TRUE - y dejar el delegando el manejo a OnAnswerCall().
Nuestra aplicación tutorial siempre acepta llamadas entrantes, entonces MyEndPoint::OnAnswerCall() simplemente retorna H323Connection::AnswerCallNow. Si deseamos rechazar una llamada, el valor retornado sería H323Connection::AnswerCallDenied. Como se mencionó al principio de este capítulo, si no puedes decidir inmediatamente la aceptación de una llamada (que podría ser el caso de la mayoría de los programas del mundo real), no sería correcto esperar dentro de OnAnswerCall(). En su lugar , OnAnswerCall() debería simplemente notificar a la aplicación acerca de la nueva llamada entrante y luego retornar H323Connection::AnswerCallPending. El stack OpenH323 enviará el PDU Alerting y la aplicación podrá aceptar o rechazar, mas tarde, la llamada utilizando el método AnsweringCall() en la clase H323Connection.
OpenAudioChannel()
MyEndPoint::OpenAudioChannel (archivo ep.cxx, líneas143-161) es otro método virtual que es importante para la funcionalidad de nuestra aplicación tutorial. Esta función permite que la aplicación defina la fuente y/o destino del audio para otros elementos además de la tarjeta de sonido (por defecto). Tres de los cuatro parámetros de OpenAudioChannel() son importantes para nuestra aplicación: H323Connection & connection es una referencia a la conexión para la cual creamos el canal del audio. BOOL isEncoding nos indica cual es la dirección de los datos de audio. Si isEncoding es TRUE, el canal de audio actuará como una fuente de datos de audio para ser codificado y transmitido a un endpoint remoto, de otra manera, el canal de audio será utilizado como un destino para el audio entrante. El tercer parámetro imnportante es H323AudioCodec &codec que nos brinda acceso al objeto codec que puede codificar el audio saliente o bien decodificar el audio entrante..
Ahora describiremos el código dentro de OpenAudioChannel(). La línea comentada 148 es un ejemplo de como deshabiltar la detección de silencios. La línea 149 verifica el valor de isEncoding. Si es TRUE (p.e. se trata de audio saliente), creamos un objeto instancia de WavChannel. El constructor de la clase WavChannel necesita dos parámetros- el primero es el nombre de un archivo WAV mientras que el segundo es una referencia al objeto H323Connection que utilizará el objeto channel (revisa la Sección 6 para una discusión detallada de los canales de audio). Habiendo creado el objeto channel, lo asignamos al codec (línea 152). El valor true definido como el segundo parámetro de AttachChannel() significa que el codec debería liberar el objeto channel cuando éste ya no se necesite. AttachChannel() retorna un valor booleano indicando si es que la operación fué exitosa y nosotros simplemente utilizamos este valor como el valor de retorno de OpenAudioChannel(). El código, para el caso en que isEncoding es false (audio entrante) es casi idéntico. La única diferencia es que creamos una instancia de la clase NullChannel en lugar de WavChannel.
OnStartLogicalChannel()
El metodo virtual del endpoint OnStartLogicalChannel() es invocado cuando el stack OpenH323 ha inicializado satisfactoriamente un hilo (thread), responsable, ya sea de la transmisión o recepción de datos de audio o video. Nuestra aplicación tutorial solo considera audio, por tanto tendremos dos hilos de canal lógico para cada llamada.. Utilizaremos el método virtual MyEndPoint::OnStartLogicalChannel() (archivo ep.cxx, lines 166-185) para generar información de tracing acerca del codec (capacidades) utilizada para los datos entrantes o saliente. El método es llamado una vez para cada canal, entonces, si la salida del tracing para una llamada específica contiene una o cero líneas con el texto "Started logical channel..." (canal lógico iniciado), sabremos que debemos considerar la revisión de problemas de negociación H.323, p.e.. errores en los codecs o reserva insuficiente de ancho de banda.
Siguiente: 6 Canales de Audio