快速入門:辨識並驗證誰在說話

參考文件 | 套件 (NuGet) | GitHub 上的其他範例

在本快速入門中,您將使用語音 SDK 了解說話者辨識的基本設計模式,包括:

  • 與文字相關,以及與文字無關的驗證。
  • 說話者辨識,用以識別語音群組之間的語音樣本。
  • 刪除語音設定檔。

如需有關說話者辨識概念的全面探討,請參閱概觀一文。 如需支援的平台清單,請參閱左側窗格的 [參考] 節點。

重要

Microsoft 限制存取說話者辨識。 套用以透過 Azure AI 說話者辨識有限存取權檢閱 表單使用它。 獲得核准後,就可以存取說話者辨識 API。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站中建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,選取 [移至資源] 以檢視和管理金鑰。 如需認 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK

開始之前,您必須安裝語音 SDK。 根據您的平台,使用下列指示:

匯入相依性

若要執行本文中的範例,請在指令碼的頂端包含下列 using 陳述式:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CognitiveServices.Speech;
using Microsoft.CognitiveServices.Speech.Audio;

建立語音設定

若要使用語音 SDK 來呼叫語音服務,您必須建立 SpeechConfig 執行個體。 在此範例中,您會使用訂閱金鑰和區域來建立 SpeechConfig 執行個體。 您也會建立一些基本的重複使用程式碼,用於本文的其餘部分,而您可針對不同的自訂項目進行修改。

重要

完成時,請記得從程式碼中移除金鑰,且不要公開張貼金鑰。 在生產環境中,請使用安全的方式來儲存和存取您的認證,例如 Azure Key Vault。 如需詳細資訊,請參閱 Azure AI 服務安全性一文。

public class Program 
{
    static async Task Main(string[] args)
    {
        // replace with your own subscription key 
        string subscriptionKey = "YourSubscriptionKey";
        // replace with your own subscription region 
        string region = "YourSubscriptionRegion";
        var config = SpeechConfig.FromSubscription(subscriptionKey, region);
    }
}

文字相依驗證

說話者驗證是確認說話者符合已知或「已註冊」語音的動作。 第一個步驟是註冊一個語音設定檔,讓服務有一些內容可比較未來的語音樣本。 在此範例中,您會使用「與文字相關」的策略來註冊設定檔,這需要使用特定的複雜密碼來進行註冊和驗證。 如需支援的傳遞片語清單,請參閱 參考文件

一開始,請先在您的 Program 類別中建立下列函式,以註冊語音設定檔:

public static async Task VerificationEnroll(SpeechConfig config, Dictionary<string, string> profileMapping)
{
    using (var client = new VoiceProfileClient(config))
    using (var profile = await client.CreateProfileAsync(VoiceProfileType.TextDependentVerification, "en-us"))
    {
        var phraseResult = await client.GetActivationPhrasesAsync(VoiceProfileType.TextDependentVerification, "en-us");
        using (var audioInput = AudioConfig.FromDefaultMicrophoneInput())
        {
            Console.WriteLine($"Enrolling profile id {profile.Id}.");
            // give the profile a human-readable display name
            profileMapping.Add(profile.Id, "Your Name");

            VoiceProfileEnrollmentResult result = null;
            while (result is null || result.RemainingEnrollmentsCount > 0)
            {
                Console.WriteLine($"Speak the passphrase, \"${phraseResult.Phrases[0]}\"");
                result = await client.EnrollProfileAsync(profile, audioInput);
                Console.WriteLine($"Remaining enrollments needed: {result.RemainingEnrollmentsCount}");
                Console.WriteLine("");
            }
            
            if (result.Reason == ResultReason.EnrolledVoiceProfile)
            {
                await SpeakerVerify(config, profile, profileMapping);
            }
            else if (result.Reason == ResultReason.Canceled)
            {
                var cancellation = VoiceProfileEnrollmentCancellationDetails.FromResult(result);
                Console.WriteLine($"CANCELED {profile.Id}: ErrorCode={cancellation.ErrorCode} ErrorDetails={cancellation.ErrorDetails}");
            }
        }
    }
}

在此函式中,await client.CreateProfileAsync() 實際上會建立新的語音設定檔。 建立之後,您可以使用此範例中的 AudioConfig.FromDefaultMicrophoneInput(),透過您的預設輸入裝置擷取音訊,藉此指定輸入音訊樣本的方式。 接下來,您會在 while 迴圈中註冊音訊樣本,以追蹤註冊的剩餘樣本數和必要樣本。 在每次反覆運算中,client.EnrollProfileAsync(profile, audioInput) 會提示您對著麥克風說出複雜密碼,並將樣本新增至語音設定檔。

註冊完成之後,請呼叫 await SpeakerVerify(config, profile, profileMapping) 以根據您剛才建立的設定檔進行驗證。 新增另一個函式來定義 SpeakerVerify

public static async Task SpeakerVerify(SpeechConfig config, VoiceProfile profile, Dictionary<string, string> profileMapping)
{
    var speakerRecognizer = new SpeakerRecognizer(config, AudioConfig.FromDefaultMicrophoneInput());
    var model = SpeakerVerificationModel.FromProfile(profile);

    Console.WriteLine("Speak the passphrase to verify: \"My voice is my passport, please verify me.\"");
    var result = await speakerRecognizer.RecognizeOnceAsync(model);
    Console.WriteLine($"Verified voice profile for speaker {profileMapping[result.ProfileId]}, score is {result.Score}");
}

在此函式中,您會傳遞剛建立的 VoiceProfile 物件以初始化模型,藉此進行驗證。 接下來,await speakerRecognizer.RecognizeOnceAsync(model) 會提示您再次說出複雜密碼。 這次會根據您的語音設定檔進行驗證,並傳回 0.0 到 1.0 的相似度分數。 根據複雜密碼是否相符,result 物件也會傳回 AcceptReject

接下來,修改您的 Main() 函式,以呼叫您所建立的新函式。 此外,請注意,您可以透過函式呼叫,建立依參考傳遞的 Dictionary<string, string>。 這是因為此服務不允許已建立的 VoiceProfile 儲存人類可閱讀的名稱,因隱私權之故只儲存識別碼號碼。 在 VerificationEnroll 函式中,您會將含有新建立之識別碼的項目加上文字名稱,加入此字典中。 在需要顯示人類可閱讀名稱的應用程式開發案例中,「您必須將此對應儲存在某處,因為服務無法儲存」。

static async Task Main(string[] args)
{
    string subscriptionKey = "YourSubscriptionKey";
    string region = "westus";
    var config = SpeechConfig.FromSubscription(subscriptionKey, region);

    // persist profileMapping if you want to store a record of who the profile is
    var profileMapping = new Dictionary<string, string>();
    await VerificationEnroll(config, profileMapping);

    Console.ReadLine();
}

執行指令碼。 系統會提示您說出三次 "My voice is my passport, verify me" 以進行註冊,另外一次進行驗證。 傳回的結果是相似性分數,您可以用它來建立自己的自訂閾值以進行驗證。

Enrolling profile id 87-2cef-4dff-995b-dcefb64e203f.
Speak the passphrase, "My voice is my passport, verify me."
Remaining enrollments needed: 2

Speak the passphrase, "My voice is my passport, verify me."
Remaining enrollments needed: 1

Speak the passphrase, "My voice is my passport, verify me."
Remaining enrollments needed: 0

Speak the passphrase to verify: "My voice is my passport, verify me."
Verified voice profile for speaker Your Name, score is 0.915581

與文字無關的驗證

相較於「與文字相關」的驗證,「與文字無關」的驗證不需要三個音訊樣本,但「需要」總共 20 秒的音訊。

VerificationEnroll 函式進行幾項簡單的變更,即可切換至與文字無關的驗證。 首先,將驗證類型變更為 VoiceProfileType.TextIndependentVerification。 接下來,將 while 迴圈變更為追蹤 result.RemainingEnrollmentsSpeechLength,這會繼續提示您說話,直到擷取達 20 秒的音訊為止。

