Implementación de un Servidor RTSP Bajo Demanda para H.264 con live555

El archivo testOnDemandRTSPServer.cpp, parte de la librería live555, sirve como un ejemplo conciso de un servidor de medios bajo demanda, capaz de transmitir una variedad de formatos multimedia. Este documento se enfoca en el análisis de la cnofiguración y el flujo de trabajo para la transmisión de archivos de vídeo en formato H.264.

Para facilitar la comprensión, las secciones del código fuente que gestionan otros tipos de archivos multimedia pueden ser excluidas temporalmente utilizando directivas de preprocesador (#if 0 ... #endif), permitiendo una visión más limpia y enfocada en el proceso para H.264.

int main(int argc, char** argv) {
  // Inicialización del entorno de ejecución:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

  UserAuthenticationDatabase* authDB = NULL;
#ifdef ACCESS_CONTROL
  // Para implementar el control de acceso de clientes al servidor RTSP:
  authDB = new UserAuthenticationDatabase;
  authDB->addUserRecord("usuario1", "clave1"); // Sustituya con credenciales reales
#endif

  // Creación del servidor RTSP:
  RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
  if (rtspServer == NULL) {
    *env << "Fallo al crear el servidor RTSP: " << env->getResultMsg() << "\n";
    exit(1);
  }

  char const* descriptionString = "Sesión transmitida por \"testOnDemandRTSPServer\"";

  // Configuración de los streams que el servidor RTSP ofrecerá.
  // Cada stream se modela mediante un "ServerMediaSession",
  // que a su vez contiene uno o varios "ServerMediaSubsession"
  // para cada componente de audio/vídeo.

  // Configuración de un stream elemental de vídeo H.264:
  {
    char const* streamName = "h264ESVideoTest"; // Nombre del recurso que el cliente solicitará
    char const* inputFileName = "test.264";    // Ruta al archivo H.264 en el servidor

    // Creación de una sesión multimedia para el stream
    ServerMediaSession* sms = ServerMediaSession::createNew(*env, streamName, streamName, descriptionString);

    // Adición de un sub-stream de vídeo H.264 a la sesión
    sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName, true)); // 'true' indica reutilizar la primera fuente

    // Registro de la sesión multimedia en el servidor RTSP
    rtspServer->addServerMediaSession(sms);

    // Anuncio del stream (función de depuración o informativa)
    announceStream(rtspServer, sms, streamName, inputFileName);
  }

  // Intento de configurar la tunelización RTSP-over-HTTP en puertos comunes:
  if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) {
    *env << "\n(Se utiliza el puerto " << rtspServer->httpServerPortNum() << " para la tunelización RTSP-over-HTTP opcional.)\n";
  } else {
    *env << "\n(La tunelización RTSP-over-HTTP no está disponible.)\n";
  }

  // Inicio del bucle principal de eventos del planificador.
  // Este bucle gestiona las operaciones de red (lectura de sockets)
  // y las tareas de envío de datos multimedia.
  env->taskScheduler().doEventLoop(); // Esta función no retorna

  return 0; // Solo para evitar advertencia del compilador
}

El proceso de inicialización del servidor comienza con la configuración de la base de su funcionamiento:

TaskScheduler* scheduler = BasicTaskScheduler::createNew();
UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

Aquí se instancia un TaskScheduler, cuya función es programar y ejecutar tareas asíncronas, como la gestión de eventos de red en los sockets. Posteriormente, se crea un UsageEnvironment, que actúa como el contexto global para la aplicación. El BasicTaskScheduler, a través de su clase base BasicTaskScheduler0, mantiene una lista doblemente enlazada de manejadores de sockets (HandlerSet), que es crucial para procesar las interacciones de red.

// Estructura interna clave del BasicTaskScheduler para la gestión de sockets:
HandlerSet* fHandlers;            // Lista doblemente enlazada para las tareas de socket
int fLastHandledSocketNum;

La siguiente etapa consiste en la creación del objeto principal del servidor RTSP:

RTSPServer* rtspServer = RTSPServer::createNew(*env, 8554, authDB);
if (rtspServer == NULL) {
  *env << "Fallo al crear el servidor RTSP: " << env->getResultMsg() << "\n";
  exit(1);
}

La función estática createNew() es la encargada de inicializar el servidor RTSP. Este proceso incluye la creación de un socket de escucha en el puerto especificado (8554 en el ejemplo) y el registro de una función para el manejo de nuevas conexiones dentro del planificador de tareas.

Desglosando la función RTSPServer::createNew():

