Como separar locutores diferentes em uma gravação de áudio

Esta página descreve como obter rótulos para diferentes locutores em dados de áudio transcritos pelo Speech-to-Text.

Às vezes, dados de áudio contêm amostras de mais de uma pessoa falando. Por exemplo, o áudio de uma chamada telefônica geralmente tem vozes de duas ou mais pessoas. O ideal é que a transcrição dessa chamada identifique quem fala em quais ocasiões.

Diarização de locutor

O Speech-to-Text pode reconhecer vários locutores no mesmo clipe de áudio. Ao enviar uma solicitação de transcrição de áudio para a Speech-to-Text, é possível incluir um parâmetro para que ela identifique os diferentes locutores na amostra de áudio. Esse recurso, chamado de diarização de locutor, detecta a alternância entre os locutores e rotula por número as vozes individuais identificadas no áudio.

Quando você ativa a diarização de locutor em sua solicitação de transcrição, o Speech-to-Text tenta distinguir as diferentes vozes incluídas na amostra de áudio. O resultado da transcrição rotula cada palavra com um número atribuído a locutores individuais. Palavras ditas pelo mesmo locutor têm o mesmo número. Um resultado da transcrição pode incluir o máximo de locutores que o Speech-to-Text for capaz identificar de forma exclusiva na amostra de áudio.

Quando você usa a diarização de locutor, o Speech-to-Text produz um conjunto de todos os resultados fornecidos na transcrição. Cada resultado inclui as palavras do resultado anterior. Assim, você verá os resultados completos e diarizados da transcrição na matriz words do resultado final.

Revise a página de suporte ao idioma para ver se esse recurso está disponível para seu idioma.

Como ativar a diarização de locutor em uma solicitação

Para ativar a diarização de locutor, é necessário definir o campo enableSpeakerDiarization como true nos parâmetros RecognitionConfig da solicitação. Para melhorar os resultados da transcrição, você também deve especificar o número de locutores presentes no clipe de áudio definindo o campo diarizationSpeakerCount nos parâmetros RecognitionConfig. O Speech-to-Text usará um valor padrão se você não fornecer um valor para diarizationSpeakerCount.

A Speech-to-Text é compatível com a diarização de locutor de todos os métodos de reconhecimento de fala: speech:recognize speech:longrunningrecognize e Streaming.

Como usar um arquivo local

Veja no snippet de código a seguir como ativar a diarização de locutor em uma solicitação de transcrição para a Speech-to-Text usando um arquivo local.

Protocolo

Consulte o endpoint da API speech:recognize para ver todos os detalhes.

Para executar o reconhecimento de fala síncrono, faça uma solicitação POST e forneça o corpo apropriado a ela. Veja a seguir um exemplo de uma solicitação POST usando curl. Nele, é usado o token de acesso de uma conta de serviço configurada para o projeto que usa o SDK do Cloud do Google Cloud. Consulte o Guia de início rápido para instruções de como instalar o SDK do Cloud, configurar um projeto com uma conta de serviço e conseguir um token de acesso.

    curl -s -H "Content-Type: application/json" \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        https://speech.googleapis.com/v1p1beta1/speech:recognize \
        --data '{
        "config": {
            "encoding": "LINEAR16",
            "languageCode": "en-US",
            "enableSpeakerDiarization": true,
            "diarizationSpeakerCount": 2,
            "model": "phone_call"
        },
        "audio": {
            "uri": "gs://cloud-samples-tests/speech/commercial_mono.wav"
        }
    }' > speaker-diarization.txt
    

Quando a solicitação é bem-sucedida, o servidor retorna um código de status HTTP 200 OK e a resposta no formato JSON, salvos em um arquivo chamado speaker-diarization.txt.

    {
      "results": [
        {
          "alternatives": [
            {
              "transcript": "hi I'd like to buy a Chromecast and I was wondering whether you could help me with that certainly which color would you like we have blue black and red uh let's go with the black one would you like the new Chromecast Ultra model or the regular Chrome Cast regular Chromecast is fine thank you okay sure we like to ship it regular or Express Express please terrific it's on the way thank you thank you very much bye",
              "confidence": 0.92142606,
              "words": [
                {
                  "startTime": "0s",
                  "endTime": "1.100s",
                  "word": "hi",
                  "speakerTag": 2
                },
                {
                  "startTime": "1.100s",
                  "endTime": "2s",
                  "word": "I'd",
                  "speakerTag": 2
                },
                {
                  "startTime": "2s",
                  "endTime": "2s",
                  "word": "like",
                  "speakerTag": 2
                },
                {
                  "startTime": "2s",
                  "endTime": "2.100s",
                  "word": "to",
                  "speakerTag": 2
                },
                ...
                {
                  "startTime": "6.500s",
                  "endTime": "6.900s",
                  "word": "certainly",
                  "speakerTag": 1
                },
                {
                  "startTime": "6.900s",
                  "endTime": "7.300s",
                  "word": "which",
                  "speakerTag": 1
                },
                {
                  "startTime": "7.300s",
                  "endTime": "7.500s",
                  "word": "color",
                  "speakerTag": 1
                },
                ...
              ]
            }
          ],
          "languageCode": "en-us"
        }
      ]
    }
    