public static async Task VerificationEnroll(SpeechConfig config, Dictionary<string, string> profileMapping)
{
    using (var client = new VoiceProfileClient(config))
    using (var profile = await client.CreateProfileAsync(VoiceProfileType.TextIndependentVerification, "en-us"))
    {
        var phraseResult = await client.GetActivationPhrasesAsync(VoiceProfileType.TextIndependentVerification, "en-us");
        using (var audioInput = AudioConfig.FromDefaultMicrophoneInput())
        {
            Console.WriteLine($"Enrolling profile id {profile.Id}.");
            // give the profile a human-readable display name
            profileMapping.Add(profile.Id, "Your Name");

            VoiceProfileEnrollmentResult result = null;
            while (result is null || result.RemainingEnrollmentsSpeechLength > TimeSpan.Zero)
            {
                Console.WriteLine($"Speak the activation phrase, \"${phraseResult.Phrases[0]}\"");
                result = await client.EnrollProfileAsync(profile, audioInput);
                Console.WriteLine($"Remaining enrollment audio time needed: {result.RemainingEnrollmentsSpeechLength}");
                Console.WriteLine("");
            }
            
            if (result.Reason == ResultReason.EnrolledVoiceProfile)
            {
                await SpeakerVerify(config, profile, profileMapping);
            }
            else if (result.Reason == ResultReason.Canceled)
            {
                var cancellation = VoiceProfileEnrollmentCancellationDetails.FromResult(result);
                Console.WriteLine($"CANCELED {profile.Id}: ErrorCode={cancellation.ErrorCode} ErrorDetails={cancellation.ErrorDetails}");
            }
        }
    }
}

再次執行程式,並傳回相似度分數。

Enrolling profile id 4tt87d4-f2d3-44ae-b5b4-f1a8d4036ee9.
Speak the activation phrase, "<FIRST ACTIVATION PHRASE>"
Remaining enrollment audio time needed: 00:00:15.3200000

Speak the activation phrase, "<FIRST ACTIVATION PHRASE>"
Remaining enrollment audio time needed: 00:00:09.8100008

Speak the activation phrase, "<FIRST ACTIVATION PHRASE>"
Remaining enrollment audio time needed: 00:00:05.1900000

Speak the activation phrase, "<FIRST ACTIVATION PHRASE>"
Remaining enrollment audio time needed: 00:00:00.8700000

Speak the activation phrase, "<FIRST ACTIVATION PHRASE>"
Remaining enrollment audio time needed: 00:00:00

Speak the passphrase to verify: "My voice is my passport, please verify me."
Verified voice profile for speaker Your Name, score is 0.849409

說話者識別

說話者辨識用來從指定的已註冊語音群組中判斷「誰」在說話。 此程序類似「與文字無關的驗證」。 主要差異在於能否一次驗證多個語音設定檔,而不是驗證單一設定檔。

建立函式 IdentificationEnroll 以註冊多個語音設定檔。 每個設定檔的註冊流程皆和「與文字無關的驗證」的註冊流程相同。 此流程要求每個設定檔都要有 20 秒的音訊。 此函式會接受一份 profileNames 的字串清單,並為清單中每個名稱建立新的語音設定檔。 函式會傳回 VoiceProfile 物件的清單,您可以在下一個函式中用來識別說話者。

public static async Task<List<VoiceProfile>> IdentificationEnroll(SpeechConfig config, List<string> profileNames, Dictionary<string, string> profileMapping)
{
    List<VoiceProfile> voiceProfiles = new List<VoiceProfile>();
    using (var client = new VoiceProfileClient(config))
    {
        var phraseResult = await client.GetActivationPhrasesAsync(VoiceProfileType.TextIndependentVerification, "en-us");
        foreach (string name in profileNames)
        {
            using (var audioInput = AudioConfig.FromDefaultMicrophoneInput())
            {
                var profile = await client.CreateProfileAsync(VoiceProfileType.TextIndependentIdentification, "en-us");
                Console.WriteLine($"Creating voice profile for {name}.");
                profileMapping.Add(profile.Id, name);

                VoiceProfileEnrollmentResult result = null;
                while (result is null || result.RemainingEnrollmentsSpeechLength > TimeSpan.Zero)
                {
                    Console.WriteLine($"Speak the activation phrase, \"${phraseResult.Phrases[0]}\" to add to the profile enrollment sample for {name}.");
                    result = await client.EnrollProfileAsync(profile, audioInput);
                    Console.WriteLine($"Remaining enrollment audio time needed: {result.RemainingEnrollmentsSpeechLength}");
                    Console.WriteLine("");
                }
                voiceProfiles.Add(profile);
            }
        }
    }
    return voiceProfiles;
}

建立下列函式 SpeakerIdentification 以提交識別要求。 相較於說話者驗證,此函式的主要差異在於使用 SpeakerIdentificationModel.FromProfiles(),這會接受 VoiceProfile 物件的清單。

public static async Task SpeakerIdentification(SpeechConfig config, List<VoiceProfile> voiceProfiles, Dictionary<string, string> profileMapping) 
{
    var speakerRecognizer = new SpeakerRecognizer(config, AudioConfig.FromDefaultMicrophoneInput());
    var model = SpeakerIdentificationModel.FromProfiles(voiceProfiles);

    Console.WriteLine("Speak some text to identify who it is from your list of enrolled speakers.");
    var result = await speakerRecognizer.RecognizeOnceAsync(model);
    Console.WriteLine($"The most similar voice profile is {profileMapping[result.ProfileId]} with similarity score {result.Score}");
}

將您的 Main() 函式變更為下列內容。 您會建立 profileNames 的字串清單,並將其傳遞給您的 IdentificationEnroll() 函式。 這會提示您為清單中每個名稱建立新的語音設定檔,因此您可以新增更多名稱,為朋友或同事建立更多設定檔。

static async Task Main(string[] args)
{
    // replace with your own subscription key 
    string subscriptionKey = "YourSubscriptionKey";
    // replace with your own subscription region 
    string region = "YourSubscriptionRegion";
    var config = SpeechConfig.FromSubscription(subscriptionKey, region);

    // persist profileMapping if you want to store a record of who the profile is
    var profileMapping = new Dictionary<string, string>();
    var profileNames = new List<string>() { "Your name", "A friend's name" };
    
    var enrolledProfiles = await IdentificationEnroll(config, profileNames, profileMapping);
    await SpeakerIdentification(config, enrolledProfiles, profileMapping);

    foreach (var profile in enrolledProfiles)
    {
        profile.Dispose();
    }
    Console.ReadLine();
}

執行指令碼。 系統會提示您說出第一個設定檔的註冊語音樣本。 註冊完成後,系統會提示您為 profileNames 清單中的每個名稱重複此流程。 完成每個註冊之後,系統會提示您讓「任一人」說話。 然後,服務會嘗試從已註冊的語音設定檔中識別此人員。

這個範例只會傳回最接近的符合項目及其相似度分數。 若要取得包含前五個相似度分數的完整回應,請將 string json = result.Properties.GetProperty(PropertyId.SpeechServiceResponse_JsonResult) 新增至您的 SpeakerIdentification 函式。

變更音訊輸入類型

本文中的範例使用預設裝置麥克風做為音訊樣本的輸入來源。 當需要使用音訊檔案而非麥克風輸入時,請將 AudioConfig.FromDefaultMicrophoneInput() 的任何執行個體變更為 AudioConfig.FromWavFileInput(path/to/your/file.wav),切換為檔案輸入。 您也可以採用混合輸入,例如用麥克風註冊並用檔案驗證。

刪除語音設定檔註冊

若要刪除已註冊的設定檔,請在 VoiceProfileClient 物件上使用 DeleteProfileAsync() 函式。 下列範例函式示範如何從已知的語音設定檔識別碼中刪除語音設定檔:

public static async Task DeleteProfile(SpeechConfig config, string profileId) 
{
    using (var client = new VoiceProfileClient(config))
    {
        var profile = new VoiceProfile(profileId);
        await client.DeleteProfileAsync(profile);
    }
}

參考文件 | 套件 (NuGet) | GitHub 上的其他範例

在本快速入門中,您將使用語音 SDK 了解說話者辨識的基本設計模式,包括:

  • 與文字相關,以及與文字無關的驗證。
  • 說話者辨識,用以識別語音群組之間的語音樣本。
  • 刪除語音設定檔。

如需有關說話者辨識概念的全面探討,請參閱概觀一文。 如需支援的平台清單,請參閱左側窗格的 [參考] 節點。

重要

Microsoft 限制存取說話者辨識。 套用以透過 Azure AI 說話者辨識有限存取權檢閱 表單使用它。 獲得核准後,就可以存取說話者辨識 API。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站中建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,選取 [移至資源] 以檢視和管理金鑰。 如需認 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK

開始之前,您必須安裝語音 SDK。 根據您的平台,使用下列指示:

匯入相依性

若要執行本文中的範例,請在 .cpp 檔案的頂端新增下列陳述式:

#include <iostream>
#include <stdexcept>
// Note: Install the NuGet package Microsoft.CognitiveServices.Speech.
#include <speechapi_cxx.h>

