Transcrire en direct des appels avec Twilio Media Streams et Google Speech-to-Text

September 12, 2019
Rédigé par

Transcrire en direct des appels téléphoniques grâce à Twilio Media Streams et Google Speech-to-Text

Grâce à Twilio Media Streams, vous pouvez désormais étendre les capacités de votre application vocale basée sur Twilio en disposant d'un accès en temps réel au flux audio brut des appels téléphoniques. Nous pouvons par exemple construire des outils qui transcrivent en direct le contenu vocal d'un appel téléphonique vers une fenêtre de navigateur, procéder à une analyse du sentiment (opinion mining) de la voix d'un appel téléphonique, ou même recourir à la biométrie vocale afin d'identifier des individus.

Ce post vous guidera pas à pas dans la transcription d'un appel téléphonique en direct dans votre navigateur, à l'aide de Google Speech-to-Text et de Node.js.

Si vous préférez ne pas utiliser les instructions pas à pas, vous pouvez cloner mon référentiel Github et suivre le fichier ReadMe pour procéder à la configuration.

Configuration requise

Avant de commencer, vous devez vous assurer de disposer des éléments suivants :

Configurer le serveur local

Twilio Media Streams utilise l'API WebSocket pour diffuser en direct le son de l'appel téléphonique vers votre application. Commençons par configurer un serveur capable de gérer les connexions WebSocket.

Ouvrez votre terminal, créez un nouveau dossier de projet et créez un fichier index.js.

$ mkdir twilio-streams
$ cd twilio-streams
$ touch index.js

Pour traiter les requêtes HTTP, nous utiliserons le module http intégré de Node ainsi qu'Express. Pour les connexions WebSocket, nous utiliserons ws, un client WebSocket léger pour Node.

Dans le terminal, exécutez les commandes suivantes pour installer ws et Express :

$ npm install ws express

Ouvrez votre fichier index.js et ajoutez le code suivant pour configurer votre serveur.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
  console.log("New Connection Initiated");
});

//Handle HTTP Request
app.get("/", (req, res) => res.send("Hello World"));

console.log("Listening at Port 8080");
server.listen(8080);

Enregistrez et exécutez index.js avec node index.js. Ouvrez votre navigateur et rendez-vous sur http://localhost:8080. Votre navigateur devrait afficher Hello World.

Hello World dans le navigateur

Maintenant que nous savons que les requêtes HTTP fonctionnent, testons notre connexion WebSocket. Ouvrez la console de votre navigateur et exécutez la commande suivante :

var connection = new WebSocket('ws://localhost:8080')

Si vous revenez au terminal, vous devriez voir une entrée de journal indiquant New Connection Initiated.

Connexion au serveur WebSocket à partir du navigateur

Configurer les appels téléphoniques

Configurons notre numéro Twilio afin de nous connecter à notre serveur WebSocket.

Nous devons d'abord modifier notre serveur de sorte qu'il gère les messages WebSocket qui seront envoyés depuis Twilio quand la diffusion de notre appel téléphonique commencera. Nous voulons écouter quatre principaux événements de message : connected, start, media et stop.

  • Connected : lorsque Twilio établit une connexion WebSocket réussie avec un serveur
  • Start : lorsque Twilio commence à diffuser des paquets multimédia
  • Media : désigne des paquets multimédia codés (en l'occurrence, l'audio brut)
  • Stop : à la fin de la diffusion, l'événement d'arrêt est envoyé.

Modifiez votre fichier index.js afin de consigner les messages lorsque chacun d'eux arrive sur notre serveur.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
console.log("New Connection Initiated");

   ws.on("message", function incoming(message) {
    const msg = JSON.parse(message);
    switch (msg.event) {
      case "connected":
        console.log(`A new call has connected.`);
        break;
      case "start":
        console.log(`Starting Media Stream ${msg.streamSid}`);
        break;
      case "media":
        console.log(`Receiving Audio...`)
        break;
      case "stop":
        console.log(`Call Has Ended`);
        break;
    }
  });

});