Java

/**
     * Transcribe the given audio file using speaker diarization.
     *
     * @param fileName the path to an audio file.
     */
    public static void transcribeDiarization(String fileName) throws Exception {
      Path path = Paths.get(fileName);
      byte[] content = Files.readAllBytes(path);

      try (SpeechClient speechClient = SpeechClient.create()) {
        // Get the contents of the local audio file
        RecognitionAudio recognitionAudio =
            RecognitionAudio.newBuilder().setContent(ByteString.copyFrom(content)).build();

        SpeakerDiarizationConfig speakerDiarizationConfig =
            SpeakerDiarizationConfig.newBuilder()
                .setEnableSpeakerDiarization(true)
                .setMinSpeakerCount(2)
                .setMaxSpeakerCount(2)
                .build();

        // Configure request to enable Speaker diarization
        RecognitionConfig config =
            RecognitionConfig.newBuilder()
                .setEncoding(AudioEncoding.LINEAR16)
                .setLanguageCode("en-US")
                .setSampleRateHertz(8000)
                .setDiarizationConfig(speakerDiarizationConfig)
                .build();

        // Perform the transcription request
        RecognizeResponse recognizeResponse = speechClient.recognize(config, recognitionAudio);

        // Speaker Tags are only included in the last result object, which has only one alternative.
        SpeechRecognitionAlternative alternative =
            recognizeResponse.getResults(recognizeResponse.getResultsCount() - 1).getAlternatives(0);

        // The alternative is made up of WordInfo objects that contain the speaker_tag.
        WordInfo wordInfo = alternative.getWords(0);
        int currentSpeakerTag = wordInfo.getSpeakerTag();

        // For each word, get all the words associated with one speaker, once the speaker changes,
        // add a new line with the new speaker and their spoken words.
        StringBuilder speakerWords =
            new StringBuilder(
                String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord()));

        for (int i = 1; i < alternative.getWordsCount(); i++) {
          wordInfo = alternative.getWords(i);
          if (currentSpeakerTag == wordInfo.getSpeakerTag()) {
            speakerWords.append(" ");
            speakerWords.append(wordInfo.getWord());
          } else {
            speakerWords.append(
                String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord()));
            currentSpeakerTag = wordInfo.getSpeakerTag();
          }
        }

        System.out.println(speakerWords.toString());
      }
    }

Node.js

const fs = require('fs');

    // Imports the Google Cloud client library
    const speech = require('@google-cloud/speech').v1p1beta1;

    // Creates a client
    const client = new speech.SpeechClient();

    /**
     * TODO(developer): Uncomment the following lines before running the sample.
     */
    // const fileName = 'Local path to audio file, e.g. /path/to/audio.raw';

    const config = {
      encoding: 'LINEAR16',
      sampleRateHertz: 8000,
      languageCode: 'en-US',
      enableSpeakerDiarization: true,
      diarizationSpeakerCount: 2,
      model: 'phone_call',
    };

    const audio = {
      content: fs.readFileSync(fileName).toString('base64'),
    };

    const request = {
      config: config,
      audio: audio,
    };

    const [response] = await client.recognize(request);
    const transcription = response.results
      .map(result => result.alternatives[0].transcript)
      .join('\n');
    console.log(`Transcription: ${transcription}`);
    console.log('Speaker Diarization:');
    const result = response.results[response.results.length - 1];
    const wordsInfo = result.alternatives[0].words;
    // Note: The transcript within each result is separate and sequential per result.
    // However, the words list within an alternative includes all the words
    // from all the results thus far. Thus, to get all the words with speaker
    // tags, you only have to take the words list from the last result:
    wordsInfo.forEach(a =>
      console.log(` word: ${a.word}, speakerTag: ${a.speakerTag}`)
    );

Python

from google.cloud import speech_v1p1beta1
    import io

    def sample_long_running_recognize(local_file_path):
        """
        Print confidence level for individual words in a transcription of a short audio
        file
        Separating different speakers in an audio file recording

        Args:
          local_file_path Path to local audio file, e.g. /path/audio.wav
        """

        client = speech_v1p1beta1.SpeechClient()

        # local_file_path = 'resources/commercial_mono.wav'

        # If enabled, each word in the first alternative of each result will be
        # tagged with a speaker tag to identify the speaker.
        enable_speaker_diarization = True

        # Optional. Specifies the estimated number of speakers in the conversation.
        diarization_speaker_count = 2

        # The language of the supplied audio
        language_code = "en-US"
        config = {
            "enable_speaker_diarization": enable_speaker_diarization,
            "diarization_speaker_count": diarization_speaker_count,
            "language_code": language_code,
        }
        with io.open(local_file_path, "rb") as f:
            content = f.read()
        audio = {"content": content}

        operation = client.long_running_recognize(config, audio)

        print(u"Waiting for operation to complete...")
        response = operation.result()

        for result in response.results:
            # First alternative has words tagged with speakers
            alternative = result.alternatives[0]
            print(u"Transcript: {}".format(alternative.transcript))
            # Print the speaker_tag of each word
            for word in alternative.words:
                print(u"Word: {}".format(word.word))
                print(u"Speaker tag: {}".format(word.speaker_tag))

    

