GUI en ruso, o terminal VKS hágalo usted mismo

Experiencia en el desarrollo de una GUI C ++ para el sistema de videoconferencia ruso (VKS). La síntesis de la tecnología moderna y los requisitos de certificación. El principal "rastrillo" del desarrollo y las formas de sortearlos. ¿Qué tienen en común la GUI y el ballet ruso?

Lo primero que ve el usuario del sistema de videoconferencia es la interfaz. Y en la mayoría de los casos, es por su apariencia y funcionalidad que juzgan el sistema. Una interfaz incómoda o en expansión no permitirá evaluar ni el alto rendimiento del sistema ni la amplia funcionalidad. Técnicamente, un sistema "hermoso" debe estar envuelto en una carcasa de trabajo atractiva y estable. Por lo tanto, al comienzo del desarrollo del sistema nacional VKS, este momento se tuvo en cuenta de inmediato.

imagen

¿Quién será el usuario del sistema de videoconferencia ruso?


Desde la primavera de 2020, la respuesta a la pregunta de la conveniencia de desarrollar un sistema VKS completo se ha vuelto obvio. Los funcionarios, las empresas comerciales, los hospitales y las escuelas necesitan medios modernos de comunicación con un cierto nivel de productividad y seguridad. Puede hablar en Zoom, pero ¿vale la pena usarlo para negociaciones comerciales serias o una reunión operativa de gerentes?

Para diversas tareas de las empresas rusas, se hizo necesario crear un sistema de videoconferencia nacional. Además, un sistema que consiste no solo en un componente de software, sino también en un hardware completo. Entre los proveedores de fama mundial, al menos 5 empresas ofrecen sistemas de videoconferencia multifuncionales. Pero en Rusia, el concepto de sustitución de importaciones está comenzando a funcionar gradualmente. Además, los problemas de seguridad para muchos se han vuelto más importantes que el país de origen del producto, y el precio a los tipos de cambio actuales no está en el último lugar. Y la "belleza" de la interfaz resultó ser bastante realista de desarrollar desde cero.

GUI al inicio


Los requisitos principales para las interfaces modernas son la velocidad de implementación, la apariencia actualizada y la usabilidad completa. Por lo tanto, la primera tarea de los desarrolladores de la interfaz gráfica de usuario (GUI) fue una definición clara de la funcionalidad del software para la videoconferencia.

Desde el punto de vista de la GUI, se formularon los siguientes requisitos:

  • Hacer llamadas salientes de video / audio;
  • Aceptar / rechazar llamadas entrantes;
  • Contestar automáticamente una llamada entrante en un intervalo de tiempo personalizado;
  • Cambiar entre dos dispositivos de audio (auriculares / altavoz) durante y fuera de la llamada;
  • Enciende / apaga el micrófono y la cámara durante y fuera de la llamada;
  • Marcación DTMF durante una llamada;
  • Conferencia reunida en la terminal;
  • Gestión de cámaras PTZ, guardar preajustes PTZ y su aplicación;
  • La capacidad de emitir video a varias ventanas diferentes;
  • Control de mouse, teclado, control remoto;
  • La capacidad de controlar remotamente el terminal desde la interfaz web.

Esta lista de funciones le permite resolver el problema de desarrollar una interfaz de varias maneras. Además, la elección de un tipo específico de implementación se vio afectada por limitaciones del tipo de lenguajes de programación (por ejemplo, Java no era categóricamente adecuado por razones de certificación, CSS / HTML, según la funcionalidad), especialización de desarrolladores y tiempo. Colectivamente, la elección se hizo a favor de C ++ y el uso del marco Qt5, ya que, por ejemplo, la tecnología QML más moderna no permite renderizar video usando un contexto arbitrario de OpenGL, y esto era necesario de acuerdo con el ToR para terminales VKS.

imagen

Rápida y eficientemente


El primer prototipo de GUI se creó para el softphone de Qt y utilizó muchas bibliotecas de código abierto. Por ejemplo, para el protocolo SIP, se utilizaron las bibliotecas eXosip / oSIP, para codificar / decodificar video y audio - ffmpeg, para trabajar con dispositivos de audio - PortAudio. Este softphone funcionaba bajo Linux, Windows, MacOS y era un demostrador de tecnología, y no un dispositivo real.

Más tarde, un softphone abstracto se transformó en un proyecto de videoteléfono real, y la primera versión del software para él debería haberse creado 2 meses después del inicio. Para resolver este problema en tan poco tiempo, el software del teléfono se dividió en módulos y se distribuyó entre varios grupos de desarrolladores de acuerdo con las competencias. Tal organización del proceso ayudó a desarrollar rápida y eficientemente el proyecto de videoteléfono.

Núcleo y frente


Para la unificación y la posibilidad de utilizar los desarrollos de GUI existentes en otros dispositivos de un proyecto existente, la base de código común está en un módulo separado: el backend de GUI o el módulo central de GUI. Y directamente las representaciones, que son diferentes para diferentes dispositivos, se implementan en módulos frontales GUI separados.

