https://a.storyblok.com/f/270183/52065/0491a1df6d/symbl-ai_videoapi.png

Transcrição de voz com o Symbl.ai e a Video API da Vonage

Publicado em April 14, 2022

Tempo de leitura: 3 minutos

A transcrição de voz, a conversão de fala em texto e a legenda em tempo real são muito procuradas no mundo atual, em que as reuniões por Video e áudio são a principal forma de comunicação. A Symbl.ai se destaca em inteligência conversacional. Hoje, vamos integrar a legenda em tempo real ao nosso Kit Inicial de Telessaúde SimplyDoc usando a API de streaming e análises da Symbl.ai. Nossos recursos de vídeo e áudio serão fornecidos pela Video API da Vonage. Vamos começar.

A video call between Mariana and Hanza. Text says: "Mariana: Okay, I just wanted to let you know that I am currently recording"

Como obter a trilha de áudio do OpenTok Publisher

Obtemos a trilha de áudio chamando o OT.initPublisher() método. Isso retorna um objeto Publisher . Podemos chamar .getAudioSource() neste objeto para receber um objeto `MediaStreamTrack`.

Agora, esse objeto MediaStream contém a faixa de áudio do editor que precisamos enviar para o Symbl.ai. Vamos usar a API Web Audio para manipular a trilha de áudio de forma que possamos enviá-la ao Symbl.ai por meio de WebSockets. Para que a Web Audio API funcione, precisamos de um objeto MediaStream. Existe uma API que nos permite usar o objeto MediaStreamTrack diretamente, mas, no momento em que este artigo foi escrito, ela só estava disponível no Firefox.

const audioTrack = publisher.getAudioSource()
const stream = new MediaStream();
stream.addTrack(audioTrack);

Agora temos o objeto stream, que podemos usar com a API Web Audio para criar um buffer de áudio a ser enviado ao Symb.ai.

Como criar um boombox com a Web Audio API

blue and orange audio player

Em primeiro lugar, é importante entender um pouco sobre a Web Audio API, já que vamos usá-la para processar nosso áudio no front-end. Pense na Web Audio API como um aparelho de som portátil, no qual declaramos cada um dos componentes. Primeiro, declaramos o objeto AudioContext, que é como a estrutura externa do aparelho.

const AudioContext = window.AudioContext;
const context = new AudioContext();

Agora que declaramos nosso contexto de áudio, estamos prontos para atribuir uma fonte a ele. Pense na fonte como a fita cassete ou o CD que se coloca no aparelho de som.

const source = context.createMediaStreamSource(stream);
const processor = context.createScriptProcessor(1024, 1, 1);
const gainNode = context.createGain();

Nessas três linhas de código, primeiro declaramos nosso nó de fonte, depois declaramos nosso ScriptProcessorNodee, por fim, nosso `gainNode`. Nenhum desses três nós está fazendo nada no momento, pois ainda não os conectamos entre si. O `gainNode` é como o botão de volume do aparelho de som, e o nó processador é como o leitor magnético ou a agulha que lê os dados da fita cassete ou do CD.

Agora vamos conectá-los.

source.connect(gainNode);
gainNode.connect(processor);
processor.connect(context.destination);

Aqui, conectamos a fonte ao `gainNode`. Podemos usar o `gainNode` para aumentar ou diminuir o volume da fonte. Assim, se, por exemplo, o microfone de uma pessoa estiver com o volume muito baixo, podemos aumentar o valor do ganho para corrigir isso.

gainNode.gain.value = 2;

Isso não é necessário para este tutorial, pois estamos partindo do princípio de que todos têm um microfone de qualidade suficiente.

Em seguida, conectamos a saída do `gainNode` ao nó processador. O nó processador recebe três argumentos: o tamanho do buffer, o número de canais de entrada e o número de canais de saída, respectivamente. Escolhemos 1024 como tamanho do buffer porque está na faixa mais baixa do espectro de quadros de amostragem (256, 512, 1024, 2048, 4096, 8192, 16384). Isso significa que obteremos melhor latência/desempenho em detrimento de um áudio extremamente preciso. Se você achar que o Symbl.ai está deixando de reconhecer palavras no seu áudio, aumentar esse valor pode ajudar. Observe que isso fará com que o onaudioprocess evento seja chamado com mais frequência, o que pode deixar seu computador mais lento.

Por falar nisso onaudioprocess, este é o evento que será disparado sempre que o nó do processador tiver um buffer de áudio pronto com o tamanho especificado. O Symbl.ai costuma preparar o buffer para envio da seguinte maneira:

processor.onaudioprocess = (e) => {
    // convert to 16-bit payload
    const inputData = e.inputBuffer.getChannelData(0) || new Float32Array(this.bufferSize);
    const targetBuffer = new Int16Array(inputData.length);
    for (let index = inputData.length; index > 0; index--) {
        targetBuffer[index] = 32767 * Math.min(1, inputData[index]);
    }
    // Send audio stream to websocket.
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(targetBuffer.buffer);
    }
  };
};

Falaremos sobre como enviamos o buffer usando WebSockets mais adiante neste artigo.

Por fim, enviamos o sinal do nó processador de volta para o destino. O destino é como os alto-falantes do boombox.

Aqui está um diagrama do que criamos até agora usando a API Web Audio.

Diagram containing the text: "Audio Source, Gain Node (Volume), Audio Processor, Audio Destination, onaudioprocess, Audio Buffer, Symbl.ai

E com isso, nosso boombox está pronto para funcionar!

Criando uma conexão WebSocket com o Symbl.ai

Agora, vamos criar uma conexão WebSocket com o Symbl.ai para que possamos enviar o buffer de áudio que preparamos nas etapas anteriores e receber as análises do Symbl.ai.

const accessToken = accessToken;
const uniqueMeetingId = btoa("user@example.com");
const symblEndpoint = `wss://api.symbl.ai/v1/realtime/insights/${uniqueMeetingId}?access_token=${accessToken}`;

Para gerar o token de acesso, siga este guia.

O ID exclusivo da reunião pode ser qualquer hash ou sequência de caracteres única. Este é apenas um exemplo de como criar uma sequência de caracteres criptografada e exclusiva a partir de um e-mail. Agora, vamos declarar nossos ouvintes.

// Fired when a message is received from the WebSocket server
ws.onmessage = (event) => {
  // You can find the conversationId in event.message.data.conversationId;
  const data = JSON.parse(event.data);
  if (data.type === 'message' && data.message.hasOwnProperty('data')) {
    console.log('conversationId', data.message.data.conversationId);
  }
  if (data.type === 'message_response') {
    for (let message of data.messages) {
      console.log('Transcript (more accurate): ', message.payload.content);
    }
  }
  if (data.type === 'topic_response') {
    for (let topic of data.topics) {
      console.log('Topic detected: ', topic.phrases)
    }
  }
  if (data.type === 'insight_response') {
    for (let insight of data.insights) {
      console.log('Insight detected: ', insight.payload.content);
    }
  }
  if (data.type === 'message' && data.message.hasOwnProperty('punctuated')) {
    console.log('Live transcript (less accurate): ', data.message.punctuated.transcript)
  }
  console.log(`Response type: ${data.type}. Object: `, data);
};
// Fired when the WebSocket closes unexpectedly due to an error or lost connection
ws.onerror  = (err) => {
  console.error(err);
};
// Fired when the WebSocket connection has been closed
ws.onclose = (event) => {
  console.info('Connection to websocket closed');
};

Optamos por usar a data.message.punctuated.transcript chave que oferece transcrição em tempo real. Isso acarreta uma certa perda de precisão, mas você pode decidir como usar os dados. Quando a conexão WebSocket for estabelecida, precisamos enviar uma mensagem ao Symbl.ai descrevendo nossa reunião e os participantes envolvidos. Isso ajuda a criar uma transcrição pós-chamada para nós, além de outras coisas, como diarização de palestrantes.

// Fired when the connection succeeds.
ws.onopen = (event) => {
  ws.send(JSON.stringify({
    type: 'start_request',
    meetingTitle: 'Websockets How-to', // Conversation name
    insightTypes: ['question', 'action_item'], // Will enable insight generation
    config: {
      confidenceThreshold: 0.5,
      languageCode: 'en-US',
      speechRecognition: {
        encoding: 'LINEAR16',
        sampleRateHertz: 44100,
      }
    },
    speaker: {
      userId: 'example@symbl.ai',
      name: 'Example Sample',
    }
  }));
};

Conclusão

E com isso, a parte principal da nossa demonstração de conversão de fala em texto está concluída! Com alguns ajustes na interface do usuário, é assim que fica uma ligação com transcrição de fala:

Compartilhar:

https://a.storyblok.com/f/270183/400x390/09555431c6/hamza-nasir.png
Hamza NasirDesenvolvedor Full Stack

Hamza é engenheiro de software e mora em Chicago. Ele trabalha na Webrtc.ventures, uma empresa líder no fornecimento de soluções WebRTC. Ele também atua como desenvolvedor full stack na Vonage, colaborando com a plataforma de Video para atender melhor às necessidades dos clientes. Como um introvertido que se orgulha disso, ele gosta de passar seu tempo livre brincando com seus gatos.