using namespace std;
using namespace Microsoft::CognitiveServices::Speech;

// Note: Change the locale if desired.
auto profile_locale = "en-us";
auto audio_config = Audio::AudioConfig::FromDefaultMicrophoneInput();
auto ticks_per_second = 10000000;

建立語音設定

若要使用語音 SDK 呼叫語音服務,請建立 SpeechConfig 類別。 此類別包含訂用帳戶的相關資訊,例如您的金鑰和關聯的區域、端點、主機或授權權杖。

重要

完成時,請記得從程式碼中移除金鑰,且不要公開張貼金鑰。 在生產環境中,請使用安全的方式來儲存和存取您的認證,例如 Azure Key Vault。 如需詳細資訊,請參閱 Azure AI 服務安全性一文。

shared_ptr<SpeechConfig> GetSpeechConfig()
{
    auto subscription_key = 'PASTE_YOUR_SPEECH_SUBSCRIPTION_KEY_HERE';
    auto region = 'PASTE_YOUR_SPEECH_ENDPOINT_REGION_HERE';
    auto config = SpeechConfig::FromSubscription(subscription_key, region);
    return config;
}

文字相依驗證

說話者驗證是確認說話者符合已知或「已註冊」語音的動作。 第一個步驟是註冊一個語音設定檔,讓服務有一些內容可比較未來的語音樣本。 在此範例中,您會使用「與文字相關」的策略來註冊設定檔,這需要使用特定的複雜密碼來進行註冊和驗證。 如需支援的傳遞片語清單,請參閱 參考文件

TextDependentVerification 函式

首先建立 TextDependentVerification 函式:

void TextDependentVerification(shared_ptr<VoiceProfileClient> client, shared_ptr<SpeakerRecognizer> recognizer)
{
    std::cout << "Text Dependent Verification:\n\n";
    // Create the profile.
    auto profile = client->CreateProfileAsync(VoiceProfileType::TextDependentVerification, profile_locale).get();
    std::cout << "Created profile ID: " << profile->GetId() << "\n";
    AddEnrollmentsToTextDependentProfile(client, profile);
    SpeakerVerify(profile, recognizer);
    // Delete the profile.
    client->DeleteProfileAsync(profile);
}

此函式會使用 CreateProfileAsync 方法建立 VoiceProfile 物件。 VoiceProfile 有三種類型

  • TextIndependentIdentification
  • TextDependentVerification
  • TextIndependentVerification

在此情況下,您可將 VoiceProfileType::TextDependentVerification 傳遞至 CreateProfileAsync

然後,呼叫您接下來會定義的兩個 Helper 函式:AddEnrollmentsToTextDependentProfileSpeakerVerify。 最後,呼叫 DeleteProfileAsync 以清除設定檔。

AddEnrollmentsToTextDependentProfile 函式

定義以下函式來註冊語音設定檔:

void AddEnrollmentsToTextDependentProfile(shared_ptr<VoiceProfileClient> client, shared_ptr<VoiceProfile> profile)
{
    shared_ptr<VoiceProfileEnrollmentResult> enroll_result = nullptr;
    auto phraseResult = client->GetActivationPhrasesAsync(profile->GetType(), profile_locale).get();
    auto phrases = phraseResult->GetPhrases();
    while (enroll_result == nullptr || enroll_result->GetEnrollmentInfo(EnrollmentInfoType::RemainingEnrollmentsCount) > 0)
    {
        if (phrases != nullptr && phrases->size() > 0)
        {
            std::cout << "Please say the passphrase, \"" << phrases->at(0) << "\"\n";
            enroll_result = client->EnrollProfileAsync(profile, audio_config).get();
            std::cout << "Remaining enrollments needed: " << enroll_result->GetEnrollmentInfo(EnrollmentInfoType::RemainingEnrollmentsCount) << ".\n";
        }
        else
        {
            std::cout << "No passphrases received, enrollment not attempted.\n\n";
        }
    }
    std::cout << "Enrollment completed.\n\n";
}

在此函式中,您會在 while 迴圈中註冊音訊樣本,以追蹤註冊的剩餘樣本數和必要樣本。 在每次反覆運算中,EnrollProfileAsync 會提示您對著麥克風說出複雜密碼,並將樣本新增至語音設定檔。

SpeakerVerify 函式

如下所示,定義 SpeakerVerify

void SpeakerVerify(shared_ptr<VoiceProfile> profile, shared_ptr<SpeakerRecognizer> recognizer)
{
    shared_ptr<SpeakerVerificationModel> model = SpeakerVerificationModel::FromProfile(profile);
    std::cout << "Speak the passphrase to verify: \"My voice is my passport, verify me.\"\n";
    shared_ptr<SpeakerRecognitionResult> result = recognizer->RecognizeOnceAsync(model).get();
    std::cout << "Verified voice profile for speaker: " << result->ProfileId << ". Score is: " << result->GetScore() << ".\n\n";
}

在此函式中,您會使用 SpeakerVerificationModel::FromProfile 方法建立 SpeakerVerificationModel 物件,並傳入您稍早建立的 VoiceProfile 物件。

接下來,SpeechRecognizer::RecognizeOnceAsync 會提示您再次說出複雜密碼。 這次會根據您的語音設定檔進行驗證,並傳回 0.0 到 1.0 的相似度分數。 根據複雜密碼是否相符,SpeakerRecognitionResult 物件也會傳回 AcceptReject

與文字無關的驗證

相較於「與文字相關」的驗證,「與文字無關」的驗證不需要三個音訊樣本,但「需要」總共 20 秒的音訊。

TextIndependentVerification 函式

首先建立 TextIndependentVerification 函式:

void TextIndependentVerification(shared_ptr<VoiceProfileClient> client, shared_ptr<SpeakerRecognizer> recognizer)
{
    std::cout << "Text Independent Verification:\n\n";
    // Create the profile.
    auto profile = client->CreateProfileAsync(VoiceProfileType::TextIndependentVerification, profile_locale).get();
    std::cout << "Created profile ID: " << profile->GetId() << "\n";
    AddEnrollmentsToTextIndependentProfile(client, profile);
    SpeakerVerify(profile, recognizer);
    // Delete the profile.
    client->DeleteProfileAsync(profile);
}

如同 TextDependentVerification 函式,此函式會使用 CreateProfileAsync 方法建立 VoiceProfile 物件。

在此情況下,您可將 VoiceProfileType::TextIndependentVerification 傳遞至 CreateProfileAsync

接著,呼叫兩個 Helper 函式:您接下來會定義的 AddEnrollmentsToTextIndependentProfile,以及您已經定義的 SpeakerVerify。 最後,呼叫 DeleteProfileAsync 以清除設定檔。

AddEnrollmentsToTextIndependentProfile

定義以下函式來註冊語音設定檔:

void AddEnrollmentsToTextIndependentProfile(shared_ptr<VoiceProfileClient> client, shared_ptr<VoiceProfile> profile)
{
    shared_ptr<VoiceProfileEnrollmentResult> enroll_result = nullptr;
    auto phraseResult = client->GetActivationPhrasesAsync(profile->GetType(), profile_locale).get();
    auto phrases = phraseResult->GetPhrases();
    while (enroll_result == nullptr || enroll_result->GetEnrollmentInfo(EnrollmentInfoType::RemainingEnrollmentsSpeechLength) > 0)
    {
        if (phrases != nullptr && phrases->size() > 0)
        {
            std::cout << "Please say the activation phrase, \"" << phrases->at(0) << "\"\n";
            enroll_result = client->EnrollProfileAsync(profile, audio_config).get();
            std::cout << "Remaining audio time needed: " << enroll_result->GetEnrollmentInfo(EnrollmentInfoType::RemainingEnrollmentsSpeechLength) / ticks_per_second << " seconds.\n";
        }
        else
        {
            std::cout << "No activation phrases received, enrollment not attempted.\n\n";
        }
    }
    std::cout << "Enrollment completed.\n\n";
}

在此函式中,您會在 while 迴圈中註冊音訊樣本,以追蹤註冊的剩餘音訊秒數和必要的音訊。 在每次反覆運算中,EnrollProfileAsync 會提示您對著麥克風說話,並將樣本新增至語音設定檔。

說話者識別

說話者辨識用來從指定的已註冊語音群組中判斷「誰」在說話。 此程序類似「與文字無關的驗證」。 主要差異在於能否一次驗證多個語音設定檔,而不是驗證單一設定檔。

TextIndependentIdentification 函式

首先建立 TextIndependentIdentification 函式:

void TextIndependentIdentification(shared_ptr<VoiceProfileClient> client, shared_ptr<SpeakerRecognizer> recognizer)
{
    std::cout << "Speaker Identification:\n\n";
    // Create the profile.
    auto profile = client->CreateProfileAsync(VoiceProfileType::TextIndependentIdentification, profile_locale).get();
    std::cout << "Created profile ID: " << profile->GetId() << "\n";
    AddEnrollmentsToTextIndependentProfile(client, profile);
    SpeakerIdentify(profile, recognizer);
    // Delete the profile.
    client->DeleteProfileAsync(profile);
}

如同 TextDependentVerificationTextIndependentVerification 函式,此函式會使用 CreateProfileAsync 方法建立 VoiceProfile 物件。

在此情況下,您可將 VoiceProfileType::TextIndependentIdentification 傳遞至 CreateProfileAsync

接著,呼叫兩個 Helper 函式:您已經定義的 AddEnrollmentsToTextIndependentProfile,以及您接下來會定義的 SpeakerIdentify。 最後,呼叫 DeleteProfileAsync 以清除設定檔。

SpeakerIdentify 函式

如下所示,定義 SpeakerIdentify 函式:

void SpeakerIdentify(shared_ptr<VoiceProfile> profile, shared_ptr<SpeakerRecognizer> recognizer)
{
    shared_ptr<SpeakerIdentificationModel> model = SpeakerIdentificationModel::FromProfiles({ profile });
    // Note: We need at least four seconds of audio after pauses are subtracted.
    std::cout << "Please speak for at least ten seconds to identify who it is from your list of enrolled speakers.\n";
    shared_ptr<SpeakerRecognitionResult> result = recognizer->RecognizeOnceAsync(model).get();
    std::cout << "The most similar voice profile is: " << result->ProfileId << " with similarity score: " << result->GetScore() << ".\n\n";
}

在此函式中,您會使用 SpeakerIdentificationModel::FromProfiles方法建立 SpeakerIdentificationModel 物件。 SpeakerIdentificationModel::FromProfiles 會接受 VoiceProfile 物件的清單。 在本案例中,您會傳入稍早建立的 VoiceProfile 物件。 如果想要,您可以傳入多個 VoiceProfile 物件,每個物件都使用不同語音的音訊樣本註冊。

接下來,SpeechRecognizer::RecognizeOnceAsync 會提示您再次說話。 這次會比較您的語音與已註冊的語音設定檔,並傳回最相似的語音設定檔。

Main 函式

最後,如下所示定義 main 函式:

int main()
{
    auto speech_config = GetSpeechConfig();
    auto client = VoiceProfileClient::FromConfig(speech_config);
    auto recognizer = SpeakerRecognizer::FromConfig(speech_config, audio_config);
    TextDependentVerification(client, recognizer);
    TextIndependentVerification(client, recognizer);
    TextIndependentIdentification(client, recognizer);
    std::cout << "End of quickstart.\n";
}

此函式會呼叫您之前定義的函式。 首先會建立 VoiceProfileClient 物件和 SpeakerRecognizer 物件。

auto speech_config = GetSpeechConfig();
auto client = VoiceProfileClient::FromConfig(speech_config);
auto recognizer = SpeakerRecognizer::FromConfig(speech_config, audio_config);

VoiceProfileClient 物件用於建立、註冊及刪除語音設定檔。 SpeakerRecognizer 物件用於驗證一或多個已註冊語音設定檔的語音樣本。

變更音訊輸入類型

本文中的範例使用預設裝置麥克風做為音訊樣本的輸入來源。 在需要使用音訊檔案而非麥克風輸入的情況下,請變更以下這行:

auto audio_config = Audio::AudioConfig::FromDefaultMicrophoneInput();

變更為:

auto audio_config = Audio::AudioConfig::FromWavFileInput("path/to/your/file.wav");

或以 Audio::AudioConfig::FromWavFileInput取代任何使用的 audio_config。 您也可以採用混合輸入,例如用麥克風註冊並用檔案驗證。

參考文件 | 套件 (Go) | GitHub 上的其他範例

在本快速入門中,您將使用語音 SDK 了解說話者辨識的基本設計模式,包括:

  • 與文字相關,以及與文字無關的驗證。
  • 說話者辨識,用以識別語音群組之間的語音樣本。
  • 刪除語音設定檔。

如需有關說話者辨識概念的全面探討,請參閱概觀一文。 如需支援的平台清單,請參閱左側窗格的 [參考] 節點。

重要

Microsoft 限制存取說話者辨識。 套用以透過 Azure AI 說話者辨識有限存取權檢閱 表單加以使用。 獲得核准後,就可以存取說話者辨識 API。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站中建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,選取 [移至資源] 以檢視和管理金鑰。 如需認 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

設定環境

安裝適用於 Go 的語音 SDK。 檢查 SDK 安裝指南是否有更多要求

執行獨立識別

請遵循下列步驟以建立新的 GO 模組。

  1. 開啟您想要有新模組的命令提示字元,然後建立名為 independent-identification.go 的新檔案。

  2. 以下列程式碼取代 independent-identification.go 的內容。

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
        "time"
    
        "github.com/Microsoft/cognitive-services-speech-sdk-go/audio"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/common"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/speaker"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
    )
    
    func GetNewVoiceProfileFromClient(client *speaker.VoiceProfileClient, expectedType common.VoiceProfileType) *speaker.VoiceProfile {
        future := client.CreateProfileAsync(expectedType, "en-US")
        outcome := <-future
        if outcome.Failed() {
            fmt.Println("Got an error creating profile: ", outcome.Error.Error())
            return nil
        }
        profile := outcome.Profile
        _, err := profile.Id()
        if err != nil {
            fmt.Println("Unexpected error creating profile id: ", err)
            return nil
        }
        profileType, err := profile.Type();
        if err != nil {
            fmt.Println("Unexpected error getting profile type: ", err)
            return nil
        }
        if profileType != expectedType {
            fmt.Println("Profile type does not match expected type")
            return nil
        }
        return profile
    }
    
    func EnrollProfile(client *speaker.VoiceProfileClient, profile *speaker.VoiceProfile, audioConfig *audio.AudioConfig) {
        enrollmentReason, currentReason := common.EnrollingVoiceProfile, common.EnrollingVoiceProfile
        var currentResult *speaker.VoiceProfileEnrollmentResult
        expectedEnrollmentCount := 1
        for currentReason == enrollmentReason {
            fmt.Println(`Please speak the following phrase: "I'll talk for a few seconds so you can recognize my voice in the future."`)
            enrollFuture := client.EnrollProfileAsync(profile, audioConfig)
            enrollOutcome := <-enrollFuture
            if enrollOutcome.Failed() {
                fmt.Println("Got an error enrolling profile: ", enrollOutcome.Error.Error())
                return
            }
            currentResult = enrollOutcome.Result
            currentReason = currentResult.Reason
            if currentResult.EnrollmentsCount != expectedEnrollmentCount {
                fmt.Println("Unexpected enrollments for profile: ", currentResult.RemainingEnrollmentsCount)
            }
            expectedEnrollmentCount += 1
        }
        if currentReason != common.EnrolledVoiceProfile {
            fmt.Println("Unexpected result enrolling profile: ", currentResult)
        }
    }
    
    func DeleteProfile(client *speaker.VoiceProfileClient, profile *speaker.VoiceProfile) {
        deleteFuture := client.DeleteProfileAsync(profile)
        deleteOutcome := <-deleteFuture
        if deleteOutcome.Failed() {
            fmt.Println("Got an error deleting profile: ", deleteOutcome.Error.Error())
            return
        }
        result := deleteOutcome.Result
        if result.Reason != common.DeletedVoiceProfile {
            fmt.Println("Unexpected result deleting profile: ", result)
        }
    }
    
    func main() {
        subscription :=  "YourSubscriptionKey"
        region := "YourServiceRegion"
        config, err := speech.NewSpeechConfigFromSubscription(subscription, region)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer config.Close()
        client, err := speaker.NewVoiceProfileClientFromConfig(config)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer client.Close()
        audioConfig, err := audio.NewAudioConfigFromDefaultMicrophoneInput()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer audioConfig.Close()
        <-time.After(10 * time.Second)
        expectedType := common.VoiceProfileType(1)
    
        profile := GetNewVoiceProfileFromClient(client, expectedType)
        if profile == nil {
            fmt.Println("Error creating profile")
            return
        }
        defer profile.Close()
    
        EnrollProfile(client, profile, audioConfig)
    
        profiles := []*speaker.VoiceProfile{profile}
        model, err := speaker.NewSpeakerIdentificationModelFromProfiles(profiles)
        if err != nil {
            fmt.Println("Error creating Identification model: ", err)
        }
        if model == nil {
            fmt.Println("Error creating Identification model: nil model")
            return
        }
        identifyAudioConfig, err := audio.NewAudioConfigFromDefaultMicrophoneInput()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer identifyAudioConfig.Close()
        speakerRecognizer, err := speaker.NewSpeakerRecognizerFromConfig(config, identifyAudioConfig)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        identifyFuture := speakerRecognizer.IdentifyOnceAsync(model)
        identifyOutcome := <-identifyFuture
        if identifyOutcome.Failed() {
            fmt.Println("Got an error identifying profile: ", identifyOutcome.Error.Error())
            return
        }
        identifyResult := identifyOutcome.Result
        if identifyResult.Reason != common.RecognizedSpeakers {
            fmt.Println("Got an unexpected result identifying profile: ", identifyResult)
        }
        expectedID, _ := profile.Id()
        if identifyResult.ProfileID != expectedID {
            fmt.Println("Got an unexpected profile id identifying profile: ", identifyResult.ProfileID)
        }
        if identifyResult.Score < 1.0 {
            fmt.Println("Got an unexpected score identifying profile: ", identifyResult.Score)
        }
    
        DeleteProfile(client, profile)
        bufio.NewReader(os.Stdin).ReadBytes('\n')
    }
    
  3. independent-identification.go 中,將 YourSubscriptionKey 更換為您的語音資源索引鍵,並將 YourServiceRegion 更換為您的語音資源區域。

    重要

    完成時,請記得從程式碼中移除金鑰,且不要公開張貼金鑰。 在生產環境中,請使用安全的方式來儲存和存取您的認證,例如 Azure Key Vault。 如需詳細資訊,請參閱 Azure AI 服務安全性一文。

