你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

快速入门:识别和验证说话人

参考文档包 (NuGet)GitHub 上的其他示例

本快速入门介绍通过使用语音 SDK 进行说话人识别的基本设计模式,其中包括:

  • 依赖于文本和独立于文本的验证。
  • 用于识别一组语音中的语音样本的说话人识别。
  • 删除语音配置文件。

若要大致了解说话人辨识概念,请参阅概述文章。 有关支持的平台列表,请查看左侧窗格中的“参考”节点。

重要

Microsoft 会限制对说话人识别的访问。 请通过填写 Azure AI 说话人识别受限访问评审表来申请使用权限。 获得批准后,你便可以访问说话人识别 API。

先决条件

安装语音 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 存储用户可读的名称,并且出于隐私目的仅存储 ID 号。 在 VerificationEnroll 函数中,使用新创建的 ID 以及文本名称向此字典添加一个条目。 在需要显示用户可读名称的应用程序开发方案中,必须将此映射存储到某个位置,因为该服务无法对其进行存储。

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() 函数。 以下示例函数演示如何从已知的语音配置文件 ID 中删除语音配置文件:

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。

先决条件

安装语音 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();

to:

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

或者将任何 audio_config 的使用替换为 Audio::AudioConfig::FromWavFileInput。 还可以具有混合输入,例如使用麦克风进行注册,使用文件进行验证。

参考文档包 (Go)GitHub 上的其他示例

本快速入门介绍通过使用语音 SDK 进行说话人识别的基本设计模式,其中包括:

  • 依赖于文本和独立于文本的验证。
  • 用于识别一组语音中的语音样本的说话人识别。
  • 删除语音配置文件。

若要大致了解说话人辨识概念,请参阅概述文章。 有关支持的平台列表,请查看左侧窗格中的“参考”节点。

重要

Microsoft 会限制对说话人识别的访问。 请通过填写 Azure AI 说话人识别受限访问评审表来申请使用权限。 获得批准后,你便可以访问说话人识别 API。

先决条件

设置环境

安装 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。

先决条件

安装语音 SDK

在启动之前,必须安装适用于 JavaScript 的语音 SDK

请根据目标环境使用以下项之一:

下载并提取适用于 JavaScript 的语音 SDK microsoft.cognitiveservices.speech.sdk.bundle.js 文件。 将其置于可供 HTML 文件访问的文件夹中。

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

提示

如果以 Web 浏览器为目标并使用 <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 对象还包含类型 ResultReasonreason 属性。 如果验证成功,则 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。

先决条件

依赖于文本的验证

说话人验证是确认说话人与已知或已注册的语音匹配的操作。 第一步是注册语音配置文件,以便该服务有可与将来的语音样本进行比较的内容。 在此示例中,使用依赖于文本的策略注册配置文件,该策略需要用于注册和验证的特定密码。 有关支持的密码的列表,请参阅参考文档

首先,创建语音配置文件。 需要在本文的每个 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 参数值,请在计算机上指定一个音频文件,其中包含一个受支持的密码,例如“我的语音就是我的通行证,请验证我”。可以使用 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
}

现在你可以使用语音配置文件识别音频样本。 识别命令接受以逗号分隔的可能的语音配置文件 ID 列表。 在本例中,你需要传入之前创建的语音配置文件的 ID。 如果需要,可以传入多个语音配置文件 ID,其中每个语音配置文件都注册了来自不同语音的音频示例。

# 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
        }
    ]
}

响应包含与所提交的音频示例最匹配的语音配置文件的 ID。 还包含按相似性顺序排列的候选语音配置文件列表。

最后,删除语音配置文件

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 支持说话人识别,但我们尚未提供有关此内容的指南。 请选择其他编程语言以开始学习并了解相关概念。

后续步骤