GUI em russo ou terminal VKS faça você mesmo

Experiência no desenvolvimento de uma GUI C ++ para o sistema de videoconferência russo (VKS). A síntese de tecnologia moderna e requisitos de certificação. O principal "ancinho" do desenvolvimento e maneiras de contorná-los. O que a GUI e o balé russo têm em comum.

A primeira coisa que o usuário do sistema de videoconferência vê é a interface. E na maioria dos casos, é pela aparência e funcionalidade que eles julgam o sistema. Uma interface inconveniente ou extensa não permitirá avaliar o alto desempenho do sistema ou a ampla funcionalidade. Tecnicamente, um sistema "bonito" deve ser envolto em uma concha de trabalho atraente e estável. Portanto, no início do desenvolvimento do sistema VKS doméstico, esse momento foi imediatamente levado em consideração.

imagem

Quem será o usuário do sistema de videoconferência russo?


Desde a primavera de 2020, a resposta à questão da conveniência de desenvolver um sistema VKS completo tornou-se óbvia. Funcionários, empresas comerciais, hospitais e escolas precisam de meios modernos de comunicação com um certo nível de produtividade e segurança. Você pode falar no Zoom, mas vale a pena usá-lo para negociações comerciais sérias ou para uma reunião operacional de gerentes?

Para várias tarefas de empresas russas, tornou-se necessário criar um sistema de videoconferência doméstico. Além disso, um sistema que consiste não apenas em um componente de software, mas também em um hardware completo. Entre os fornecedores mundialmente famosos, pelo menos 5 empresas oferecem sistemas de videoconferência multifuncionais. Mas na Rússia, o conceito de substituição de importações está gradualmente começando a funcionar. Além disso, muitos problemas de segurança se tornaram mais importantes que o país de origem do produto e o preço nas taxas de câmbio atuais não está em último lugar. E a "beleza" da interface acabou sendo bastante realista para se desenvolver a partir do zero.

GUI no início


Os principais requisitos para interfaces modernas são velocidade de implementação, aparência atualizada e usabilidade total. Assim, a primeira tarefa dos desenvolvedores da interface gráfica do usuário (GUI) foi uma definição clara da funcionalidade do software para a videoconferência.

Do ponto de vista da GUI, foram formulados os seguintes requisitos:

  • Fazer chamadas de vídeo / áudio;
  • Aceitar / rejeitar chamadas recebidas;
  • Atender automaticamente uma chamada em um intervalo de tempo personalizado;
  • Alterne entre dois dispositivos de áudio (fone de ouvido / viva-voz) durante e fora da chamada;
  • Ligue / desligue o microfone e a câmera durante e fora da chamada;
  • Discagem DTMF durante uma chamada;
  • Reunião de conferência no terminal;
  • Gerenciamento de câmeras PTZ, salvando presets de PTZ e sua aplicação;
  • A capacidade de produzir vídeo para várias janelas diferentes;
  • Controle mouse, teclado, controle remoto;
  • A capacidade de controlar remotamente o terminal a partir da interface da Web.

Esta lista de funções permite resolver o problema de desenvolver uma interface de várias maneiras. Além disso, a escolha de um tipo específico de implementação foi afetada por limitações do tipo de linguagens de programação (por exemplo, Java não era categoricamente adequado por razões de certificação, CSS / HTML - de acordo com a funcionalidade), especialização de desenvolvedores e tempo. Coletivamente, a escolha foi feita em favor do C ++ e o uso da estrutura Qt5, pois, por exemplo, a tecnologia QML mais moderna não permite renderizar vídeo usando um contexto arbitrário do OpenGL, e isso era necessário de acordo com os terminais ToR for VKS.

imagem

Rapidamente e eficientemente


O primeiro protótipo da GUI foi criado para o softphone da Qt e usou muitas bibliotecas de código aberto. Por exemplo, para o protocolo SIP, foram usadas bibliotecas eXosip / oSIP, para codificação / decodificação de vídeo e áudio - ffmpeg, para trabalhar com dispositivos de áudio - PortAudio. Este softphone funcionava no Linux, Windows, MacOS e era um demonstrador de tecnologia, e não um dispositivo real.

Mais tarde, um softphone abstrato foi transformado em um projeto de videofone real, e a primeira versão do software para ele deveria ter sido criada 2 meses após o início. Para resolver esse problema em tão pouco tempo, o software do telefone foi dividido em módulos e distribuído entre vários grupos de desenvolvedores de acordo com as competências. Essa organização do processo ajudou a desenvolver rápida e eficientemente o projeto de videofone.

Núcleo e frente


Para a unificação e a possibilidade de usar desenvolvimentos existentes da GUI em outros dispositivos de um projeto existente, a base de código comum está em um módulo separado - o back-end da GUI ou o módulo principal da GUI. E representações diretamente, diferentes para diferentes dispositivos, são implementadas em módulos frontais da GUI separados.