執行下列命令來建立 go.mod 檔案,以連結至裝載於 GitHub 上的元件:

go mod init independent-identification
go get github.com/Microsoft/cognitive-services-speech-sdk-go

現在建置並執行程式碼:

go build
go run independent-identification

執行獨立驗證

請遵循下列步驟以建立新的 GO 模組。

  1. 開啟您想要有新模組的命令提示字元,然後建立名為 independent-verification.go 的新檔案。

  2. 以下列程式碼取代 independent-verification.go 的內容。

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
        "time"
    
        "github.com/Microsoft/cognitive-services-speech-sdk-go/audio"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/common"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/speaker"
        "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
    )
    
    func GetNewVoiceProfileFromClient(client *speaker.VoiceProfileClient, expectedType common.VoiceProfileType) *speaker.VoiceProfile {
        future := client.CreateProfileAsync(expectedType, "en-US")
        outcome := <-future
        if outcome.Failed() {
            fmt.Println("Got an error creating profile: ", outcome.Error.Error())
            return nil
        }
        profile := outcome.Profile
        _, err := profile.Id()
        if err != nil {
            fmt.Println("Unexpected error creating profile id: ", err)
            return nil
        }
        profileType, err := profile.Type();
        if err != nil {
            fmt.Println("Unexpected error getting profile type: ", err)
            return nil
        }
        if profileType != expectedType {
            fmt.Println("Profile type does not match expected type")
            return nil
        }
        return profile
    }
    
    func EnrollProfile(client *speaker.VoiceProfileClient, profile *speaker.VoiceProfile, audioConfig *audio.AudioConfig) {
        enrollmentReason, currentReason := common.EnrollingVoiceProfile, common.EnrollingVoiceProfile
        var currentResult *speaker.VoiceProfileEnrollmentResult
        expectedEnrollmentCount := 1
        for currentReason == enrollmentReason {
            fmt.Println(`Please speak the following phrase: "I'll talk for a few seconds so you can recognize my voice in the future."`)
            enrollFuture := client.EnrollProfileAsync(profile, audioConfig)
            enrollOutcome := <-enrollFuture
            if enrollOutcome.Failed() {
                fmt.Println("Got an error enrolling profile: ", enrollOutcome.Error.Error())
                return
            }
            currentResult = enrollOutcome.Result
            currentReason = currentResult.Reason
            if currentResult.EnrollmentsCount != expectedEnrollmentCount {
                fmt.Println("Unexpected enrollments for profile: ", currentResult.RemainingEnrollmentsCount)
            }
            expectedEnrollmentCount += 1
        }
        if currentReason != common.EnrolledVoiceProfile {
            fmt.Println("Unexpected result enrolling profile: ", currentResult)
        }
    }
    
    func DeleteProfile(client *speaker.VoiceProfileClient, profile *speaker.VoiceProfile) {
        deleteFuture := client.DeleteProfileAsync(profile)
        deleteOutcome := <-deleteFuture
        if deleteOutcome.Failed() {
            fmt.Println("Got an error deleting profile: ", deleteOutcome.Error.Error())
            return
        }
        result := deleteOutcome.Result
        if result.Reason != common.DeletedVoiceProfile {
            fmt.Println("Unexpected result deleting profile: ", result)
        }
    }
    
    func main() {
        subscription :=  "YourSubscriptionKey"
        region := "YourServiceRegion"
        config, err := speech.NewSpeechConfigFromSubscription(subscription, region)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer config.Close()
        client, err := speaker.NewVoiceProfileClientFromConfig(config)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer client.Close()
        audioConfig, err := audio.NewAudioConfigFromDefaultMicrophoneInput()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer audioConfig.Close()
        <-time.After(10 * time.Second)
        expectedType := common.VoiceProfileType(3)
    
        profile := GetNewVoiceProfileFromClient(client, expectedType)
        if profile == nil {
            fmt.Println("Error creating profile")
            return
        }
        defer profile.Close()
    
        EnrollProfile(client, profile, audioConfig)
    
        model, err := speaker.NewSpeakerVerificationModelFromProfile(profile)
        if err != nil {
            fmt.Println("Error creating Verification model: ", err)
        }
        if model == nil {
            fmt.Println("Error creating Verification model: nil model")
            return
        }
        verifyAudioConfig, err := audio.NewAudioConfigFromDefaultMicrophoneInput()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        defer verifyAudioConfig.Close()
        speakerRecognizer, err := speaker.NewSpeakerRecognizerFromConfig(config, verifyAudioConfig)
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }
        verifyFuture := speakerRecognizer.VerifyOnceAsync(model)
        verifyOutcome := <-verifyFuture
        if verifyOutcome.Failed() {
            fmt.Println("Got an error verifying profile: ", verifyOutcome.Error.Error())
            return
        }
        verifyResult := verifyOutcome.Result
        if verifyResult.Reason != common.RecognizedSpeaker {
            fmt.Println("Got an unexpected result verifying profile: ", verifyResult)
        }
        expectedID, _ := profile.Id()
        if verifyResult.ProfileID != expectedID {
            fmt.Println("Got an unexpected profile id verifying profile: ", verifyResult.ProfileID)
        }
        if verifyResult.Score < 1.0 {
            fmt.Println("Got an unexpected score verifying profile: ", verifyResult.Score)
        }
    
        DeleteProfile(client, profile)
        bufio.NewReader(os.Stdin).ReadBytes('\n')
    }
    
  3. independent-verification.go 中,將 YourSubscriptionKey 換成您的語音資源金鑰,並將 YourServiceRegion 換成您的語音資源區域。

執行下列命令來建立 go.mod 檔案,以連結至裝載於 GitHub 上的元件:

go mod init independent-verification
go get github.com/Microsoft/cognitive-services-speech-sdk-go

現在組建並執行程式碼:

go build
go run independent-verification

清除資源

您可以使用 Azure 入口網站Azure 命令列介面 (CLI) 來移除您所建立的語音資源。

參考文件 | GitHub 上的其他範例

適用於 JAVA 的語音 SDK 支援說話者辨識,但這裡尚未列入指南。 請選取另一種程式設計語言來開始使用並了解概念,或參閱本文開頭連結的 Java 參考和範例。

參考文件 | 套件 (npm) | GitHub 上的其他範例 | 程式庫原始程式碼

在本快速入門中,您將使用語音 SDK 了解說話者辨識的基本設計模式,包括:

  • 與文字相關,以及與文字無關的驗證。
  • 說話者辨識,用以識別語音群組之間的語音樣本。
  • 刪除語音設定檔。

如需有關說話者辨識概念的全面探討,請參閱概觀一文。 如需支援的平台清單,請參閱左側窗格的 [參考] 節點。

重要