Como usar um bucket do Google Cloud Storage

Veja no snippet de código a seguir como ativar a diarização de locutor em uma solicitação de transcrição para a Speech-to-Text usando um arquivo do Google Cloud Storage.

Java

/**
     * Transcribe a remote audio file using speaker diarization.
     *
     * @param gcsUri the path to an audio file.
     */
    public static void transcribeDiarizationGcs(String gcsUri) throws Exception {
      try (SpeechClient speechClient = SpeechClient.create()) {
        SpeakerDiarizationConfig speakerDiarizationConfig =
            SpeakerDiarizationConfig.newBuilder()
                .setEnableSpeakerDiarization(true)
                .setMinSpeakerCount(2)
                .setMaxSpeakerCount(2)
                .build();

        // Configure request to enable Speaker diarization
        RecognitionConfig config =
            RecognitionConfig.newBuilder()
                .setEncoding(AudioEncoding.LINEAR16)
                .setLanguageCode("en-US")
                .setSampleRateHertz(8000)
                .setDiarizationConfig(speakerDiarizationConfig)
                .build();

        // Set the remote path for the audio file
        RecognitionAudio audio = RecognitionAudio.newBuilder().setUri(gcsUri).build();

        // Use non-blocking call for getting file transcription
        OperationFuture<LongRunningRecognizeResponse, LongRunningRecognizeMetadata> response =
            speechClient.longRunningRecognizeAsync(config, audio);

        while (!response.isDone()) {
          System.out.println("Waiting for response...");
          Thread.sleep(10000);
        }

        // Speaker Tags are only included in the last result object, which has only one alternative.
        LongRunningRecognizeResponse longRunningRecognizeResponse = response.get();
        SpeechRecognitionAlternative alternative =
            longRunningRecognizeResponse
                .getResults(longRunningRecognizeResponse.getResultsCount() - 1)
                .getAlternatives(0);

        // The alternative is made up of WordInfo objects that contain the speaker_tag.
        WordInfo wordInfo = alternative.getWords(0);
        int currentSpeakerTag = wordInfo.getSpeakerTag();

        // For each word, get all the words associated with one speaker, once the speaker changes,
        // add a new line with the new speaker and their spoken words.
        StringBuilder speakerWords =
            new StringBuilder(
                String.format("Speaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord()));

        for (int i = 1; i < alternative.getWordsCount(); i++) {
          wordInfo = alternative.getWords(i);
          if (currentSpeakerTag == wordInfo.getSpeakerTag()) {
            speakerWords.append(" ");
            speakerWords.append(wordInfo.getWord());
          } else {
            speakerWords.append(
                String.format("\nSpeaker %d: %s", wordInfo.getSpeakerTag(), wordInfo.getWord()));
            currentSpeakerTag = wordInfo.getSpeakerTag();
          }
        }

        System.out.println(speakerWords.toString());
      }
    }

Node.js

// Imports the Google Cloud client library
    const speech = require('@google-cloud/speech').v1p1beta1;

    // Creates a client
    const client = new speech.SpeechClient();

    /**
     * TODO(developer): Uncomment the following line before running the sample.
     */
    // const uri = path to GCS audio file e.g. `gs:/bucket/audio.wav`;

    const config = {
      encoding: 'LINEAR16',
      sampleRateHertz: 8000,
      languageCode: 'en-US',
      enableSpeakerDiarization: true,
      diarizationSpeakerCount: 2,
      model: 'phone_call',
    };

    const audio = {
      uri: gcsUri,
    };

    const request = {
      config: config,
      audio: audio,
    };

    const [response] = await client.recognize(request);
    const transcription = response.results
      .map(result => result.alternatives[0].transcript)
      .join('\n');
    console.log(`Transcription: ${transcription}`);
    console.log('Speaker Diarization:');
    const result = response.results[response.results.length - 1];
    const wordsInfo = result.alternatives[0].words;
    // Note: The transcript within each result is separate and sequential per result.
    // However, the words list within an alternative includes all the words
    // from all the results thus far. Thus, to get all the words with speaker
    // tags, you only have to take the words list from the last result:
    wordsInfo.forEach(a =>
      console.log(` word: ${a.word}, speakerTag: ${a.speakerTag}`)
    );