Essa arquitetura dos módulos da GUI mostrou-se vantajosa e levou ao resultado desejado: o desenvolvimento de interfaces para os novos componentes do sistema VKS se tornou relativamente rápido e de alta qualidade. Afinal, agora as interfaces para os terminais VKS não precisavam ser reescritas do zero.

Tormento e vitória


No caminho para criar qualquer software, naturalmente, existem dificuldades e problemas. Criar uma GUI para a videoconferência não foi exceção. Independentemente da finalidade específica do sistema, eles podem ser repetidos em qualquer comando. Dificuldades e vitórias no caminho do desenvolvimento são interessantes para os colegas, e talvez elas busquem soluções eficazes sem o nosso “ancinho”.

Sempre consistência


Historicamente, o primeiro problema interessante que surgiu durante o desenvolvimento da GUI para vários tipos de terminais VKS foi o problema de consistência, ou seja, o estado coordenado de todos os módulos. Durante a operação, a GUI interage com vários outros módulos: um módulo para interagir com o hardware, um subsistema de gerenciamento de chamadas, um MCU (Media Processing Module) e um subsistema de interação do usuário.

imagem

Inicialmente, a GUI trabalhava com todos esses módulos como independentes, ou seja, podia enviar solicitações para 2 módulos diferentes ao mesmo tempo. Isso acabou errado e algumas vezes levou a problemas, já que esses módulos não eram independentes e interagiram ativamente entre si. A solução para o problema foi a criação de um esquema de trabalho especial, que garantiu a execução estritamente seqüencial de solicitações em todos os módulos.

Havia duas dificuldades em adicionar: primeiro, alguns (mas não todos) pedidos requerem uma resposta, antecipando que o terminal, de fato, esteja em um estado inconsistente, portanto outros pedidos não podem ser executados. No entanto, bloquear a interface do usuário enquanto aguarda respostas também é indesejável. Em segundo lugar, as respostas às solicitações da GUI dos módulos, bem como às solicitações dos módulos da GUI, vêm em seus próprios encadeamentos, diferentes da GUI, mas a GUI deve implementar todas as alterações de estado em seu encadeamento (para algumas ações o Qt exige isso, mas em em alguns casos, isso evita dificuldades desnecessárias para garantir a sincronização do encadeamento).

A solução foi encontrada e consistia em duas partes. Primeiro, todas as solicitações / respostas de outros módulos foram redirecionadas para o fluxo da GUI usando o mecanismo de slot de sinal Qt em conjunto com QueuedConnection, ou seja, usando o loop de eventos QApplication global. Em seguida, para garantir o processamento consistente de todas as solicitações, um sistema Transitions foi desenvolvido com sua própria fila e ciclo de processamento (TransitionLoop).

Portanto, quando o usuário pressiona algum botão de ação na GUI (por exemplo, o botão de chamada), uma transição correspondente é criada, que é colocada na fila de transição. Depois disso, um sinal é gerado para o ciclo de processamento de transição. O TransitionLoop, ao receber um sinal, procura ver se há alguma transição em andamento agora. Se houver, a espera pela conclusão da transição atual continua; caso contrário, a próxima transição é recuperada da fila de transição e iniciada. Quando uma resposta é recebida de outro módulo TransitionLoop usando o mesmo sinal, a conclusão da transição atual é notificada e o TransitionLoop pode iniciar a próxima transição da fila.

O importante aqui é que todo o processamento de transição seja feito em um encadeamento da GUI. Isso é garantido pelo uso do mecanismo de slot de sinal Qt na variante QueuedConnection, na qual um evento é gerado para cada sinal e colocado no EventLoop principal do aplicativo.

Renderização OpenGL em hardware de baixa potência


Outra dificuldade que tivemos que enfrentar foi o problema de renderização de vídeo. O Qt fornece ao OpenGL a renderização de uma classe QOpenGLWidget especial e de classes auxiliares relacionadas, que foram originalmente usadas para renderizar vídeo. Os dados para renderização (quadros de vídeo decodificados) são fornecidos pelo módulo de processamento de mídia (MCU), que, entre outras coisas, implementa a decodificação de hardware do fluxo de vídeo (na GPU). Em processadores de baixa potência, “diminuindo a velocidade” foi encontrada a renderização do vídeo em FullHD. A solução direta foi substituir o processador, mas isso exigiria um processamento sério dos componentes já finalizados do sistema de videoconferência e aumentaria o custo dos próprios dispositivos. Portanto, todo o processo de renderização foi cuidadosamente analisado para encontrar maneiras mais bonitas de resolver o problema.