Microsoft 限制存取說話者辨識。 套用以透過 Azure AI 說話者辨識有限存取權檢閱 表單加以使用。 獲得核准後,就可以存取說話者辨識 API。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站中建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,選取 [移至資源] 以檢視和管理金鑰。 如需認 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

安裝語音 SDK

開始之前,您必須安裝適用於 JavaScript 的語音 SDK

根據目標環境而定,使用下列其中一項:

下載並擷取適用於 JavaScript 的語音 SDKmicrosoft.cognitiveservices.speech.sdk.bundle.js 檔案。 將此檔案放在 HTML 檔案可存取的資料夾中。

<script src="microsoft.cognitiveservices.speech.sdk.bundle.js"></script>;

秘訣

如果您是以網頁瀏覽器為目標,並使用 <script> 標籤,則不需要 sdk 前置詞。 sdk 前置詞是用來命名 require 模組的別名。

匯入相依性

若要執行本文中的範例,請在 .js 檔案的頂端新增下列陳述式:

"use strict";

/* To run this sample, install:
npm install microsoft-cognitiveservices-speech-sdk
*/
var sdk = require("microsoft-cognitiveservices-speech-sdk");
var fs = require("fs");

// Note: Change the locale if desired.
const profile_locale = "en-us";

/* Note: passphrase_files and verify_file should contain paths to audio files that contain \"My voice is my passport, verify me.\"
You can obtain these files from:
https://github.com/Azure-Samples/cognitive-services-speech-sdk/tree/fa6428a0837779cbeae172688e0286625e340942/quickstart/javascript/node/speaker-recognition/verification
*/ 
const passphrase_files = ["myVoiceIsMyPassportVerifyMe01.wav", "myVoiceIsMyPassportVerifyMe02.wav", "myVoiceIsMyPassportVerifyMe03.wav"];
const verify_file = "myVoiceIsMyPassportVerifyMe04.wav";
/* Note: identify_file should contain a path to an audio file that uses the same voice as the other files, but contains different speech. You can obtain this file from:
https://github.com/Azure-Samples/cognitive-services-speech-sdk/tree/fa6428a0837779cbeae172688e0286625e340942/quickstart/javascript/node/speaker-recognition/identification
*/
const identify_file = "aboutSpeechSdk.wav";

var subscription_key = 'PASTE_YOUR_SPEECH_SUBSCRIPTION_KEY_HERE';
var region = 'PASTE_YOUR_SPEECH_ENDPOINT_REGION_HERE';

const ticks_per_second = 10000000;

這些陳述式會匯入必要的程式庫,並從您的環境變數取得您的語音服務訂用帳戶金鑰和區域。 其也會指定您將在下列工作中使用的音訊檔案路徑。

重要

完成時,請記得從程式碼中移除金鑰,且不要公開張貼金鑰。 在生產環境中,請使用安全的方式來儲存和存取您的認證,例如 Azure Key Vault。 如需詳細資訊,請參閱 Azure AI 服務安全性一文。

建立 Helper 函式

新增下列 Helper 函式,將音訊檔案讀取至串流中,以供語音服務使用:

function GetAudioConfigFromFile (file)
{
    return sdk.AudioConfig.fromWavFileInput(fs.readFileSync(file));
}

在此函式中,您會使用 AudioInputStream.createPushStreamAudioConfig.fromStreamInput 方法來建立 AudioConfig 物件。 這個 AudioConfig 物件代表音訊串流。 在下列工作期間,您將使用其中幾個 AudioConfig 物件。

文字相依驗證

說話者驗證是確認說話者符合已知或「已註冊」語音的動作。 第一個步驟是註冊一個語音設定檔,讓服務有一些內容可比較未來的語音樣本。 在此範例中,您會使用「與文字相關」的策略來註冊設定檔,這需要使用特定的複雜密碼來進行註冊和驗證。 如需支援的傳遞片語清單,請參閱 參考文件

TextDependentVerification 函式

首先建立 TextDependentVerification 函式。

async function TextDependentVerification(client, speech_config)
{
    console.log ("Text Dependent Verification:\n");
    var profile = null;
    try {
        const type = sdk.VoiceProfileType.TextDependentVerification;
        // Create the profile.
        profile = await client.createProfileAsync(type, profile_locale);
        console.log ("Created profile ID: " + profile.profileId);
        // Get the activation phrases
        await GetActivationPhrases(type, profile_locale);
        await AddEnrollmentsToTextDependentProfile(client, profile, passphrase_files);
        const audio_config = GetAudioConfigFromFile(verify_file);
        const recognizer = new sdk.SpeakerRecognizer(speech_config, audio_config);
        await SpeakerVerify(profile, recognizer);
    }
    catch (error) {
        console.log ("Error:\n" + error);
    }
    finally {
        if (profile !== null) {
            console.log ("Deleting profile ID: " + profile.profileId);
            const deleteResult = await client.deleteProfileAsync (profile);
        }
    }
}

此函式會使用 VoiceProfileClient.createProfileAsync 方法建立 VoiceProfile 物件。 VoiceProfile 有三種類型

  • TextIndependentIdentification
  • TextDependentVerification
  • TextIndependentVerification

在此情況下,您可將 VoiceProfileType.TextDependentVerification 傳遞至 VoiceProfileClient.createProfileAsync

然後,呼叫您接下來會定義的兩個 Helper 函式:AddEnrollmentsToTextDependentProfileSpeakerVerify。 最後,呼叫 VoiceProfileClient.deleteProfileAsync 以移除設定檔。

AddEnrollmentsToTextDependentProfile 函式

定義以下函式來註冊語音設定檔:

async function AddEnrollmentsToTextDependentProfile(client, profile, audio_files)
{
    try {
        for (const file of audio_files) {
            console.log ("Adding enrollment to text dependent profile...");
            const audio_config = GetAudioConfigFromFile(file);
            const result = await client.enrollProfileAsync(profile, audio_config);
            if (result.reason === sdk.ResultReason.Canceled) {
                throw(JSON.stringify(sdk.VoiceProfileEnrollmentCancellationDetails.fromResult(result)));
            }
            else {
                console.log ("Remaining enrollments needed: " + result.privDetails["remainingEnrollmentsCount"] + ".");
            }
        };
        console.log ("Enrollment completed.\n");
    } catch (error) {
        console.log ("Error adding enrollments: " + error);
    }
}

在此函式中,您會呼叫稍早定義的 GetAudioConfigFromFile 函式,以從音訊樣本建立 AudioConfig 物件。 這些音訊範例包含複雜密碼,例如 "My voice is my passport, verify me"。然後,使用 VoiceProfileClient.enrollProfileAsync 方法來註冊這些音訊範例。

SpeakerVerify 函式

如下所示,定義 SpeakerVerify

async function SpeakerVerify(profile, recognizer)
{
    try {
        const model = sdk.SpeakerVerificationModel.fromProfile(profile);
        const result = await recognizer.recognizeOnceAsync(model);
        console.log ("Verified voice profile for speaker: " + result.profileId + ". Score is: " + result.score + ".\n");
    } catch (error) {
        console.log ("Error verifying speaker: " + error);
    }
}

在此函式中,您會使用 SpeakerVerificationModel.FromProfile 方法建立 SpeakerVerificationModel 物件,並傳入您稍早建立的 VoiceProfile 物件。

接下來,呼叫 SpeechRecognizer.recognizeOnceAsync 方法來驗證音訊樣本,其中包含與您先前註冊的音訊樣本相同的複雜密碼。 SpeechRecognizer.recognizeOnceAsync 會傳回 SpeakerRecognitionResult 物件,其 score 屬性包含的相似度分數範圍為 0.0 到 1.0。 SpeakerRecognitionResult 物件也包含 ResultReason 類型的 reason 屬性。 如果驗證成功,reason 屬性應會有 RecognizedSpeaker 值。

與文字無關的驗證

相對於與文字相關的驗證,與文字無關的驗證是:

  • 不需要說出特定的複雜密碼。 可以說任何語句。
  • 不需要三個音訊樣本,但是「一定」要有總長 20 秒的音訊。

TextIndependentVerification 函式

首先建立 TextIndependentVerification 函式。