//Handle HTTP Request
app.get("/", (req, res) => res.send("Hello World");

console.log("Listening at Port 8080");
server.listen(8080);

Nous devons maintenant configurer notre numéro Twilio afin de commencer à diffuser l'audio vers notre serveur. Nous pouvons contrôler ce qui se passe lorsque nous appelons notre numéro Twilio au moyen de TwiML. Nous créerons une route HTTP qui renverra du TwiML donnant à Twilio l'instruction de diffuser l'audio de l'appel vers notre serveur.

Ajoutez la route POST suivante à votre fichier index.js.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
console.log("New Connection Initiated");

   ws.on("message", function incoming(message) {
    const msg = JSON.parse(message);
    switch (msg.event) {
      case "connected":
        console.log(`A new call has connected.`);
        break;
      case "start":
        console.log(`Starting Media Stream ${msg.streamSid}`);
        break;
      case "media":
        console.log(`Receiving Audio...`)
        break;
      case "stop":
        console.log(`Call Has Ended`);
        break;
    }
  });

};

//Handle HTTP Request
app.get("/", (req, res) => res.send("Hello World");

app.post("/", (req, res) => {
  res.set("Content-Type", "text/xml");

  res.send(`
    <Response>
      <Start>
        <Stream url="wss://${req.headers.host}/"/>
      </Start>
      <Say>I will stream the next 60 seconds of audio through your websocket</Say>
      <Pause length="60" />
    </Response>
  `);
});

console.log("Listening at Port 8080");
server.listen(8080);

Pour que Twilio se connecte à votre serveur local, nous devons exposer le port à Internet. La façon la plus simple de le faire est d'utiliser l'interface de ligne de commande de Twilio. Ouvrez un nouveau terminal pour continuer.

Commençons par acheter un numéro de téléphone. Dans votre terminal, exécutez la commande suivante. J'ai utilisé le code pays GB pour acheter un numéro de téléphone portable, mais n'hésitez pas à le remplacer par un numéro correspondant à votre situation géographique. Assurez-vous de bien retenir le Friendly Name du numéro une fois la réponse renvoyée.

$ twilio phone-numbers:buy:mobile --country-code GB

Pour finir, mettons à jour le numéro de téléphone pour qu'il pointe vers notre URL d'hôte local. Nous devons utiliser ngrok pour créer un tunnel vers notre port d'hôte local et l'exposer à Internet. Dans une nouvelle fenêtre de terminal, exécutez la commande suivante :

$ ngrok http 8080

Vous devriez obtenir une sortie avec une adresse de transfert ressemblant à celle-ci. Copiez l'URL dans le presse-papiers. Assurez-vous de bien enregistrer l'URL https.

Forwarding                    https://xxxxxxxx.ngrok.io -> http://localhost:8080

Exécution de ngrok dans le terminal et copie de l&#x27;URL https

Revenons à la fenêtre de terminal où nous avons acheté notre numéro Twilio et mettons à jour notre numéro de téléphone afin de lancer une requête HTTP post vers notre serveur.

Exécutez la commande suivante :

$ twilio phone-numbers:update $TWILIO_NUMBER --voice-url  https://xxxxxxxx.ngrok.io

Rendez-vous dans une nouvelle fenêtre de terminal et exécutez votre fichier index.js. Maintenant, appelez votre numéro de téléphone Twilio et vous devriez entendre une invite du type : « Je vais diffuser les 60 prochaines secondes de flux audio via votre websocket ». Le terminal devrait afficher : Receiving Audio…

Réception audio dans le terminal

REMARQUE : assurez-vous que vous avez au moins 2 terminaux en cours d'exécution si votre journal ne correspond pas à la réponse attendue, l'un exécutant votre serveur (index.js) et l'autre exécutant ngrok.

Transcrire la voix en texte

À ce stade, le flux audio de notre appel téléphonique est diffusé vers notre serveur. Nous utiliserons aujourd'hui l'API Speech-to-Text de Google Cloud Platform pour transcrire les données vocales de l'appel téléphonique.

Nous allons devoir configurer certains éléments avant de commencer.

  1. Installer et initialiser le SDK de Cloud
  2. Configurer un nouveau projet GCP
  • Créez ou sélectionnez un projet.
  • Activez l'API Google Speech-to-Text pour ce projet.
  • Créez un compte de service.
  • Téléchargez une clé privée au format JSON.
  1. Définissez la variable d'environnement GOOGLE_APPLICATION_CREDENTIALS sur le chemin du fichier JSON contenant votre clé de compte de service. Cette variable s'applique uniquement à votre session shell actuelle. Par conséquent, si vous ouvrez une nouvelle session, définissez à nouveau la variable.

Exécutez la commande suivante pour installer les bibliothèques du client Google Cloud Speech-to-Text.

$ npm install --save @google-cloud/speech

Maintenant, utilisons-le dans notre code.

Nous inclurons tout d'abord le client vocal de la bibliothèque Google Speech-to-Text, avant de configurer une Transcription Request. Pour obtenir les résultats de la transcription en temps réel, assurez-vous de définir interimResults sur true. J'ai également défini le code de langue sur en-GB, mais n'hésitez pas à définir le vôtre sur une autre région linguistique.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });

//Include Google Speech to Text
const speech = require("@google-cloud/speech");
const client = new speech.SpeechClient();

//Configure Transcription Request
const request = {
  config: {
    encoding: "MULAW",
    sampleRateHertz: 8000,
    languageCode: "en-GB"
  },
  interimResults: true
};

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
console.log("New Connection Initiated");

   ws.on("message", function incoming(message) {
    const msg = JSON.parse(message);
    switch (msg.event) {
      case "connected":
        console.log(`A new call has connected.`);
        break;
      case "start":
        console.log(`Starting Media Stream ${msg.streamSid}`);
        break;
      case "media":
        console.log(`Receiving Audio...`)
        break;
      case "stop":
        console.log(`Call Has Ended`);
        break;
    }
  });

});