Esta arquitectura de los módulos GUI resultó ser ventajosa y condujo al resultado deseado: el desarrollo de interfaces para los nuevos componentes del sistema VKS se ha vuelto relativamente rápido y de alta calidad. Después de todo, ahora las interfaces para terminales VKS no necesitaban reescribirse desde cero.

Tormento y Victoria


En el camino hacia la creación de cualquier software, naturalmente, hay dificultades y problemas. Crear una GUI para la videoconferencia no fue la excepción. Independientemente del propósito específico del sistema, se pueden repetir en cualquier comando. Las dificultades y victorias en el camino del desarrollo son interesantes para los colegas, y tal vez generarán soluciones efectivas sin nuestro "rastrillo".

Consistencia para siempre


Históricamente, el primer problema interesante que surgió durante el desarrollo de la GUI para varios tipos de terminales VKS fue el problema de consistencia, es decir, el estado coordinado de todos los módulos. Durante la operación, la GUI interactúa con varios otros módulos: un módulo para interactuar con hardware, un subsistema de administración de llamadas, un módulo de procesamiento de medios (MCU) y un subsistema de interacción del usuario.

imagen

Inicialmente, la GUI funcionaba con todos estos módulos como independientes, es decir, podía enviar solicitudes a 2 módulos diferentes al mismo tiempo. Esto resultó ser incorrecto y a veces condujo a problemas, ya que estos módulos en sí mismos no eran independientes e interactuaban activamente entre sí. La solución al problema fue la creación de un esquema de trabajo especial, que aseguró la ejecución estrictamente secuencial de las solicitudes dentro de todos los módulos.

Se agregaron 2 dificultades: en primer lugar, algunas (pero no todas) las solicitudes requieren una respuesta, anticipando que el terminal, de hecho, se encuentra en un estado inconsistente, por lo que no se pueden realizar otras solicitudes. Sin embargo, bloquear la interfaz de usuario mientras espera respuestas también es indeseable. En segundo lugar, las respuestas a las solicitudes GUI de los módulos, así como las solicitudes de los módulos a la GUI, vienen en sus propios hilos, diferentes de la GUI, pero la GUI debe implementar todos los cambios de estado en su hilo (para algunas acciones Qt requiere esto, pero en en algunos casos, esto evita dificultades innecesarias para garantizar la sincronización de subprocesos).

La solución se encontró y consistió en dos partes. Primero, todas las solicitudes / respuestas de otros módulos se redirigieron a la secuencia de la GUI usando el mecanismo de ranura de señal Qt junto con QueuedConnection, es decir, usando el bucle de evento QApplication global. Luego, para garantizar el procesamiento consistente de todas las solicitudes, se desarrolló un sistema de Transiciones con su propia cola y ciclo de procesamiento (TransitionLoop).

Por lo tanto, cuando el usuario presiona algún botón de acción en la GUI (por ejemplo, el botón de llamada), se crea una Transición correspondiente, que se coloca en la cola de transición. Después de eso, se genera una señal para el ciclo de procesamiento de transición. TransitionLoop, al recibir una señal, mira para ver si hay alguna transición en progreso ahora. Si lo hay, entonces la espera para la finalización de la transición actual continúa; si no, la próxima transición se recupera de la cola de transición y se inicia. Cuando se recibe una respuesta de otro módulo TransitionLoop utilizando la misma señal, se notifica la finalización de la transición actual y TransitionLoop puede comenzar la próxima transición desde la cola.

Lo importante aquí es que todo el procesamiento de transición se realiza en un hilo GUI. Esto se garantiza mediante el uso del mecanismo de ranura de señal Qt en la variante QueuedConnection, en la que se genera un evento para cada señal y se coloca en el EventLoop principal de la aplicación.

Representación de OpenGL en hardware de baja potencia


Otra dificultad con la que tuvimos que lidiar fue el problema de renderizar video. Qt proporciona para OpenGL renderizar una clase especial QOpenGLWidget y clases auxiliares relacionadas, que originalmente se utilizó para renderizar video. Los datos para la representación (cuadros de video decodificados) son proporcionados por el módulo de procesamiento de medios (MCU), que, entre otras cosas, implementa la decodificación de hardware del flujo de video (en la GPU). En los procesadores de baja potencia, se encontró "ralentización" de la reproducción de video FullHD. La solución directa era reemplazar el procesador, pero esto requeriría un procesamiento serio de los componentes ya terminados del sistema de videoconferencia y aumentaría el costo de los propios dispositivos. Por lo tanto, todo el proceso de renderizado se analizó cuidadosamente para encontrar formas más hermosas de resolver el problema.