Com a renderização OpenGL padrão e a decodificação de hardware, ocorre o seguinte: os dados com vídeo codificado vêm da rede, são armazenados na RAM e, em seguida, esses dados da RAM são transferidos para a memória de vídeo na GPU, onde são decodificados. Em seguida, os dados decodificados com um volume significativamente maior que os dados codificados são transferidos novamente para a RAM. Em seguida, um código de renderização entra em ação, que transfere esses dados da RAM de volta para a GPU diretamente para renderização. Assim, grandes quantidades de dados são bombeadas para frente e para trás através do barramento de memória, e o barramento simplesmente não pode fazer isso.

Nas versões modernas do OpenGL, existem extensões especiais que permitem especificar dados de renderização que já estão na memória da GPU e não dados na RAM principal, como de costume. Esse mecanismo excluiu o movimento dos dados dos quadros decodificados por hardware da GPU para a RAM e depois para trás. Assim, o problema de renderização em processadores de baixa potência foi quase resolvido.

imagem

Outro grande problema foram os contextos OpenGL suportados no Qt. Eles não permitem o uso da extensão OpenGL necessária, ou seja, você não pode usar o QOpenGLWidget com esta opção. A solução foi usar um QWidget regular, mas desativou o pipeline de renderização Qt. Essa oportunidade existe no Qt. No entanto, surgiu uma pergunta aqui, porque nesta opção a GUI é totalmente responsável por toda a renderização na área deste widget, o Qt não nos ajuda. Isso é normal para a exibição de vídeo, mas para o uso de widgets em cima de vídeo, as ferramentas regulares do Qt não podem ser usadas, pois, por exemplo, um menu pop-up adicional deve ser exibido na parte superior do vídeo.

Esse problema foi resolvido da seguinte forma: no widget existente, sua imagem é obtida (o QWidget possui um método grab () para isso), a imagem resultante é convertida em textura OpenGL e a última é renderizada na parte superior do vídeo usando as ferramentas OpenGL. Ao adicionar o ambiente apropriado, foi implementado um mecanismo universal que pode ser usado para exibir widgets padrão na parte superior do vídeo de maneira não padronizada.

Quiosques e widgets


A tarefa de gerenciar exibições e distribuir fragmentos da interface do usuário no modo “quiosque” não foi fácil. O terminal VKS pode operar em 2 modos - modo de janela, ou seja, como qualquer outro aplicativo de janela no ambiente de desktop do sistema operacional e "modo de quiosque" (ou seja, o sistema operacional executa apenas um aplicativo com uma interface gráfica - VKST - e não há ambiente) Área de Trabalho).

No modo janela, tudo é relativamente simples: a janela é controlada pelo gerenciador de janelas do ambiente da área de trabalho, o aplicativo cria uma segunda janela, se necessário, e o usuário distribui as janelas nos monitores conforme necessário. Porém, no modo “quiosque”, tudo é muito mais complicado, pois o sistema não possui um gerenciador de janelas e pode haver apenas uma janela, e o usuário não pode movê-lo. Portanto, a tarefa de detectar automaticamente um evento, por exemplo, conectar / desconectar um monitor, apareceu. Quando esse evento ocorreu, era necessário configurar automaticamente os monitores e colocar corretamente fragmentos da interface do usuário neles.

imagem

A resposta veio da biblioteca do sistema LINUX Xrandr OS, responsável por trabalhar com monitores. Como há muito pouca documentação na Internet, a implementação foi realizada usando exemplos da Internet, inclusive da Habr. Além disso, era necessário criar um algoritmo para distribuir fragmentos de interface entre os monitores, além de integrar duas janelas diferentes em uma única. O último foi implementado da seguinte forma: o que são janelas no modo janela, no modo “quiosque” são widgets dentro de uma janela grande, que se estende por 2 telas (se houver 2). Nesse caso, é necessário configurar as posições dos monitores para criar um espaço virtual contínuo (isso é feito usando a biblioteca XRandr) e, em seguida, definir a geometria dos widgets internos dentro de uma única janela global para quepara que todos apareçam na tela.

Criamos russo


Todo o modo de criar o sistema de videoconferência russo consistia e consiste em muitos estágios, e a GUI é apenas a ponta do iceberg. O mais perceptível e não o mais difícil. No entanto, a complexidade da solução, a combinação de software e hardware e componentes de software e o desejo de criar um sistema técnico e esteticamente "bonito" criaram muitas dificuldades ao longo do caminho. Novas tarefas deram origem a soluções não padronizadas e ajudaram a criar um produto que não tem vergonha de mostrar não apenas na Rússia, mas também no exterior.

Os desenvolvimentos russos há muito provam seu desempenho, e com uma bela concha e competitividade. Nossos hacks de vida serão úteis para qualquer pessoa que esteja seriamente envolvida no desenvolvimento de GUI, e esperamos que eles ajudem outros desenvolvedores a acelerar e simplificar o processo de criação de shells modernos para novos produtos de software russos. Acreditamos que as decisões russas serão valorizadas no mundo não menos que o balé russo ou o caviar preto.

All Articles