//Handle HTTP Request
app.get("/", (req, res) => res.send("Hello World");

app.post("/", (req, res) => {
  res.set("Content-Type", "text/xml");

  res.send(`
    <Response>
      <Start>
        <Stream url="wss://${req.headers.host}/"/>
      </Start>
      <Say>I will stream the next 60 seconds of audio through your websocket</Say>
      <Pause length="60" />
    </Response>
  `);
});

console.log("Listening at Port 8080");
server.listen(8080);

Créons maintenant un nouveau flux pour envoyer l'audio de notre serveur vers l'API Google. Nous lui donnerons le nom recognizeStream et nous écrirons les paquets audio de notre appel téléphonique vers ce flux. Une fois l'appel terminé, nous appellerons .destroy() pour mettre fin au flux.

Modifiez votre code pour y inclure ces modifications.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });

//Include Google Speech to Text
const speech = require("@google-cloud/speech");
const client = new speech.SpeechClient();

//Configure Transcription Request
const request = {
  config: {
    encoding: "MULAW",
    sampleRateHertz: 8000,
    languageCode: "en-GB"
  },
  interimResults: true
};

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
console.log("New Connection Initiated");

 let recognizeStream = null;

  ws.on("message", function incoming(message) {
    const msg = JSON.parse(message);
    switch (msg.event) {
      case "connected":
        console.log(`A new call has connected.`);

        // Create Stream to the Google Speech to Text API
        recognizeStream = client
          .streamingRecognize(request)
          .on("error", console.error)
          .on("data", data => {
            console.log(data.results[0].alternatives[0].transcript);
          });
        break;
      case "start":
        console.log(`Starting Media Stream ${msg.streamSid}`);
        break;
      case "media":
        // Write Media Packets to the recognize stream
        recognizeStream.write(msg.media.payload);
        break;
      case "stop":
        console.log(`Call Has Ended`);
        recognizeStream.destroy();
        break;
    }
  });
});