Con la representación estándar de OpenGL y la decodificación de hardware, ocurre lo siguiente: los datos con video codificado provienen de la red, se almacenan en la RAM, luego estos datos de la RAM se transfieren a la memoria de video en la GPU, donde se decodifican. Luego, los datos decodificados que tienen un volumen significativamente mayor que los datos codificados se transfieren nuevamente a la RAM. A continuación, entra en juego un código de representación, que transfiere estos datos desde la RAM a la GPU directamente para la representación. Por lo tanto, se bombean grandes cantidades de datos de un lado a otro a través del bus de memoria, y el bus simplemente no puede hacer esto.

En las versiones modernas de OpenGL, hay extensiones especiales que le permiten especificar para representar datos que ya están en la memoria de la GPU, y no datos en la RAM principal, como de costumbre. Este mecanismo excluyó el movimiento de datos de marcos decodificados por hardware desde la GPU a la RAM, y luego de regreso. Por lo tanto, el problema de renderizar en procesadores de baja potencia estaba casi resuelto.

imagen

Otro problema importante fueron los contextos OpenGL soportados en Qt. No le permiten usar la extensión necesaria de OpenGL, es decir, no puede usar QOpenGLWidget con esta opción. La solución fue usar un QWidget regular, pero apagó la tubería de renderización Qt. Tal oportunidad existe en Qt. Sin embargo, surgió una pregunta aquí, porque en esta opción la GUI es totalmente responsable de todo el procesamiento en el área de este widget, Qt no nos ayuda. Esto es normal para mostrar video, pero para usar widgets en la parte superior del video, no se pueden usar herramientas Qt normales, ya que, por ejemplo, se debe mostrar un menú emergente adicional en la parte superior del video.

Este problema se resolvió de la siguiente manera: a partir del widget existente, se obtiene su imagen (QWidget tiene un método grab () para esto), la imagen resultante se convierte en textura OpenGL y esta última se representa en la parte superior del video utilizando herramientas OpenGL. Al agregar el entorno apropiado, se implementó un mecanismo universal que se puede utilizar para mostrar cualquier widget estándar en la parte superior del video de una manera no estándar.

Quioscos y widgets


La tarea de administrar pantallas y distribuir fragmentos de la interfaz de usuario en el modo "kiosco" no fue fácil. El terminal VKS puede funcionar en 2 modos: modo de ventana, es decir, como cualquier otra aplicación de ventana en el entorno de escritorio del sistema operativo, y "modo de quiosco" (es decir, el sistema operativo ejecuta solo una aplicación con una interfaz gráfica - VKST - y no hay entorno escritorio).

En modo ventana, todo es relativamente simple: la ventana es controlada por el administrador de ventanas del entorno de escritorio, la aplicación crea una segunda ventana si es necesario y el usuario distribuye las ventanas en las pantallas según lo necesite. Pero en el modo "kiosco", todo es mucho más complicado, ya que el sistema no tiene un administrador de ventanas y solo puede haber una ventana, y el usuario no tiene la capacidad de moverla. Por lo tanto, apareció la tarea de detectar automáticamente un evento, por ejemplo, conectar / desconectar una pantalla. Cuando se produjo este evento, fue necesario configurar automáticamente las pantallas y colocar correctamente fragmentos de la interfaz de usuario en ellas.

imagen

La respuesta provino de la biblioteca del sistema LINUX Xrandr OS, que es responsable de trabajar con pantallas. Hay muy poca documentación en Internet, por lo que la implementación se llevó a cabo utilizando ejemplos de Internet, incluso de Habr. Además, era necesario crear un algoritmo para distribuir fragmentos de interfaz entre pantallas, así como integrar dos ventanas diferentes en una sola. Este último se implementó de la siguiente manera: lo que son ventanas en modo ventana, en modo "quiosco" son widgets dentro de una ventana grande, que se extiende sobre 2 pantallas (si hay 2 de ellas). En este caso, es necesario configurar las posiciones de las pantallas para que se cree un espacio virtual continuo (esto se hace utilizando la biblioteca XRandr), y luego establecer la geometría de los widgets internos dentro de una sola ventana global para quepara que todos se muestren.

Creamos ruso


Toda la forma de crear el sistema de videoconferencia ruso consistió y consta de muchas etapas, y la GUI es solo la punta del iceberg. Lo más notable y no lo más difícil. Sin embargo, la complejidad de la solución, la combinación de software y hardware y componentes de software, y el deseo de crear un sistema técnico y estéticamente "hermoso" creó muchas dificultades en el camino. Las nuevas tareas dieron lugar a soluciones no estándar y ayudaron a crear un producto que no se avergüenza de mostrar no solo en Rusia sino también en el extranjero.

Los desarrollos rusos han demostrado durante mucho tiempo su rendimiento, y en un hermoso caparazón y competitividad. Nuestros trucos de vida serán útiles para todos los que estén seriamente involucrados en el desarrollo de GUI, y esperamos que ayuden a otros desarrolladores a acelerar y simplificar el proceso de creación de shells modernos para nuevos productos de software rusos. Creemos que las decisiones rusas serán valoradas en el mundo no menos que el ballet ruso o el caviar negro.

All Articles