Saltar para o conteúdo

Reuniões e transcrição

O Control Center consegue gravar uma chamada e devolver-lhe um relatório limpo — um resumo, tarefas e decisões — com reconhecimento de fala e separação de falantes a correr no seu próprio hardware. Pense no Granola, mas autoalojado e ligado ao mesmo espaço de trabalho onde vivem os seus agentes.

Uma reunião é uma sessão gravada e transcrita. Não é a mesma coisa que um evento de calendário: um evento é um compromisso agendado sincronizado a partir do Google, enquanto uma reunião é algo que gravou de facto. Os dois podem estar ligados, uma vez que pode iniciar uma gravação a partir de um evento, mas continuam a ser registos distintos.

Todas as reuniões têm o âmbito do espaço de trabalho, tal como tudo o resto no Control Center. As reuniões gravadas num espaço de trabalho nunca aparecem noutro.

«No dispositivo» é o instinto certo, mas o substantivo errado. O cliente capta o áudio e transmite tramas PCM16 a 16 kHz para a máquina que executa o cc_server. Esse host faz tudo o resto: escreve os WAV por canal em <dataDir>/meetings/<meetingId>/, transcreve num isolate de worker, filtra o eco e faz a diarização a posteriori. Num computador autónomo, esse host é a mesma máquina. Apontado a um servidor remoto, já não é — por isso «nada sai do meu portátil» só é verdade quando o servidor é o seu portátil.

Há mais uma exceção, e é a importante.

Tudo antes do resumo — captura, transcrição, cancelamento de eco, diarização e correspondência de impressões de voz — corre no servidor sem qualquer chamada de rede.

Os modelos de embedding, diarização e fala (ASR) são instalados à força no primeiro arranque (ignorado numa demonstração), pelo que a separação de falantes e a gravação existem sem visitar as definições. O ASR continua a ser selecionável — ocupa várias centenas de megabytes e há quatro compilações no seletor. A fala ainda precisa de um reinício após essa primeira transferência, porque o transcritor resolve o seu modelo antes de a transferência poder terminar.

As quatro entradas do seletor, que diferem em tamanho, cobertura de idiomas e precisão:

  • Parakeet TDT 0.6B v3 — multilingue, a predefinição recomendada (~600 MB)
  • Parakeet TDT 0.6B v2 — inglês (~480 MB)
  • Whisper large-v3-turbo — multilingue (~626 MB)
  • Whisper base.en — apenas inglês, a pequena predefinição histórica (~198 MB)

Os modelos são os únicos artefactos que o servidor obtém em tempo de execução; todas as bibliotecas nativas vêm incluídas no pacote.

Uma reunião grava dois canais de áudio em simultâneo:

  • O utilizador («me»): o seu microfone.
  • Os outros («them»): a saída de áudio da reunião, captada por um loopback sem controladores — taps de processo/dispositivo do Core Audio no macOS, loopback WASAPI no Windows e um monitor PipeWire / PulseAudio no Linux.

Como os dois canais são captados separadamente, a transcrição fica atribuída por falante desde o início: as suas palavras são marcadas com me e todos os que estão na chamada são marcados com them.

A sua própria voz passa da saída do sistema de volta para o microfone e, de outro modo, seria transcrita duas vezes. Dois filtros impedem isso. O AEC3 ao nível do sinal corre em todas as gravações em modo remoto e é obrigatório — se a biblioteca AEC nativa não conseguir carregar, a gravação recusa-se a iniciar em vez de transmitir silenciosamente um microfone poluído pelo eco. (O modo presencial não tem referência do lado remoto contra a qual cancelar, pelo que aí o AEC é uma passagem direta.) Além disso, um filtro de eco ao nível do texto do lado do host corre em todas as plataformas.

Todo o processamento de microfone ao nível do sistema operativo está deliberadamente desativado. Ativar o cancelamento de eco ou o ganho automático do próprio sistema no macOS muda o microfone para Voice Processing I/O, o que nesta compilação matava tanto o microfone como o tap de áudio do sistema.

Enquanto grava, o áudio é descodificado em janelas deslizantes — cortadas numa breve cauda de silêncio ou ao atingir um comprimento máximo de janela — fora da thread da interface. Cada janela torna-se um segmento de transcrição marcado por falante, com deslocamentos em milissegundos.