//Handle HTTP Request
app.get("/", (req, res) => res.send("Hello World");

app.post("/", (req, res) => {
  res.set("Content-Type", "text/xml");

  res.send(`
    <Response>
      <Start>
        <Stream url="wss://${req.headers.host}/"/>
      </Start>
      <Say>I will stream the next 60 seconds of audio through your websocket</Say>
      <Pause length="60" />
    </Response>
  `);
});

console.log("Listening at Port 8080");
server.listen(8080);

Redémarrez votre serveur, appelez votre numéro de téléphone Twilio et commencez à parler. Les résultats de transcription intermédiaires devraient commencer à s'afficher dans votre terminal.

Transcription en direct d&#x27;un appel téléphonique dans le terminal

Envoyer la transcription en direct vers le navigateur

L'un des avantages de l'utilisation de WebSockets est que nous pouvons diffuser des messages vers d'autres clients, y compris des navigateurs.

Modifions notre code afin de diffuser nos résultats de transcription intermédiaires vers tous les clients connectés. Nous modifierons également la route GET. Plutôt que d'envoyer « Hello World », envoyons un fichier HTML. Nous aurons également besoin du package path. N'oubliez donc pas de l'exiger.

Modifiez votre fichier index.js comme suit.

const WebSocket = require("ws");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const wss = new WebSocket.Server({ server });
const path = require("path");

//Include Google Speech to Text
const speech = require("@google-cloud/speech");
const client = new speech.SpeechClient();

//Configure Transcription Request
const request = {
  config: {
    encoding: "MULAW",
    sampleRateHertz: 8000,
    languageCode: "en-GB"
  },
  interimResults: true
};

// Handle Web Socket Connection
wss.on("connection", function connection(ws) {
console.log("New Connection Initiated");

let recognizeStream = null;

   ws.on("message", function incoming(message) {
    const msg = JSON.parse(message);
    switch (msg.event) {
      case "connected":
        console.log(`A new call has connected.`);
  //Create Stream to the Google Speech to Text API
  recognizeStream = client
    .streamingRecognize(request)
    .on("error", console.error)
    .on("data", data => {
      console.log(data.results[0].alternatives[0].transcript);
      wss.clients.forEach( client => {
           if (client.readyState === WebSocket.OPEN) {
             client.send(
               JSON.stringify({
               event: "interim-transcription",
               text: data.results[0].alternatives[0].transcript
             })
           );
         }
       });

    });

        break;
      case "start":
        console.log(`Starting Media Stream ${msg.streamSid}`);
        break;
      case "media":
        // Write Media Packets to the recognize stream
        recognizeStream.write(msg.media.payload);
        break;
      case "stop":
        console.log(`Call Has Ended`);
        recognizeStream.destroy();
        break;
    }
  });

});

//Handle HTTP Request
app.get("/", (req, res) => res.sendFile(path.join(__dirname, "/index.html")));

app.post("/", (req, res) => {
  res.set("Content-Type", "text/xml");

  res.send(`
    <Response>
      <Start>
        <Stream url="wss://${req.headers.host}/"/>
      </Start>
      <Say>I will stream the next 60 seconds of audio through your websocket</Say>
      <Pause length="60" />
    </Response>
  `);
});

console.log("Listening at Port 8080");
server.listen(8080);

Configurons une page Web afin de gérer les transcriptions intermédiaires et de les afficher dans le navigateur.

Créez un nouveau fichier nommé index.html et incluez-y les éléments suivants :

<!DOCTYPE html>
<html>
  <head>
    <title>Live Transcription with Twilio Media Streams</title>
  </head>
  <body>
    <h1>Live Transcription with Twilio Media Streams</h1>
    <h3>
      Call your Twilio Number, start talking and watch your words magically
      appear.
    </h3>
    <p id="transcription-container"></p>
    <script>
      document.addEventListener("DOMContentLoaded", event => {
        webSocket = new WebSocket("ws://localhost:8080");
        webSocket.onmessage = function(msg) {
          const data = JSON.parse(msg.data);
          if (data.event === "interim-transcription") {
            document.getElementById("transcription-container").innerHTML =
              data.text;
          }
        };
      });
    </script>
  </body>
</html>

Redémarrez votre serveur, chargez localhost:8080 dans votre navigateur, puis appelez votre numéro de téléphone Twilio et regardez les mots que vous prononcez commencer à apparaître dans votre navigateur.

Transcription en direct d&#x27;un appel téléphonique dans le navigateur

Conclusion

Félicitations ! Vous pouvez désormais tirer parti de la puissance des flux multimédia Twilio pour étendre vos applications vocales. Maintenant que vous savez mettre en place une transcription en direct, essayez de traduire le texte avec l'API Translate de Google afin de produire une traduction de la voix en direct, ou lancez une analyse de sentiment sur le flux audio afin d'analyser les émotions derrière le discours.

Pour toute question, tout commentaire, ou si vous souhaitez simplement me montrer ce que vous construisez, n'hésitez pas à me contacter :