async function TextIndependentVerification(client, speech_config)
{
    console.log ("Text Independent Verification:\n");
    var profile = null;
    try {
        const type = sdk.VoiceProfileType.TextIndependentVerification;
        // Create the profile.
        profile = await client.createProfileAsync(type, profile_locale);
        console.log ("Created profile ID: " + profile.profileId);
        // Get the activation phrases
        await GetActivationPhrases(type, profile_locale);
        await AddEnrollmentsToTextIndependentProfile(client, profile, [identify_file]);
        const audio_config = GetAudioConfigFromFile(passphrase_files[0]);
        const recognizer = new sdk.SpeakerRecognizer(speech_config, audio_config);
        await SpeakerVerify(profile, recognizer);
    }
    catch (error) {
        console.log ("Error:\n" + error);
    }
    finally {
        if (profile !== null) {
            console.log ("Deleting profile ID: " + profile.profileId);
            const deleteResult = await client.deleteProfileAsync (profile);
        }
    }
}

如同 TextDependentVerification 函式,此函式會使用 VoiceProfileClient.createProfileAsync 方法建立 VoiceProfile 物件。

在此情況下,您可將 VoiceProfileType.TextIndependentVerification 傳遞至 createProfileAsync

接著,呼叫兩個 Helper 函式:您接下來會定義的 AddEnrollmentsToTextIndependentProfile,以及您已經定義的 SpeakerVerify。 最後,呼叫 VoiceProfileClient.deleteProfileAsync 以移除設定檔。

AddEnrollmentsToTextIndependentProfile

定義以下函式來註冊語音設定檔:

async function AddEnrollmentsToTextIndependentProfile(client, profile, audio_files)
{
    try {
        for (const file of audio_files) {
            console.log ("Adding enrollment to text independent profile...");
            const audio_config = GetAudioConfigFromFile(file);
            const result = await client.enrollProfileAsync (profile, audio_config);
            if (result.reason === sdk.ResultReason.Canceled) {
                throw(JSON.stringify(sdk.VoiceProfileEnrollmentCancellationDetails.fromResult(result)));
            }
            else {
                console.log ("Remaining audio time needed: " + (result.privDetails["remainingEnrollmentsSpeechLength"] / ticks_per_second) + " seconds.");
            }
        }
        console.log ("Enrollment completed.\n");
    } catch (error) {
        console.log ("Error adding enrollments: " + error);
    }
}

在此函式中,您會呼叫稍早定義的 GetAudioConfigFromFile 函式,以從音訊樣本建立 AudioConfig 物件。 然後,使用 VoiceProfileClient.enrollProfileAsync 方法來註冊這些音訊樣本。

說話者識別

說話者辨識用來從指定的已註冊語音群組中判斷「誰」在說話。 此程序類似「與文字無關的驗證」。 主要差異在於能否一次驗證多個語音設定檔,而不是驗證單一設定檔。

TextIndependentIdentification 函式

首先建立 TextIndependentIdentification 函式。

async function TextIndependentIdentification(client, speech_config)
{
    console.log ("Text Independent Identification:\n");
    var profile = null;
    try {
        const type = sdk.VoiceProfileType.TextIndependentIdentification;
        // Create the profile.
        profile = await client.createProfileAsync(type, profile_locale);
        console.log ("Created profile ID: " + profile.profileId);
        // Get the activation phrases
        await GetActivationPhrases(type, profile_locale);
        await AddEnrollmentsToTextIndependentProfile(client, profile, [identify_file]);
        const audio_config = GetAudioConfigFromFile(passphrase_files[0]);
        const recognizer = new sdk.SpeakerRecognizer(speech_config, audio_config);
        await SpeakerIdentify(profile, recognizer);
    }
    catch (error) {
        console.log ("Error:\n" + error);
    }
    finally {
        if (profile !== null) {
            console.log ("Deleting profile ID: " + profile.profileId);
            const deleteResult = await client.deleteProfileAsync (profile);
        }
    }
}

如同 TextDependentVerificationTextIndependentVerification 函式,此函式會使用 VoiceProfileClient.createProfileAsync 方法建立 VoiceProfile 物件。

在此情況下,您可將 VoiceProfileType.TextIndependentIdentification 傳遞至 VoiceProfileClient.createProfileAsync

接著,呼叫兩個 Helper 函式:您已經定義的 AddEnrollmentsToTextIndependentProfile,以及您接下來會定義的 SpeakerIdentify。 最後,呼叫 VoiceProfileClient.deleteProfileAsync 以移除設定檔。

SpeakerIdentify 函式

如下所示,定義 SpeakerIdentify 函式:

async function SpeakerIdentify(profile, recognizer)
{
    try {
        const model = sdk.SpeakerIdentificationModel.fromProfiles([profile]);
        const result = await recognizer.recognizeOnceAsync(model);
        console.log ("The most similar voice profile is: " + result.profileId + " with similarity score: " + result.score + ".\n");
    } catch (error) {
        console.log ("Error identifying speaker: " + error);
    }
}

在此函式中,您會使用 SpeakerIdentificationModel.fromProfiles 方法建立 SpeakerIdentificationModel 物件,並傳入您稍早建立的 VoiceProfile 物件。

接下來,呼叫 SpeechRecognizer.recognizeOnceAsync 方法並傳入音訊樣本。 SpeechRecognizer.recognizeOnceAsync 會根據您用來建立 SpeakerIdentificationModelVoiceProfile 物件,嘗試識別此音訊樣本的語音。 其會傳回 SpeakerRecognitionResult 物件,其 profileId 屬性會識別相符的 VoiceProfile (如果有的話),而 score 屬性則包含 0.0 到 1.0 的相似度分數。

Main 函式

最後,如下所示定義 main 函式:

async function main() {
    const speech_config = sdk.SpeechConfig.fromSubscription(subscription_key, region);
    const client = new sdk.VoiceProfileClient(speech_config);

    await TextDependentVerification(client, speech_config);
    await TextIndependentVerification(client, speech_config);
    await TextIndependentIdentification(client, speech_config);
    console.log ("End of quickstart.");
}
main();

此函式會建立 VoiceProfileClient 物件,該物件用於建立、註冊及刪除語音設定檔。 然後會呼叫您先前定義的函式。

參考文件 | 套件 (下載) | GitHub 上的其他範例

適用于 Objective-C 的語音 SDK 不支援說話者辨識。 請選取另一個程式設計語言,或本文開頭的 Objective-C 參考和樣本連結。

參考文件 | 套件 (下載) | GitHub 上的其他範例

適用于 Swift 的語音 SDK 不支援說話者辨識。 請選取另一個程式設計語言,或本文開頭的 Swift 參考和樣本連結。

參考文件 | 套件 (PyPi) | GitHub 上的其他範例

適用于 Python 的語音 SDK 不支援說話者辨識。 請選取另一個程式設計語言,或本文開頭的 Python 參考和樣本連結。

語音轉換文字 REST API 參考 | 適用於簡短音訊的語音轉換文字 REST API 參考 | GitHub 上的其他樣本

在本快速入門中,您將使用語音 SDK 了解說話者辨識的基本設計模式,包括:

  • 與文字相關,以及與文字無關的驗證。
  • 說話者辨識,用以識別語音群組之間的語音樣本。
  • 刪除語音設定檔。

如需有關說話者辨識概念的全面探討,請參閱概觀一文。 如需支援的平台清單,請參閱左側窗格的 [參考] 節點。

重要

Microsoft 限制存取說話者辨識。 套用以透過 Azure AI 說話者辨識有限存取權檢閱 表單加以使用。 獲得核准後,就可以存取說話者辨識 API。

必要條件

  • Azure 訂用帳戶 - 建立免費帳戶
  • 在 Azure 入口網站中建立語音資源
  • 您的語音資源金鑰和區域。 部署語音資源之後,選取 [移至資源] 以檢視和管理金鑰。 如需認 Azure AI 服務資源的詳細資訊,請參閱取得資源的金鑰

文字相依驗證

說話者驗證是確認說話者符合已知或「已註冊」語音的動作。 第一個步驟是註冊一個語音設定檔,讓服務有一些內容可比較未來的語音樣本。 在此範例中,您會使用「與文字相關」的策略來註冊設定檔,這需要使用特定的複雜密碼來進行註冊和驗證。 如需支援的傳遞片語清單,請參閱 參考文件

首先建立語音設定檔。 您必須將語音服務訂閱金鑰和區域插入本文中的每個 curl 命令。

重要

完成時,請記得從程式碼中移除金鑰,且不要公開張貼金鑰。 在生產環境中,請使用安全的方式來儲存和存取您的認證,例如 Azure Key Vault。 如需詳細資訊,請參閱 Azure AI 服務安全性一文。

# Note Change locale if needed.
curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-dependent/profiles?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: application/json' \
--data-raw '{
    '\''locale'\'':'\''en-us'\''
}'

語音設定檔有三種類型:

  • 文字相依驗證
  • 與文字無關的驗證
  • 與文字無關的識別