As janelas que nunca ultrapassaram um limite RMS são ignoradas sem serem sequer descodificadas, porque um modelo de fala interpreta o silêncio como tokens de não-fala alucinados e a descodificação desperdiça CPU. A consequência prática: um falante consistentemente muito baixo não produz transcrição. Se alguém aparecer sem nada, verifique o nível de entrada antes de culpar o modelo.

Depois de parar, a diarização corre offline sobre a gravação e divide o canal remoto em falantes individuais (Person 1, Person 2, …) que pode renomear. O número de falantes é inferido a partir de um limiar de agrupamento, não de uma contagem que defina — por isso é um artefacto do limiar e um falante baixo ou sobreposto pode ser fundido com um vizinho. A transcrição é apresentada como linhas [mm:ss] SPEAKER: text.

Renomear um falante faz mais do que etiquetar uma transcrição. O Control Center pode guardar a impressão de voz do falante como um perfil de voz, para o reconhecer automaticamente em reuniões futuras. Quando dá um nome a um falante diarizado, é-lhe perguntado se quer guardar a impressão de voz; se guardar, esta é integrada num centroide acumulado para esse nome e comparada com novos falantes por semelhança de cosseno, para que a mesma pessoa apareça com o seu nome da próxima vez sem ter de a reetiquetar.

Os perfis de voz têm o âmbito do espaço de trabalho e nunca atravessam a fronteira. Faça a gestão deles em Definições → Espaço de trabalho → Reuniões. Renomear um falante que já tinha sido guardado anula o registo do nome antigo e oferece-se para registar o novo; eliminar um perfil remove a impressão de voz guardada, mas deixa intactos quaisquer nomes já aplicados a reuniões anteriores.

Quando uma gravação para, o servidor publica um MeetingRecordingStopped evento de domínio. O modelo de pipeline integrado meeting_summary é acionado por esse evento. O gravador não espera por ele; a reunião limita-se a transitar pelo seu ciclo de vida de estados:

recording → processing → done

O agente de resumo recebe o título, as suas notas ao vivo aproximadas e a transcrição, e devolve JSON estruturado. As secções que escreve são determinadas pelo modelo de notas de reunião ativo do espaço de trabalho (Definições → Espaço de trabalho → Reuniões); o modelo ativo aplica-se a resumos novos e a resumos reexecutados. Os passos de persistência do pipeline escrevem então esse JSON em linhas distintas:

  • enhancedNotes e summary → as notas da reunião
  • cada tarefa → uma linha MeetingActionItem (conteúdo, responsável, ligação opcional a ticket)
  • cada decisão → uma linha MeetingDecision

As tarefas e as decisões nunca são extraídas de markdown de forma livre, apenas dos arrays estruturados do agente. Se uma execução não produzir saída estruturada, os passos de persistência são ignorados e a transcrição em bruto é mantida como alternativa, para nunca perder o registo. Nada no pipeline marca a reunião como done — é um único reconciliador que o faz a partir do evento terminal da execução, para que a falha de um passo irmão não deixe uma reunião meio escrita que já parece terminada.

Superfície O que mostra
/meetings A lista de reuniões, com contagens de tarefas e decisões
/meetings/record O HUD de gravação em direto: as suas notas de um lado e a transcrição em fluxo do outro
/meetings/:meetingId O detalhe de uma reunião: separadores Notas, Transcrição, Tarefas e Decisões
Definições → Espaço de trabalho → Reuniões Modelos de notas e perfis de voz guardados para este espaço de trabalho

A captura não precisa de controladores nas três plataformas de computador (taps do Core Audio no macOS, WASAPI no Windows e PipeWire no Linux).

O cliente web também grava, algo que o enquadramento no computador do resto desta página pode esconder: o navegador obtém o microfone através de getUserMedia e o áudio da reunião através de getDisplayMedia (partilha de ecrã), reduz a amostragem para PCM16 a 16 kHz e transmite para o mesmo host. Aí aplicam-se três ressalvas:

  • A superfície partilhada tem de transportar uma faixa de áudio. O Safari, o Firefox e a partilha de ecrã inteiro do macOS não fornecem nenhuma, e a gravação recusa-se a iniciar.
  • Não há AEC3 nativo. O cancelamento de eco do próprio navegador é pedido no microfone; o filtro de eco de texto do lado do host continua a correr. O HUD mostra um medidor de nível de entrada alimentado pelo PCM do microfone.
  • Refazer resumo e cancelar o processamento só existem no computador.

A ação Gravar e vincular do calendário só existe no computador e indica-o claramente na web, mesmo que o próprio fluxo de gravação do ecrã de reuniões funcione aí.