RTSPServer*
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
              UserAuthenticationDatabase* authDatabase,
              unsigned reclamationSeconds) {
  int ourSocket = setUpOurSocket(env, ourPort);    // Crea y configura el socket de escucha
  if (ourSocket == -1) return NULL;
  
  // El constructor de la clase base GenericMediaServer completará el registro de los manejadores.
  return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationSeconds);
}

La función setUpOurSocket() establece el socket que el servidor utilizará para aceptar conexiones entrantes. Después, el control se transfiere al constructor de RTSPServer, el cual, a su vez, invoca al constructor de su clase base, GenericMediaServer.

El constructor de RTSPServer:

RTSPServer::RTSPServer(UsageEnvironment& env,
                int ourSocket, Port ourPort,
                UserAuthenticationDatabase* authDatabase,
                unsigned reclamationSeconds)
  : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
    fHTTPServerSocket(-1), fHTTPServerPort(0),
    fClientConnectionsForHTTPTunneling(NULL),
    fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
    fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
    fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(true) {
}

Este constructor delega la mayor parte de la inicialización del socket de escucha y su registro al constructor de su clase base, GenericMediaServer.

Finalmente, el constructor de GenericMediaServer es donde se establece la conexión entre el socket de escucha y su función de manejo:

GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
              unsigned reclamationSeconds)
  : Medium(env),
    fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
    fClientSessions(HashTable::create(STRING_HASH_KEYS)) {
  ignoreSigPipeOnSocket(fServerSocket); // Para evitar la terminación del servidor si un cliente local es abruptamente cerrado
  
  // Registro de la función de manejo para nuevas conexiones entrantes:
  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}

En este punto, el TaskScheduler (accesible a través de env.taskScheduler()) se configura para monitorear el fServerSocket en segundo plano. Cuando el servidor detecta un intento de conexión, se invoca la función incomingConnectionHandler(). Este mecnaismo es conceptualmente similar al flujo estándar de un servidor basado en sockets: se crea el socket, se enlaza a un puerto (bind), se pone en modo escucha (listen) y las nuevas conexiones se "aceptan", con incomingConnectionHandler encapsulando la lógica de "aceptación".

Una vez que la infraestructura del servidor está establecida, el siguiente paso es definir el contenido multimedia que el servidor ofrecerá. Volviendo a la función main(), se configura la información para el stream de vídeo H.264:

// Definición del stream de vídeo H.264:
{
  char const* streamName = "h264ESVideoTest"; // Identificador lógico del stream
  char const* inputFileName = "test.264";    // Ruta al archivo H.264

  // Creación de una sesión multimedia principal (ServerMediaSession)
  // Esta sesión agrupa lógicamente los sub-streams relacionados (ej. vídeo y audio).
  ServerMediaSession* sms = ServerMediaSession::createNew(*env, streamName, streamName, descriptionString);

  // Adición del sub-stream de vídeo H.264 a la sesión.
  // H264VideoFileServerMediaSubsession es la clase especializada para gestionar archivos H.264.
  sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(*env, inputFileName, true));

  // La sesión multimedia se añade al servidor RTSP, haciéndola disponible para los clientes.
  rtspServer->addServerMediaSession(sms);

  // Función para anunciar el stream (utilidad para depuración o información).
  announceStream(rtspServer, sms, streamName, inputFileName);
}

Este bloque de código asigna un nombre lógico al stream (h264ESVideoTest) y especifica el archivo H.264 que se transmitirá (test.264). Se crea un ServerMediaSession, que actúa como un contenedor para el stream, y se le añade un H264VideoFileServerMediaSubsession. Este último componente es responsable de leer y empaquetar el vídeo H.264 adecuadamente para su transmisión a través de RTSP/RTP. Finalmente, la sesión se registra con el servidor RTSP, lo que hace que el recurso esté listo para ser solicitado por los clientes.

Con toda la configuración completada, el servidor entra en su bucle principal de eventos:

env->taskScheduler().doEventLoop(); // El servidor permanece en espera de conexiones y gestiona eventos

Esta llamada es un bucle infinito que bloquea la ejecución y permite que el TaskScheduler gestione todas las tareas registradas, incluyendo la detección de nuevas conexiones de clientes y el envío de datos de los streams multimedia. El servidor está ahora en funcionamiento, esperando solicitudes de clientes RTSP.

Para probar la funcionalidad del servidor, un cliente compatible con RTSP como VLC Media Player puede conectarse utilizando una URL con el formato rtsp://[dirección_IP_servidor]:8554/h264ESVideoTest.

Etiquetas: live555 rtsp H.264 C++ streaming

Publicado el 7-22 08:18