在此情況下,您會建立與文字相關的驗證語音設定檔。 您應該會收到以下回應:

{
    "remainingEnrollmentsCount": 3,
    "locale": "en-us",
    "createdDateTime": "2020-09-29T14:54:29.683Z",
    "enrollmentStatus": "Enrolling",
    "modelVersion": null,
    "profileId": "714ce523-de76-4220-b93f-7c1cc1882d6e",
    "lastUpdatedDateTime": null,
    "enrollmentsCount": 0,
    "enrollmentsLength": 0.0,
    "enrollmentSpeechLength": 0.0
}

接下來,註冊語音設定檔。 在 --data-binary 參數值中,指定電腦上包含其中一個受支援複雜密碼的音訊檔案,例如 "My voice is my passport, verify me"。您可以使用 Windows 語音錄音機這類的應用程式來錄製音訊檔案。 或者,您可以使用 文字轉換語音來產生它。

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-dependent/profiles/INSERT_PROFILE_ID_HERE/enrollments?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

您應該會收到以下回應:

{
    "remainingEnrollmentsCount": 2,
    "passPhrase": "my voice is my passport verify me",
    "profileId": "714ce523-de76-4220-b93f-7c1cc1882d6e",
    "enrollmentStatus": "Enrolling",
    "enrollmentsCount": 1,
    "enrollmentsLength": 3.5,
    "enrollmentsSpeechLength": 2.88,
    "audioLength": 3.5,
    "audioSpeechLength": 2.88
}

此回應會告訴您,您需要註冊兩個以上的音訊樣本。

註冊總計三個音訊樣本之後,您應該會收到以下回應:

{
    "remainingEnrollmentsCount": 0,
    "passPhrase": "my voice is my passport verify me",
    "profileId": "714ce523-de76-4220-b93f-7c1cc1882d6e",
    "enrollmentStatus": "Enrolled",
    "enrollmentsCount": 3,
    "enrollmentsLength": 10.5,
    "enrollmentsSpeechLength": 8.64,
    "audioLength": 3.5,
    "audioSpeechLength": 2.88
}

您現在已準備好根據語音設定檔驗證音訊樣本。 此音訊樣本應該包含與您用來註冊語音設定檔的樣本相同的複雜密碼。

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-dependent/profiles/INSERT_PROFILE_ID_HERE:verify?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

您應該會收到以下回應:

{
    "recognitionResult": "Accept",
    "score": 1.0
}

Accept 表示複雜密碼相符且驗證成功。 回應也包含 0.0 到 1.0 的相似度分數。

若要完成,請刪除語音設定檔

curl --location --request DELETE \
'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-dependent/profiles/INSERT_PROFILE_ID_HERE?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE'

沒有回應。

與文字無關的驗證

相對於與文字相關的驗證,與文字無關的驗證是:

  • 不需要說出特定的複雜密碼。 可以說任何語句。
  • 不需要三個音訊樣本,但是「一定」要有總長 20 秒的音訊。

首先建立與文字無關的驗證設定檔

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-independent/profiles?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: application/json' \
--data-raw '{
    '\''locale'\'':'\''en-us'\''
}'

您應該會收到以下回應:

{
    "profileStatus": "Inactive",
    "remainingEnrollmentsSpeechLength": 20.0,
    "profileId": "3f85dca9-ffc9-4011-bf21-37fad2beb4d2",
    "locale": "en-us",
    "enrollmentStatus": "Enrolling",
    "createdDateTime": "2020-09-29T16:08:52.409Z",
    "lastUpdatedDateTime": null,
    "enrollmentsCount": 0,
    "enrollmentsLength": 0.0,
    "enrollmentSpeechLength": 0.0
    "modelVersion": null,
}

接下來,註冊語音設定檔。 同樣地,您需要提交包含總長 20 秒音訊的音訊樣本,而不是提交三個音訊樣本。

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-independent/profiles/INSERT_PROFILE_ID_HERE/enrollments?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

提交足夠的音訊樣本後,您應該會收到以下回應:

{
    "remainingEnrollmentsSpeechLength": 0.0,
    "profileId": "3f85dca9-ffc9-4011-bf21-37fad2beb4d2",
    "enrollmentStatus": "Enrolled",
    "enrollmentsCount": 1,
    "enrollmentsLength": 33.16,
    "enrollmentsSpeechLength": 29.21,
    "audioLength": 33.16,
    "audioSpeechLength": 29.21
}

您現在已準備好根據語音設定檔驗證音訊樣本。 同樣地,此音訊樣本不需要包含複雜密碼。 其可包含任何語音,但一定要包含總長至少四秒的音訊。

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-independent/profiles/INSERT_PROFILE_ID_HERE:verify?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

您應該會收到以下回應:

{
    "recognitionResult": "Accept",
    "score": 0.9196669459342957
}

Accept 表示驗證成功。 回應也包含 0.0 到 1.0 的相似度分數。

若要完成,請刪除語音設定檔

curl --location --request DELETE 'INSERT_ENDPOINT_HERE/speaker-recognition/verification/text-independent/profiles/INSERT_PROFILE_ID_HERE?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE'

沒有回應。

說話者識別

說話者辨識用來從指定的已註冊語音群組中判斷「誰」在說話。 此程序類似「與文字無關的驗證」。 主要差異在於能否一次驗證多個語音設定檔,而不是驗證單一設定檔。

首先建立與文字無關的識別設定檔

# Note Change locale if needed.
curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/identification/text-independent/profiles?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: application/json' \
--data-raw '{
    '\''locale'\'':'\''en-us'\''
}'

您應該會收到以下回應:

{
    "profileStatus": "Inactive",
    "remainingEnrollmentsSpeechLengthInSec": 20.0,
    "profileId": "de99ab38-36c8-4b82-b137-510907c61fe8",
    "locale": "en-us",
    "enrollmentStatus": "Enrolling",
    "createdDateTime": "2020-09-22T17:25:48.642Z",
    "lastUpdatedDateTime": null,
    "enrollmentsCount": 0,
    "enrollmentsLengthInSec": 0.0,
    "enrollmentsSpeechLengthInSec": 0.0,
    "modelVersion": null
}

接下來,註冊語音設定檔。 同樣地,您需要提交包含總計 20 秒音訊的音訊樣本。 這些樣本不需要包含複雜密碼。

curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/identification/text-independent/profiles/INSERT_PROFILE_ID_HERE/enrollments?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

提交足夠的音訊樣本後,您應該會收到以下回應:

{
    "remainingEnrollmentsSpeechLength": 0.0,
    "profileId": "de99ab38-36c8-4b82-b137-510907c61fe8",
    "enrollmentStatus": "Enrolled",
    "enrollmentsCount": 2,
    "enrollmentsLength": 36.69,
    "enrollmentsSpeechLength": 31.95,
    "audioLength": 33.16,
    "audioSpeechLength": 29.21
}

您現在已準備好使用語音設定檔來識別音訊樣本。 識別命令可接受以逗號分隔的可能語音設定檔識別碼清單。 在本例中,您要傳入前面建立的語音設定檔識別碼。 如果想要,您可以傳入多個語音設定檔識別碼,其中每個語音設定檔都是以不同語音的音訊樣本註冊。

# Profile ids comma seperated list
curl --location --request POST 'INSERT_ENDPOINT_HERE/speaker-recognition/identification/text-independent/profiles:identifySingleSpeaker?api-version=2021-09-05&profileIds=INSERT_PROFILE_ID_HERE' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE' \
--header 'Content-Type: audio/wav' \
--data-binary @'INSERT_FILE_PATH_HERE'

您應該會收到以下回應:

Success:
{
    "identifiedProfile": {
        "profileId": "de99ab38-36c8-4b82-b137-510907c61fe8",
        "score": 0.9083486
    },
    "profilesRanking": [
        {
            "profileId": "de99ab38-36c8-4b82-b137-510907c61fe8",
            "score": 0.9083486
        }
    ]
}

回應包含最符合您所提交音訊樣本的語音設定檔識別碼。 也包含候選語音設定檔清單,並依相似度順序排序。

若要完成,請刪除語音設定檔

curl --location --request DELETE \
'INSERT_ENDPOINT_HERE/speaker-recognition/identification/text-independent/profiles/INSERT_PROFILE_ID_HERE?api-version=2021-09-05' \
--header 'Ocp-Apim-Subscription-Key: INSERT_SUBSCRIPTION_KEY_HERE'

沒有回應。

語音 CLI 支援說話者辨識,但這裡尚未列入指南。 請選取另一種程式設計語言,以開始使用並了解概念。

後續步驟