다음을 통해 공유


텍스트에서 음성을 합성하는 방법

참조 설명서 | 패키지(NuGet) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 전체 목록을 가져오거나 음성 갤러리에서 사용해 볼 수 있습니다.

입력 텍스트와 일치하도록 SpeechConfig의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다. 다음 코드 조각은 이 기술의 작동 방식을 보여 줍니다.

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.SpeechSynthesisLanguage = "en-US"; 
    speechConfig.SpeechSynthesisVoiceName = "en-US-AvaMultilingualNeural";
}

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural을 선택하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

음성을 파일로 합성

SpeechSynthesizer 개체를 만듭니다. 다음 코드 조각에 표시되는 이 개체는 텍스트 음성 변환을 실행하고 스피커, 파일 또는 기타 출력 스트림으로 출력합니다. SpeechSynthesizer는 다음을 매개 변수로 허용합니다.

  • 이전 단계에서 만든 SpeechConfig 개체
  • 출력 결과를 처리하는 방법을 지정하는 AudioConfig 개체
  1. 다음과 같이 FromWavFileOutput() 함수를 사용하여 자동으로 .wav 파일에 출력을 기록하는 AudioConfig 인스턴스를 만듭니다. using 문을 사용하여 이 인스턴스를 인스턴스화합니다.

    static async Task SynthesizeAudioAsync()
    {
        var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
        using var audioConfig = AudioConfig.FromWavFileOutput("path/to/write/file.wav");
    }
    

    이 컨텍스트의 using 문은 관리되지 않는 리소스를 자동으로 삭제하고, 삭제되면 개체가 범위를 벗어납니다.

  2. 다른 using 문을 사용하여 SpeechSynthesizer 인스턴스를 인스턴스화합니다. speechConfig 개체 및 audioConfig 개체를 매개 변수로 전달합니다. 음성을 합성하고 파일에 쓰려면 텍스트 문자열을 사용하여 SpeakTextAsync()를 실행합니다.

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var audioConfig = AudioConfig.FromWavFileOutput("path/to/write/file.wav");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
}

프로그램을 실행하면 사용자가 지정한 위치에 기록되는 합성된 .wav 파일이 만들어집니다. 이 결과는 가장 기본적인 사용법의 좋은 예입니다. 다음으로, 출력을 사용자 지정하고 출력 응답을 사용자 지정 시나리오에 잘 맞는 메모리 내 스트림으로 처리할 수 있습니다.

스피커 출력으로 합성

합성된 음성을 스피커와 같은 현재 활성 출력 디바이스에 출력하려면 SpeechSynthesizer 인스턴스를 만들 때 AudioConfig 매개 변수를 생략합니다. 예를 들면 다음과 같습니다.

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig);
    await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
}

결과를 메모리 내 스트림으로 가져오기

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로, 먼저 AudioConfig 블록을 제거합니다. SpeechSynthesizer 생성자에서 AudioConfig에 대해 null을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 null을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. AudioData 속성에는 출력 데이터의 byte [] 인스턴스가 포함됩니다. 이 byte [] 인스턴스를 수동으로 작업할 수도 있고, AudioDataStream 클래스를 사용하여 메모리 내 스트림을 관리할 수도 있습니다.

다음 예제에서는 AudioDataStream.FromResult() 정적 함수를 사용하여 결과에서 스트림을 가져옵니다.

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);

    var result = await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");
    using var stream = AudioDataStream.FromResult(result);
}

이 시점에서 최종 stream 개체를 사용하여 사용자 지정 동작을 구현할 수 있습니다.

오디오 형식 사용자 지정

다음을 포함하여 오디오 출력 특성을 사용자 지정할 수 있습니다.

  • 오디오 파일 형식
  • 샘플 속도
  • 비트 수준

오디오 형식을 변경하려면 SpeechConfig 개체에서 SetSpeechSynthesisOutputFormat() 함수를 사용합니다. 이 함수에는 SpeechSynthesisOutputFormat 형식의 enum 인스턴스가 필요합니다. enum을 사용하여 출력 형식을 선택합니다. 사용 가능한 형식은 오디오 형식 목록을 참조하세요.

요구 사항에 따라 다양한 파일 형식에 대한 다양한 옵션이 있습니다. 정의에 따라 Raw24Khz16BitMonoPcm과 같은 원시 형식에는 오디오 헤더가 포함되지 않습니다. 다음과 같은 상황에는 원시 형식만 사용합니다.

  • 다운스트림 구현에서 원시 비트스트림을 디코딩할 수 있다는 것을 알고 있습니다.
  • 비트 수준, 샘플 속도 및 채널 수와 같은 요인에 따라 헤더를 수동으로 빌드할 계획입니다.

이 예제에서는 SpeechConfig 개체에서 SpeechSynthesisOutputFormat을 설정하여 고화질 RIFF 형식인 Riff24Khz16BitMonoPcm을 지정합니다. 이전 섹션의 예제와 마찬가지로 AudioDataStream을 사용하여 결과의 메모리 내 스트림을 가져온 다음, 파일에 씁니다.

static async Task SynthesizeAudioAsync()
{
    var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
    speechConfig.SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm);

    using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    var result = await speechSynthesizer.SpeakTextAsync("I'm excited to try text to speech");

    using var stream = AudioDataStream.FromResult(result);
    await stream.SaveToWaveFileAsync("path/to/write/file.wav");
}

이 프로그램을 실행하면 지정된 경로에 .wav 파일을 씁니다.

SSML을 사용하여 음성 특성 사용자 지정

SSML을 사용하여 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 텍스트 음성 변환 출력의 다른 측면을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 Speech Synthesis Markup Language 개요를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

  1. SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다.

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    이 예제에서 파일은 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

  2. XML 파일을 참조하도록 음성 합성 요청을 변경합니다. 요청은 대부분 동일하지만 SpeakTextAsync() 함수를 사용하는 대신 SpeakSsmlAsync()를 사용합니다. 이 함수에는 XML 문자열이 필요합니다. 먼저 File.ReadAllText()를 사용하여 SSML 구성을 문자열로 로드합니다. 이 시점에서 결과 개체는 이전 예제와 정확히 동일합니다.

    참고 항목

    Visual Studio를 사용하는 경우 빌드 구성에서 기본적으로 XML 파일을 찾지 못합니다. XML 파일을 마우스 오른쪽 단추로 클릭한 다음 속성을 선택합니다. 빌드 작업콘텐츠로 변경합니다. 출력 디렉터리로 복사항상 복사로 변경합니다.

    public static async Task SynthesizeAudioAsync()
    {
        var speechConfig = SpeechConfig.FromSubscription("YourSpeechKey", "YourSpeechRegion");
        using var speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    
        var ssml = File.ReadAllText("./ssml.xml");
        var result = await speechSynthesizer.SpeakSsmlAsync(ssml);
    
        using var stream = AudioDataStream.FromResult(result);
        await stream.SaveToWaveFileAsync("path/to/write/file.wav");
    }
    

참고 항목

SSML을 사용하지 않고 음성을 변경하려면 SpeechConfig.SpeechSynthesisVoiceName = "en-US-AvaMultilingualNeural";을 사용하여 SpeechConfig에서 속성을 설정하면 됩니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 Program.cs 파일의 내용을 다음 C# 코드로 바꿀 수 있습니다.

using Microsoft.CognitiveServices.Speech;

class Program 
{
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    static string speechKey = Environment.GetEnvironmentVariable("SPEECH_KEY");
    static string speechRegion = Environment.GetEnvironmentVariable("SPEECH_REGION");

    async static Task Main(string[] args)
    {
        var speechConfig = SpeechConfig.FromSubscription(speechKey, speechRegion);
         
        var speechSynthesisVoiceName  = "en-US-AvaMultilingualNeural";  
        var ssml = @$"<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
            <voice name='{speechSynthesisVoiceName}'>
                <mstts:viseme type='redlips_front'/>
                The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
            </voice>
        </speak>";

        // Required for sentence-level WordBoundary events
        speechConfig.SetProperty(PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

        using (var speechSynthesizer = new SpeechSynthesizer(speechConfig))
        {
            // Subscribe to events

            speechSynthesizer.BookmarkReached += (s, e) =>
            {
                Console.WriteLine($"BookmarkReached event:" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tText: \"{e.Text}\".");
            };

            speechSynthesizer.SynthesisCanceled += (s, e) =>
            {
                Console.WriteLine("SynthesisCanceled event");
            };

            speechSynthesizer.SynthesisCompleted += (s, e) =>
            {                
                Console.WriteLine($"SynthesisCompleted event:" +
                    $"\r\n\tAudioData: {e.Result.AudioData.Length} bytes" +
                    $"\r\n\tAudioDuration: {e.Result.AudioDuration}");
            };

            speechSynthesizer.SynthesisStarted += (s, e) =>
            {
                Console.WriteLine("SynthesisStarted event");
            };

            speechSynthesizer.Synthesizing += (s, e) =>
            {
                Console.WriteLine($"Synthesizing event:" +
                    $"\r\n\tAudioData: {e.Result.AudioData.Length} bytes");
            };

            speechSynthesizer.VisemeReceived += (s, e) =>
            {
                Console.WriteLine($"VisemeReceived event:" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tVisemeId: {e.VisemeId}");
            };

            speechSynthesizer.WordBoundary += (s, e) =>
            {
                Console.WriteLine($"WordBoundary event:" +
                    // Word, Punctuation, or Sentence
                    $"\r\n\tBoundaryType: {e.BoundaryType}" +
                    $"\r\n\tAudioOffset: {(e.AudioOffset + 5000) / 10000}ms" +
                    $"\r\n\tDuration: {e.Duration}" +
                    $"\r\n\tText: \"{e.Text}\"" +
                    $"\r\n\tTextOffset: {e.TextOffset}" +
                    $"\r\n\tWordLength: {e.WordLength}");
            };

            // Synthesize the SSML
            Console.WriteLine($"SSML to synthesize: \r\n{ssml}");
            var speechSynthesisResult = await speechSynthesizer.SpeakSsmlAsync(ssml);

            // Output the results
            switch (speechSynthesisResult.Reason)
            {
                case ResultReason.SynthesizingAudioCompleted:
                    Console.WriteLine("SynthesizingAudioCompleted result");
                    break;
                case ResultReason.Canceled:
                    var cancellation = SpeechSynthesisCancellationDetails.FromResult(speechSynthesisResult);
                    Console.WriteLine($"CANCELED: Reason={cancellation.Reason}");

                    if (cancellation.Reason == CancellationReason.Error)
                    {
                        Console.WriteLine($"CANCELED: ErrorCode={cancellation.ErrorCode}");
                        Console.WriteLine($"CANCELED: ErrorDetails=[{cancellation.ErrorDetails}]");
                        Console.WriteLine($"CANCELED: Did you set the speech resource key and region values?");
                    }
                    break;
                default:
                    break;
            }
        }

        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }
}

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

사용자 지정 엔드포인트 사용

사용자 지정 엔드포인트는 텍스트 음성 변환 요청에 사용되는 표준 엔드포인트와 기능적으로 동일합니다.

한 가지 차이점은 Speech SDK를 통해 사용자 지정 음성을 사용하려면 EndpointId를 지정해야 한다는 것입니다. 텍스트 음성 변환 빠른 시작으로 시작한 다음 EndpointIdSpeechSynthesisVoiceName으로 코드를 업데이트할 수 있습니다.

var speechConfig = SpeechConfig.FromSubscription(speechKey, speechRegion);     
speechConfig.SpeechSynthesisVoiceName = "YourCustomVoiceName";
speechConfig.EndpointId = "YourEndpointId";

SSML(Speech Synthesis Markup Language)을 통해 사용자 지정 음성을 사용하려면 모델 이름을 음성 이름으로 지정합니다. 이 예제에서는 YourCustomVoiceName 음성을 사용합니다.

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(NuGet) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 지원되는 텍스트 음성 변환 로캘의 전체 목록을 참조하거나 음성 갤러리에서 사용해 보세요.

입력 텍스트와 일치하도록 SpeechConfig 클래스의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다. 다음 코드 조각은 이 기술의 작동 방식을 보여 줍니다.

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig->SetSpeechSynthesisLanguage("en-US"); 
    speechConfig->SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");
}

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural로 설정하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

음성을 파일로 합성

SpeechSynthesizer 개체를 만듭니다. 다음 코드 조각에 표시되는 이 개체는 텍스트 음성 변환을 실행하고 스피커, 파일 또는 기타 출력 스트림으로 출력합니다. SpeechSynthesizer는 다음을 매개 변수로 허용합니다.

  • 이전 단계에서 만든 SpeechConfig 개체
  • 출력 결과를 처리하는 방법을 지정하는 AudioConfig 개체
  1. 다음과 같이 FromWavFileOutput() 함수를 사용하여 자동으로 .wav 파일에 출력을 기록하는 AudioConfig 인스턴스를 만듭니다.

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav");
    }
    
  2. SpeechSynthesizer 인스턴스를 인스턴스화합니다. speechConfig 개체 및 audioConfig 개체를 매개 변수로 전달합니다. 음성을 합성하고 파일에 쓰려면 텍스트 문자열을 사용하여 SpeakTextAsync()를 실행합니다.

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto audioConfig = AudioConfig::FromWavFileOutput("path/to/write/file.wav");
        auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig, audioConfig);
        auto result = speechSynthesizer->SpeakTextAsync("A simple test to write to a file.").get();
    }
    

프로그램을 실행하면 사용자가 지정한 위치에 기록되는 합성된 .wav 파일이 만들어집니다. 이 결과는 가장 기본적인 사용법의 좋은 예입니다. 다음으로, 출력을 사용자 지정하고 출력 응답을 사용자 지정 시나리오에 잘 맞는 메모리 내 스트림으로 처리할 수 있습니다.

스피커 출력으로 합성

합성된 음성을 스피커와 같은 현재 활성 출력 디바이스에 출력하려면 SpeechSynthesizer 인스턴스를 만들 때 AudioConfig 매개 변수를 생략합니다. 예를 들면 다음과 같습니다.

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    auto result = speechSynthesizer->SpeakTextAsync("I'm excited to try text to speech").get();
}

결과를 메모리 내 스트림으로 가져오기

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로, 먼저 AudioConfig 블록을 제거합니다. SpeechSynthesizer 생성자에서 AudioConfig에 대해 NULL을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 NULL을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. GetAudioData getter는 출력 데이터의 byte [] 인스턴스를 반환합니다. 이 byte [] 인스턴스를 수동으로 작업할 수도 있고, AudioDataStream 클래스를 사용하여 메모리 내 스트림을 관리할 수도 있습니다.

다음 예제에서는 AudioDataStream.FromResult() 정적 함수를 사용하여 결과에서 스트림을 가져옵니다.

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);

    auto result = speechSynthesizer->SpeakTextAsync("Getting the response as an in-memory stream.").get();
    auto stream = AudioDataStream::FromResult(result);
}

이 시점에서 최종 stream 개체를 사용하여 사용자 지정 동작을 구현할 수 있습니다.

오디오 형식 사용자 지정

다음을 포함하여 오디오 출력 특성을 사용자 지정할 수 있습니다.

  • 오디오 파일 형식
  • 샘플 속도
  • 비트 수준

오디오 형식을 변경하려면 SpeechConfig 개체에서 SetSpeechSynthesisOutputFormat() 함수를 사용합니다. 이 함수에는 SpeechSynthesisOutputFormat 형식의 enum 인스턴스가 필요합니다. enum을 사용하여 출력 형식을 선택합니다. 사용 가능한 형식은 오디오 형식 목록을 참조하세요.

요구 사항에 따라 다양한 파일 형식에 대한 다양한 옵션이 있습니다. 정의에 따라 Raw24Khz16BitMonoPcm과 같은 원시 형식에는 오디오 헤더가 포함되지 않습니다. 다음과 같은 상황에는 원시 형식만 사용합니다.

  • 다운스트림 구현에서 원시 비트스트림을 디코딩할 수 있다는 것을 알고 있습니다.
  • 비트 수준, 샘플 속도 및 채널 수와 같은 요인에 따라 헤더를 수동으로 빌드할 계획입니다.

이 예제에서는 SpeechConfig 개체에서 SpeechSynthesisOutputFormat을 설정하여 고화질 RIFF 형식인 Riff24Khz16BitMonoPcm을 지정합니다. 이전 섹션의 예제와 마찬가지로 AudioDataStream을 사용하여 결과의 메모리 내 스트림을 가져온 다음, 파일에 씁니다.

void synthesizeSpeech()
{
    auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
    speechConfig->SetSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat::Riff24Khz16BitMonoPcm);

    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    auto result = speechSynthesizer->SpeakTextAsync("A simple test to write to a file.").get();

    auto stream = AudioDataStream::FromResult(result);
    stream->SaveToWavFileAsync("path/to/write/file.wav").get();
}

이 프로그램을 실행하면 지정된 경로에 .wav 파일을 씁니다.

SSML을 사용하여 음성 특성 사용자 지정

SSML을 사용하여 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 텍스트 음성 변환 출력의 다른 측면을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 Speech Synthesis Markup Language 개요를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

  1. SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다.

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    이 예제에서 파일은 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

  2. XML 파일을 참조하도록 음성 합성 요청을 변경합니다. 요청은 대부분 동일합니다. SpeakTextAsync() 함수를 사용하는 대신 SpeakSsmlAsync()를 사용합니다. 이 함수에는 XML 문자열이 필요합니다. 먼저 SSML 구성을 문자열로 로드합니다. 이 시점에서 결과 개체는 이전 예제와 정확히 동일합니다.

    void synthesizeSpeech()
    {
        auto speechConfig = SpeechConfig::FromSubscription("YourSpeechKey", "YourSpeechRegion");
        auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);
    
        std::ifstream file("./ssml.xml");
        std::string ssml, line;
        while (std::getline(file, line))
        {
            ssml += line;
            ssml.push_back('\n');
        }
        auto result = speechSynthesizer->SpeakSsmlAsync(ssml).get();
    
        auto stream = AudioDataStream::FromResult(result);
        stream->SaveToWavFileAsync("path/to/write/file.wav").get();
    }
    

참고 항목

SSML을 사용하지 않고 음성을 변경하려면 SpeechConfig.SetSpeechSynthesisVoiceName("en-US-AndrewMultilingualNeural")을 사용하여 SpeechConfig에서 속성을 설정하면 됩니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 main.cpp 파일의 내용을 다음 코드로 바꿀 수 있습니다.

#include <iostream> 
#include <stdlib.h>
#include <speechapi_cxx.h>

using namespace Microsoft::CognitiveServices::Speech;
using namespace Microsoft::CognitiveServices::Speech::Audio;

std::string getEnvironmentVariable(const char* name);

int main()
{
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    auto speechKey = getEnvironmentVariable("SPEECH_KEY");
    auto speechRegion = getEnvironmentVariable("SPEECH_REGION");

    if ((size(speechKey) == 0) || (size(speechRegion) == 0)) {
        std::cout << "Please set both SPEECH_KEY and SPEECH_REGION environment variables." << std::endl;
        return -1;
    }

    auto speechConfig = SpeechConfig::FromSubscription(speechKey, speechRegion);

    // Required for WordBoundary event sentences.
    speechConfig->SetProperty(PropertyId::SpeechServiceResponse_RequestSentenceBoundary, "true");

    const auto ssml = R"(<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
        <voice name = 'en-US-AvaMultilingualNeural'>
            <mstts:viseme type = 'redlips_front' />
            The rainbow has seven colors : <bookmark mark = 'colors_list_begin' />Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark = 'colors_list_end' />.
        </voice>
        </speak>)";

    auto speechSynthesizer = SpeechSynthesizer::FromConfig(speechConfig);

    // Subscribe to events

    speechSynthesizer->BookmarkReached += [](const SpeechSynthesisBookmarkEventArgs& e)
    {
        std::cout << "Bookmark reached. "
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tText: " << e.Text << std::endl;
    };

    speechSynthesizer->SynthesisCanceled += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "SynthesisCanceled event" << std::endl;
    };

    speechSynthesizer->SynthesisCompleted += [](const SpeechSynthesisEventArgs& e)
    {
        auto audioDuration = std::chrono::duration_cast<std::chrono::milliseconds>(e.Result->AudioDuration).count();

        std::cout << "SynthesisCompleted event:"
            << "\r\n\tAudioData: " << e.Result->GetAudioData()->size() << "bytes"
            << "\r\n\tAudioDuration: " << audioDuration << std::endl;
    };

    speechSynthesizer->SynthesisStarted += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "SynthesisStarted event" << std::endl;
    };

    speechSynthesizer->Synthesizing += [](const SpeechSynthesisEventArgs& e)
    {
        std::cout << "Synthesizing event:"
            << "\r\n\tAudioData: " << e.Result->GetAudioData()->size() << "bytes" << std::endl;
    };

    speechSynthesizer->VisemeReceived += [](const SpeechSynthesisVisemeEventArgs& e)
    {
        std::cout << "VisemeReceived event:"
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tVisemeId: " << e.VisemeId << std::endl;
    };

    speechSynthesizer->WordBoundary += [](const SpeechSynthesisWordBoundaryEventArgs& e)
    {
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(e.Duration).count();
        
        auto boundaryType = "";
        switch (e.BoundaryType) {
        case SpeechSynthesisBoundaryType::Punctuation:
            boundaryType = "Punctuation";
            break;
        case SpeechSynthesisBoundaryType::Sentence:
            boundaryType = "Sentence";
            break;
        case SpeechSynthesisBoundaryType::Word:
            boundaryType = "Word";
            break;
        }

        std::cout << "WordBoundary event:"
            // Word, Punctuation, or Sentence
            << "\r\n\tBoundaryType: " << boundaryType
            << "\r\n\tAudioOffset: " << round(e.AudioOffset / 10000) << "ms"
            << "\r\n\tDuration: " << duration
            << "\r\n\tText: \"" << e.Text << "\""
            << "\r\n\tTextOffset: " << e.TextOffset
            << "\r\n\tWordLength: " << e.WordLength << std::endl;
    };

    auto result = speechSynthesizer->SpeakSsmlAsync(ssml).get();

    // Checks result.
    if (result->Reason == ResultReason::SynthesizingAudioCompleted)
    {
        std::cout << "SynthesizingAudioCompleted result" << std::endl;
    }
    else if (result->Reason == ResultReason::Canceled)
    {
        auto cancellation = SpeechSynthesisCancellationDetails::FromResult(result);
        std::cout << "CANCELED: Reason=" << (int)cancellation->Reason << std::endl;

        if (cancellation->Reason == CancellationReason::Error)
        {
            std::cout << "CANCELED: ErrorCode=" << (int)cancellation->ErrorCode << std::endl;
            std::cout << "CANCELED: ErrorDetails=[" << cancellation->ErrorDetails << "]" << std::endl;
            std::cout << "CANCELED: Did you set the speech resource key and region values?" << std::endl;
        }
    }

    std::cout << "Press enter to exit..." << std::endl;
    std::cin.get();
}

std::string getEnvironmentVariable(const char* name)
{
#if defined(_MSC_VER)
    size_t requiredSize = 0;
    (void)getenv_s(&requiredSize, nullptr, 0, name);
    if (requiredSize == 0)
    {
        return "";
    }
    auto buffer = std::make_unique<char[]>(requiredSize);
    (void)getenv_s(&requiredSize, buffer.get(), requiredSize, name);
    return buffer.get();
#else
    auto value = getenv(name);
    return value ? value : "";
#endif
}

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

사용자 지정 엔드포인트 사용

사용자 지정 엔드포인트는 텍스트 음성 변환 요청에 사용되는 표준 엔드포인트와 기능적으로 동일합니다.

한 가지 차이점은 Speech SDK를 통해 사용자 지정 음성을 사용하려면 EndpointId를 지정해야 한다는 것입니다. 텍스트 음성 변환 빠른 시작으로 시작한 다음 EndpointIdSpeechSynthesisVoiceName으로 코드를 업데이트할 수 있습니다.

auto speechConfig = SpeechConfig::FromSubscription(speechKey, speechRegion);
speechConfig->SetSpeechSynthesisVoiceName("YourCustomVoiceName");
speechConfig->SetEndpointId("YourEndpointId");

SSML(Speech Synthesis Markup Language)을 통해 사용자 지정 음성을 사용하려면 모델 이름을 음성 이름으로 지정합니다. 이 예제에서는 YourCustomVoiceName 음성을 사용합니다.

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(Go) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

필수 조건

Speech SDK 설치

작업을 수행하려면 먼저 Go용 Speech SDK를 설치해야 합니다.

화자로 텍스트 음성 변환

다음 코드 샘플을 사용하여 기본 오디오 출력 디바이스에 음성 합성을 실행합니다. subscriptionregion 변수를 음성 키와 위치/지역으로 바꿉니다. 스크립트를 실행하면 입력 텍스트가 기본 화자로 표시됩니다.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "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/speech"
)

func synthesizeStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesis started.")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesizing, audio chunk size %d.\n", len(event.Result.AudioData))
}

func synthesizedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesized, audio length %d.\n", len(event.Result.AudioData))
}

func cancelledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Received a cancellation.")
}

func main() {
    subscription := "YourSpeechKey"
    region := "YourSpeechRegion"

    audioConfig, err := audio.NewAudioConfigFromDefaultSpeakerOutput()
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer audioConfig.Close()
    speechConfig, err := speech.NewSpeechConfigFromSubscription(subscription, region)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()
    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, audioConfig)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.SynthesisStarted(synthesizeStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.SynthesisCompleted(synthesizedHandler)
    speechSynthesizer.SynthesisCanceled(cancelledHandler)

    for {
        fmt.Printf("Enter some text that you want to speak, or enter empty text to exit.\n> ")
        text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
        text = strings.TrimSuffix(text, "\n")
        if len(text) == 0 {
            break
        }

        task := speechSynthesizer.SpeakTextAsync(text)
        var outcome speech.SpeechSynthesisOutcome
        select {
        case outcome = <-task:
        case <-time.After(60 * time.Second):
            fmt.Println("Timed out")
            return
        }
        defer outcome.Close()
        if outcome.Error != nil {
            fmt.Println("Got an error: ", outcome.Error)
            return
        }

        if outcome.Result.Reason == common.SynthesizingAudioCompleted {
            fmt.Printf("Speech synthesized to speaker for text [%s].\n", text)
        } else {
            cancellation, _ := speech.NewCancellationDetailsFromSpeechSynthesisResult(outcome.Result)
            fmt.Printf("CANCELED: Reason=%d.\n", cancellation.Reason)

            if cancellation.Reason == common.Error {
                fmt.Printf("CANCELED: ErrorCode=%d\nCANCELED: ErrorDetails=[%s]\nCANCELED: Did you set the speech resource key and region values?\n",
                    cancellation.ErrorCode,
                    cancellation.ErrorDetails)
            }
        }
    }
}

다음 명령을 실행하여 GitHub에서 호스팅되는 구성 요소에 연결되는 go.mod 파일을 만듭니다.

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

이제 코드를 빌드하고 실행합니다.

go build
go run quickstart

클래스에 대한 자세한 내용은 SpeechConfigSpeechSynthesizer 참조 문서를 확인하세요.

메모리 내 스트림으로 텍스트 음성 변환

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로 AudioConfig 블록을 제거합니다. 그런 다음, SpeechSynthesizer 생성자에서 AudioConfig에 대해 nil을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 nil을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. AudioData 속성은 출력 데이터의 []byte 인스턴스를 반환합니다. 이 []byte 인스턴스를 수동으로 작업할 수도 있고, AudioDataStream 클래스를 사용하여 메모리 내 스트림을 관리할 수도 있습니다. 다음 예제에서는 NewAudioDataStreamFromSpeechSynthesisResult() 정적 함수를 사용하여 결과에서 스트림을 가져옵니다.

subscriptionregion 변수를 음성 키와 위치/지역으로 바꿉니다.

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
    "time"

    "github.com/Microsoft/cognitive-services-speech-sdk-go/speech"
)

func synthesizeStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesis started.")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesizing, audio chunk size %d.\n", len(event.Result.AudioData))
}

func synthesizedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Printf("Synthesized, audio length %d.\n", len(event.Result.AudioData))
}

func cancelledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Received a cancellation.")
}

func main() {
    subscription := "YourSpeechKey"
    region := "YourSpeechRegion"

    speechConfig, err := speech.NewSpeechConfigFromSubscription(subscription, region)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()
    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, nil)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.SynthesisStarted(synthesizeStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.SynthesisCompleted(synthesizedHandler)
    speechSynthesizer.SynthesisCanceled(cancelledHandler)

    for {
        fmt.Printf("Enter some text that you want to speak, or enter empty text to exit.\n> ")
        text, _ := bufio.NewReader(os.Stdin).ReadString('\n')
        text = strings.TrimSuffix(text, "\n")
        if len(text) == 0 {
            break
        }

        // StartSpeakingTextAsync sends the result to channel when the synthesis starts.
        task := speechSynthesizer.StartSpeakingTextAsync(text)
        var outcome speech.SpeechSynthesisOutcome
        select {
        case outcome = <-task:
        case <-time.After(60 * time.Second):
            fmt.Println("Timed out")
            return
        }
        defer outcome.Close()
        if outcome.Error != nil {
            fmt.Println("Got an error: ", outcome.Error)
            return
        }

        // In most cases, we want to streaming receive the audio to lower the latency.
        // We can use AudioDataStream to do so.
        stream, err := speech.NewAudioDataStreamFromSpeechSynthesisResult(outcome.Result)
        defer stream.Close()
        if err != nil {
            fmt.Println("Got an error: ", err)
            return
        }

        var all_audio []byte
        audio_chunk := make([]byte, 2048)
        for {
            n, err := stream.Read(audio_chunk)

            if err == io.EOF {
                break
            }

            all_audio = append(all_audio, audio_chunk[:n]...)
        }

        fmt.Printf("Read [%d] bytes from audio data stream.\n", len(all_audio))
    }
}

다음 명령을 실행하여 GitHub에서 호스팅되는 구성 요소에 연결되는 go.mod 파일을 만듭니다.

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

이제 코드를 빌드하고 실행합니다.

go build
go run quickstart

클래스에 대한 자세한 내용은 SpeechConfigSpeechSynthesizer 참조 문서를 확인하세요.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 전체 목록을 가져오거나 음성 갤러리에서 사용해 볼 수 있습니다.

입력 텍스트와 일치하도록 SpeechConfig의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다.

speechConfig, err := speech.NewSpeechConfigFromSubscription(key, region)
if err != nil {
    fmt.Println("Got an error: ", err)
    return
}
defer speechConfig.Close()

speechConfig.SetSpeechSynthesisLanguage("en-US")
speechConfig.SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural")

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural로 설정하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

SSML을 사용하여 음성 특성 사용자 지정

SSML(Speech Synthesis Markup Language)을 사용하면 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 더 많은 텍스트 음성 변환 출력을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 Speech Synthesis Markup Language 개요를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

먼저, SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다. 이 예에서는 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

<speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
  <voice name="en-US-AvaMultilingualNeural">
    When you're on the freeway, it's a good idea to use a GPS.
  </voice>
</speak>

다음으로, XML 파일을 참조하도록 음성 합성 요청을 변경해야 합니다. 요청은 대부분 동일하지만 SpeakTextAsync() 함수를 사용하는 대신 SpeakSsmlAsync()를 사용합니다. 이 함수에는 XML 문자열이 필요하므로 먼저 SSML 구성을 문자열로 로드합니다. 이 시점에서 결과 개체는 이전 예제와 정확히 동일합니다.

참고 항목

SSML을 사용하지 않고 음성을 설정하려면 speechConfig.SetSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural")을 사용하여 SpeechConfig에서 속성을 설정하면 됩니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 speech-synthesis.go 파일의 내용을 다음 Go 코드로 바꿀 수 있습니다.

package main

import (
    "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/speech"
)

func bookmarkReachedHandler(event speech.SpeechSynthesisBookmarkEventArgs) {
    defer event.Close()
    fmt.Println("BookmarkReached event")
}

func synthesisCanceledHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisCanceled event")
}

func synthesisCompletedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisCompleted event")
    fmt.Printf("\tAudioData: %d bytes\n", len(event.Result.AudioData))
    fmt.Printf("\tAudioDuration: %d\n", event.Result.AudioDuration)
}

func synthesisStartedHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("SynthesisStarted event")
}

func synthesizingHandler(event speech.SpeechSynthesisEventArgs) {
    defer event.Close()
    fmt.Println("Synthesizing event")
    fmt.Printf("\tAudioData %d bytes\n", len(event.Result.AudioData))
}

func visemeReceivedHandler(event speech.SpeechSynthesisVisemeEventArgs) {
    defer event.Close()
    fmt.Println("VisemeReceived event")
    fmt.Printf("\tAudioOffset: %dms\n", (event.AudioOffset+5000)/10000)
    fmt.Printf("\tVisemeID %d\n", event.VisemeID)
}

func wordBoundaryHandler(event speech.SpeechSynthesisWordBoundaryEventArgs) {
    defer event.Close()
    boundaryType := ""
    switch event.BoundaryType {
    case 0:
        boundaryType = "Word"
    case 1:
        boundaryType = "Punctuation"
    case 2:
        boundaryType = "Sentence"
    }
    fmt.Println("WordBoundary event")
    fmt.Printf("\tBoundaryType %v\n", boundaryType)
    fmt.Printf("\tAudioOffset: %dms\n", (event.AudioOffset+5000)/10000)
    fmt.Printf("\tDuration %d\n", event.Duration)
    fmt.Printf("\tText %s\n", event.Text)
    fmt.Printf("\tTextOffset %d\n", event.TextOffset)
    fmt.Printf("\tWordLength %d\n", event.WordLength)
}

func main() {
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    speechKey := os.Getenv("SPEECH_KEY")
    speechRegion := os.Getenv("SPEECH_REGION")

    audioConfig, err := audio.NewAudioConfigFromDefaultSpeakerOutput()
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer audioConfig.Close()
    speechConfig, err := speech.NewSpeechConfigFromSubscription(speechKey, speechRegion)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechConfig.Close()

    // Required for WordBoundary event sentences.
    speechConfig.SetProperty(common.SpeechServiceResponseRequestSentenceBoundary, "true")

    speechSynthesizer, err := speech.NewSpeechSynthesizerFromConfig(speechConfig, audioConfig)
    if err != nil {
        fmt.Println("Got an error: ", err)
        return
    }
    defer speechSynthesizer.Close()

    speechSynthesizer.BookmarkReached(bookmarkReachedHandler)
    speechSynthesizer.SynthesisCanceled(synthesisCanceledHandler)
    speechSynthesizer.SynthesisCompleted(synthesisCompletedHandler)
    speechSynthesizer.SynthesisStarted(synthesisStartedHandler)
    speechSynthesizer.Synthesizing(synthesizingHandler)
    speechSynthesizer.VisemeReceived(visemeReceivedHandler)
    speechSynthesizer.WordBoundary(wordBoundaryHandler)

    speechSynthesisVoiceName := "en-US-AvaMultilingualNeural"

    ssml := fmt.Sprintf(`<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
            <voice name='%s'>
                <mstts:viseme type='redlips_front'/>
                The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
            </voice>
        </speak>`, speechSynthesisVoiceName)

    // Synthesize the SSML
    fmt.Printf("SSML to synthesize: \n\t%s\n", ssml)
    task := speechSynthesizer.SpeakSsmlAsync(ssml)

    var outcome speech.SpeechSynthesisOutcome
    select {
    case outcome = <-task:
    case <-time.After(60 * time.Second):
        fmt.Println("Timed out")
        return
    }
    defer outcome.Close()
    if outcome.Error != nil {
        fmt.Println("Got an error: ", outcome.Error)
        return
    }

    if outcome.Result.Reason == common.SynthesizingAudioCompleted {
        fmt.Println("SynthesizingAudioCompleted result")
    } else {
        cancellation, _ := speech.NewCancellationDetailsFromSpeechSynthesisResult(outcome.Result)
        fmt.Printf("CANCELED: Reason=%d.\n", cancellation.Reason)

        if cancellation.Reason == common.Error {
            fmt.Printf("CANCELED: ErrorCode=%d\nCANCELED: ErrorDetails=[%s]\nCANCELED: Did you set the speech resource key and region values?\n",
                cancellation.ErrorCode,
                cancellation.ErrorDetails)
        }
    }
}

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 전체 목록을 가져오거나 음성 갤러리에서 사용해 볼 수 있습니다.

입력 텍스트와 일치하도록 SpeechConfig의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다. 다음 코드 조각은 이 기술의 작동 방식을 보여 줍니다.

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.setSpeechSynthesisLanguage("en-US"); 
    speechConfig.setSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");
}

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural로 설정하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

음성을 파일로 합성

SpeechSynthesizer 개체를 만듭니다. 이 개체는 텍스트 음성 변환을 실행하고 스피커, 파일 또는 기타 출력 스트림으로 출력합니다. SpeechSynthesizer는 다음을 매개 변수로 허용합니다.

  • 이전 단계에서 만든 SpeechConfig 개체
  • 출력 결과를 처리하는 방법을 지정하는 AudioConfig 개체
  1. 다음과 같이 fromWavFileOutput() 정적 함수를 사용하여 자동으로 .wav 파일에 출력을 기록하는 AudioConfig 인스턴스를 만듭니다.

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        AudioConfig audioConfig = AudioConfig.fromWavFileOutput("path/to/write/file.wav");
    }
    
  2. SpeechSynthesizer 인스턴스를 인스턴스화합니다. speechConfig 개체 및 audioConfig 개체를 매개 변수로 전달합니다. 음성을 합성하고 파일에 쓰려면 텍스트 문자열을 사용하여 SpeakText()를 실행합니다.

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        AudioConfig audioConfig = AudioConfig.fromWavFileOutput("path/to/write/file.wav");
    
        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
        speechSynthesizer.SpeakText("I'm excited to try text to speech");
    }
    

프로그램을 실행하면 사용자가 지정한 위치에 기록되는 합성된 .wav 파일이 만들어집니다. 이 결과는 가장 기본적인 사용법의 좋은 예입니다. 다음으로, 출력을 사용자 지정하고 출력 응답을 사용자 지정 시나리오에 잘 맞는 메모리 내 스트림으로 처리할 수 있습니다.

스피커 출력으로 합성

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

합성된 음성을 스피커와 같은 현재 활성 출력 디바이스로 출력하려면 fromDefaultSpeakerOutput() 정적 함수를 사용하여 AudioConfig를 인스턴스화합니다. 예를 들면 다음과 같습니다.

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    AudioConfig audioConfig = AudioConfig.fromDefaultSpeakerOutput();

    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    speechSynthesizer.SpeakText("I'm excited to try text to speech");
}

결과를 메모리 내 스트림으로 가져오기

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로, 먼저 AudioConfig 블록을 제거합니다. 그런 다음, SpeechSynthesizer 생성자에서 AudioConfig에 대해 null을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 null을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. SpeechSynthesisResult.getAudioData() 함수는 출력 데이터의 byte [] 인스턴스를 반환합니다. 이 byte [] 인스턴스를 수동으로 작업할 수도 있고, AudioDataStream 클래스를 사용하여 메모리 내 스트림을 관리할 수도 있습니다.

다음 예제에서는 AudioDataStream.fromResult() 정적 함수를 사용하여 결과에서 스트림을 가져옵니다.

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);

    SpeechSynthesisResult result = speechSynthesizer.SpeakText("I'm excited to try text to speech");
    AudioDataStream stream = AudioDataStream.fromResult(result);
    System.out.print(stream.getStatus());
}

이 시점에서 최종 stream 개체를 사용하여 사용자 지정 동작을 구현할 수 있습니다.

오디오 형식 사용자 지정

다음을 포함하여 오디오 출력 특성을 사용자 지정할 수 있습니다.

  • 오디오 파일 형식
  • 샘플 속도
  • 비트 수준

오디오 형식을 변경하려면 SpeechConfig 개체에서 setSpeechSynthesisOutputFormat() 함수를 사용합니다. 이 함수에는 SpeechSynthesisOutputFormat 형식의 enum 인스턴스가 필요합니다. enum을 사용하여 출력 형식을 선택합니다. 사용 가능한 형식은 오디오 형식 목록을 참조하세요.

요구 사항에 따라 다양한 파일 형식에 대한 다양한 옵션이 있습니다. 정의에 따라 Raw24Khz16BitMonoPcm과 같은 원시 형식에는 오디오 헤더가 포함되지 않습니다. 다음과 같은 상황에는 원시 형식만 사용합니다.

  • 다운스트림 구현에서 원시 비트스트림을 디코딩할 수 있다는 것을 알고 있습니다.
  • 비트 수준, 샘플 속도 및 채널 수와 같은 요인에 따라 헤더를 수동으로 빌드할 계획입니다.

이 예제에서는 SpeechConfig 개체에서 SpeechSynthesisOutputFormat을 설정하여 고화질 RIFF 형식인 Riff24Khz16BitMonoPcm을 지정합니다. 이전 섹션의 예제와 마찬가지로 AudioDataStream을 사용하여 결과의 메모리 내 스트림을 가져온 다음, 파일에 씁니다.

public static void main(String[] args) {
    SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");

    // set the output format
    speechConfig.setSpeechSynthesisOutputFormat(SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm);

    SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    SpeechSynthesisResult result = speechSynthesizer.SpeakText("I'm excited to try text to speech");
    AudioDataStream stream = AudioDataStream.fromResult(result);
    stream.saveToWavFile("path/to/write/file.wav");
}

이 프로그램을 실행하면 지정된 경로에 .wav 파일을 씁니다.

SSML을 사용하여 음성 특성 사용자 지정

SSML을 사용하여 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 텍스트 음성 변환 출력의 다른 측면을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 SSML 방법 문서를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

  1. SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다.

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    이 예제에서 파일은 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

  2. XML 파일을 참조하도록 음성 합성 요청을 변경합니다. 요청은 대부분 동일합니다. SpeakText() 함수를 사용하는 대신 SpeakSsml()를 사용합니다. 이 함수에는 XML 문자열이 필요하므로, 먼저 XML 파일을 로드하고 문자열로 반환하는 함수를 만듭니다.

    private static String xmlToString(String filePath) {
        File file = new File(filePath);
        StringBuilder fileContents = new StringBuilder((int)file.length());
    
        try (Scanner scanner = new Scanner(file)) {
            while(scanner.hasNextLine()) {
                fileContents.append(scanner.nextLine() + System.lineSeparator());
            }
            return fileContents.toString().trim();
        } catch (FileNotFoundException ex) {
            return "File not found.";
        }
    }
    

    이 시점에서 결과 개체는 이전 예제와 정확히 동일합니다.

    public static void main(String[] args) {
        SpeechConfig speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig, null);
    
        String ssml = xmlToString("ssml.xml");
        SpeechSynthesisResult result = speechSynthesizer.SpeakSsml(ssml);
        AudioDataStream stream = AudioDataStream.fromResult(result);
        stream.saveToWavFile("path/to/write/file.wav");
    }
    

참고 항목

SSML을 사용하지 않고 음성을 변경하려면 SpeechConfig.setSpeechSynthesisVoiceName("en-US-AvaMultilingualNeural");을 사용하여 SpeechConfig에서 속성을 설정합니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 SpeechSynthesis.java 파일의 내용을 다음 Java 코드로 바꿀 수 있습니다.

import com.microsoft.cognitiveservices.speech.*;
import com.microsoft.cognitiveservices.speech.audio.*;

import java.util.Scanner;
import java.util.concurrent.ExecutionException;

public class SpeechSynthesis {
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    private static String speechKey = System.getenv("SPEECH_KEY");
    private static String speechRegion = System.getenv("SPEECH_REGION");

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        SpeechConfig speechConfig = SpeechConfig.fromSubscription(speechKey, speechRegion);
        
        // Required for WordBoundary event sentences.
        speechConfig.setProperty(PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

        String speechSynthesisVoiceName = "en-US-AvaMultilingualNeural"; 
        
        String ssml = String.format("<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>"
            .concat(String.format("<voice name='%s'>", speechSynthesisVoiceName))
            .concat("<mstts:viseme type='redlips_front'/>")
            .concat("The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.")
            .concat("</voice>")
            .concat("</speak>"));

        SpeechSynthesizer speechSynthesizer = new SpeechSynthesizer(speechConfig);
        {
            // Subscribe to events

            speechSynthesizer.BookmarkReached.addEventListener((o, e) -> {
                System.out.println("BookmarkReached event:");
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tText: " + e.getText());
            });

            speechSynthesizer.SynthesisCanceled.addEventListener((o, e) -> {
                System.out.println("SynthesisCanceled event");
            });

            speechSynthesizer.SynthesisCompleted.addEventListener((o, e) -> {
                SpeechSynthesisResult result = e.getResult();                
                byte[] audioData = result.getAudioData();
                System.out.println("SynthesisCompleted event:");
                System.out.println("\tAudioData: " + audioData.length + " bytes");
                System.out.println("\tAudioDuration: " + result.getAudioDuration());
                result.close();
            });
            
            speechSynthesizer.SynthesisStarted.addEventListener((o, e) -> {
                System.out.println("SynthesisStarted event");
            });

            speechSynthesizer.Synthesizing.addEventListener((o, e) -> {
                SpeechSynthesisResult result = e.getResult();
                byte[] audioData = result.getAudioData();
                System.out.println("Synthesizing event:");
                System.out.println("\tAudioData: " + audioData.length + " bytes");
                result.close();
            });

            speechSynthesizer.VisemeReceived.addEventListener((o, e) -> {
                System.out.println("VisemeReceived event:");
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tVisemeId: " + e.getVisemeId());
            });

            speechSynthesizer.WordBoundary.addEventListener((o, e) -> {
                System.out.println("WordBoundary event:");
                System.out.println("\tBoundaryType: " + e.getBoundaryType());
                System.out.println("\tAudioOffset: " + ((e.getAudioOffset() + 5000) / 10000) + "ms");
                System.out.println("\tDuration: " + e.getDuration());
                System.out.println("\tText: " + e.getText());
                System.out.println("\tTextOffset: " + e.getTextOffset());
                System.out.println("\tWordLength: " + e.getWordLength());
            });

            // Synthesize the SSML
            System.out.println("SSML to synthesize:");
            System.out.println(ssml);
            SpeechSynthesisResult speechSynthesisResult = speechSynthesizer.SpeakSsmlAsync(ssml).get();

            if (speechSynthesisResult.getReason() == ResultReason.SynthesizingAudioCompleted) {
                System.out.println("SynthesizingAudioCompleted result");
            }
            else if (speechSynthesisResult.getReason() == ResultReason.Canceled) {
                SpeechSynthesisCancellationDetails cancellation = SpeechSynthesisCancellationDetails.fromResult(speechSynthesisResult);
                System.out.println("CANCELED: Reason=" + cancellation.getReason());

                if (cancellation.getReason() == CancellationReason.Error) {
                    System.out.println("CANCELED: ErrorCode=" + cancellation.getErrorCode());
                    System.out.println("CANCELED: ErrorDetails=" + cancellation.getErrorDetails());
                    System.out.println("CANCELED: Did you set the speech resource key and region values?");
                }
            }
        }
        speechSynthesizer.close();

        System.exit(0);
    }
}

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

사용자 지정 엔드포인트 사용

사용자 지정 엔드포인트는 텍스트 음성 변환 요청에 사용되는 표준 엔드포인트와 기능적으로 동일합니다.

한 가지 차이점은 Speech SDK를 통해 사용자 지정 음성을 사용하려면 EndpointId를 지정해야 한다는 것입니다. 텍스트 음성 변환 빠른 시작으로 시작한 다음 EndpointIdSpeechSynthesisVoiceName으로 코드를 업데이트할 수 있습니다.

SpeechConfig speechConfig = SpeechConfig.fromSubscription(speechKey, speechRegion);
speechConfig.setSpeechSynthesisVoiceName("YourCustomVoiceName");
speechConfig.setEndpointId("YourEndpointId");

SSML(Speech Synthesis Markup Language)을 통해 사용자 지정 음성을 사용하려면 모델 이름을 음성 이름으로 지정합니다. 이 예제에서는 YourCustomVoiceName 음성을 사용합니다.

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(npm) | GitHub의 추가 샘플 | 라이브러리 소스 코드

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 전체 목록을 가져오거나 음성 갤러리에서 사용해 볼 수 있습니다.

입력 텍스트와 일치하도록 SpeechConfig의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다.

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    // Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
    speechConfig.speechSynthesisLanguage = "en-US"; 
    speechConfig.speechSynthesisVoiceName = "en-US-AvaMultilingualNeural";
}

synthesizeSpeech();

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural로 설정하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

텍스트 음성 변환 합성

합성된 음성을 스피커와 같은 현재 활성 출력 디바이스로 출력하려면 fromDefaultSpeakerOutput() 정적 함수를 사용하여 AudioConfig를 인스턴스화합니다. 예를 들면 다음과 같습니다.

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const audioConfig = sdk.AudioConfig.fromDefaultSpeakerOutput();

    const speechSynthesizer = new SpeechSynthesizer(speechConfig, audioConfig);
    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            if (result) {
                speechSynthesizer.close();
                return result.audioData;
            }
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

이 프로그램을 실행하면 합성된 오디오가 스피커에서 재생됩니다. 이 결과는 가장 기본적인 사용법의 좋은 예입니다. 다음으로, 출력을 사용자 지정하고 출력 응답을 사용자 지정 시나리오에 잘 맞는 메모리 내 스트림으로 처리할 수 있습니다.

결과를 메모리 내 스트림으로 가져오기

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로 AudioConfig 블록을 제거합니다. 그런 다음, SpeechSynthesizer 생성자에서 AudioConfig에 대해 null을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 null을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. SpeechSynthesisResult.audioData 속성은 기본 브라우저 스트림 유형인 출력 데이터의 ArrayBuffer 값을 반환합니다. 서버 쪽 코드의 경우 ArrayBuffer를 버퍼 스트림으로 변환합니다.

다음 코드는 클라이언트 측에서 작동합니다.

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig);

    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            speechSynthesizer.close();
            return result.audioData;
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

최종 ArrayBuffer 개체를 사용하여 사용자 지정 동작을 구현할 수 있습니다. ArrayBuffer는 브라우저에서 수신하여 이 형식에서 재생할 수 있는 일반적인 유형입니다.

서버 기반 코드의 경우 데이터를 스트림으로 작업해야 할 때는 다음과 같이 ArrayBuffer 개체를 스트림으로 변환해야 합니다.

function synthesizeSpeech() {
    const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig);

    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            const { audioData } = result;

            speechSynthesizer.close();

            // convert arrayBuffer to stream
            // return stream
            const bufferStream = new PassThrough();
            bufferStream.end(Buffer.from(audioData));
            return bufferStream;
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

오디오 형식 사용자 지정

다음을 포함하여 오디오 출력 특성을 사용자 지정할 수 있습니다.

  • 오디오 파일 형식
  • 샘플 속도
  • 비트 수준

오디오 형식을 변경하려면 SpeechConfig 개체에서 speechSynthesisOutputFormat 속성을 사용합니다. 이 속성에는 SpeechSynthesisOutputFormat 형식의 enum 인스턴스가 필요합니다. enum을 사용하여 출력 형식을 선택합니다. 사용 가능한 형식은 오디오 형식 목록을 참조하세요.

요구 사항에 따라 다양한 파일 형식에 대한 다양한 옵션이 있습니다. 정의에 따라 Raw24Khz16BitMonoPcm과 같은 원시 형식에는 오디오 헤더가 포함되지 않습니다. 다음과 같은 상황에는 원시 형식만 사용합니다.

  • 다운스트림 구현에서 원시 비트스트림을 디코딩할 수 있다는 것을 알고 있습니다.
  • 비트 수준, 샘플 속도 및 채널 수와 같은 요인에 따라 헤더를 수동으로 빌드할 계획입니다.

이 예제에서는 SpeechConfig 개체에서 speechSynthesisOutputFormat을 설정하여 고화질 RIFF 형식인 Riff24Khz16BitMonoPcm을 지정합니다. 이전 섹션의 예제와 마찬가지로 오디오 ArrayBuffer 데이터를 가져와 상호 작용합니다.

function synthesizeSpeech() {
    const speechConfig = SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");

    // Set the output format
    speechConfig.speechSynthesisOutputFormat = sdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm;

    const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, null);
    speechSynthesizer.speakTextAsync(
        "I'm excited to try text to speech",
        result => {
            // Interact with the audio ArrayBuffer data
            const audioData = result.audioData;
            console.log(`Audio data byte size: ${audioData.byteLength}.`)

            speechSynthesizer.close();
        },
        error => {
            console.log(error);
            speechSynthesizer.close();
        });
}

SSML을 사용하여 음성 특성 사용자 지정

SSML을 사용하여 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 텍스트 음성 변환 출력의 다른 측면을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 Speech Synthesis Markup Language 개요를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

  1. SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다.

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    이 예제에서는 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

  2. XML 파일을 참조하도록 음성 합성 요청을 변경합니다. 요청은 대부분 동일하지만 speakTextAsync() 함수를 사용하는 대신 speakSsmlAsync()를 사용합니다. 이 함수에는 XML 문자열이 필요합니다. XML 파일을 로드하고 문자열로 반환하는 함수를 만듭니다.

    function xmlToString(filePath) {
        const xml = readFileSync(filePath, "utf8");
        return xml;
    }
    

    readFileSync에 대한 자세한 내용은 Node.js 파일 시스템을 참조하세요.

    결과 개체는 이전 예제와 정확히 동일합니다.

    function synthesizeSpeech() {
        const speechConfig = sdk.SpeechConfig.fromSubscription("YourSpeechKey", "YourSpeechRegion");
        const speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, null);
    
        const ssml = xmlToString("ssml.xml");
        speechSynthesizer.speakSsmlAsync(
            ssml,
            result => {
                if (result.errorDetails) {
                    console.error(result.errorDetails);
                } else {
                    console.log(JSON.stringify(result));
                }
    
                speechSynthesizer.close();
            },
            error => {
                console.log(error);
                speechSynthesizer.close();
            });
    }
    

참고 항목

SSML을 사용하지 않고 음성을 변경하려면 SpeechConfig.speechSynthesisVoiceName = "en-US-AvaMultilingualNeural";을 사용하여 SpeechConfig에서 속성을 설정하면 됩니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 SpeechSynthesis.js 파일의 내용을 다음 JavaScript 코드로 바꿀 수 있습니다.

(function() {

    "use strict";

    var sdk = require("microsoft-cognitiveservices-speech-sdk");

    var audioFile = "YourAudioFile.wav";
    // This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
    const speechConfig = sdk.SpeechConfig.fromSubscription(process.env.SPEECH_KEY, process.env.SPEECH_REGION);
    const audioConfig = sdk.AudioConfig.fromAudioFileOutput(audioFile);

    var speechSynthesisVoiceName  = "en-US-AvaMultilingualNeural";  
    var ssml = `<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'> \r\n \
        <voice name='${speechSynthesisVoiceName}'> \r\n \
            <mstts:viseme type='redlips_front'/> \r\n \
            The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>. \r\n \
        </voice> \r\n \
    </speak>`;
    
    // Required for WordBoundary event sentences.
    speechConfig.setProperty(sdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, "true");

    // Create the speech speechSynthesizer.
    var speechSynthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);

    speechSynthesizer.bookmarkReached = function (s, e) {
        var str = `BookmarkReached event: \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tText: \"${e.text}\".`;
        console.log(str);
    };

    speechSynthesizer.synthesisCanceled = function (s, e) {
        console.log("SynthesisCanceled event");
    };
    
    speechSynthesizer.synthesisCompleted = function (s, e) {
        var str = `SynthesisCompleted event: \
                    \r\n\tAudioData: ${e.result.audioData.byteLength} bytes \
                    \r\n\tAudioDuration: ${e.result.audioDuration}`;
        console.log(str);
    };

    speechSynthesizer.synthesisStarted = function (s, e) {
        console.log("SynthesisStarted event");
    };

    speechSynthesizer.synthesizing = function (s, e) {
        var str = `Synthesizing event: \
            \r\n\tAudioData: ${e.result.audioData.byteLength} bytes`;
        console.log(str);
    };
    
    speechSynthesizer.visemeReceived = function(s, e) {
        var str = `VisemeReceived event: \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tVisemeId: ${e.visemeId}`;
        console.log(str);
    };

    speechSynthesizer.wordBoundary = function (s, e) {
        // Word, Punctuation, or Sentence
        var str = `WordBoundary event: \
            \r\n\tBoundaryType: ${e.boundaryType} \
            \r\n\tAudioOffset: ${(e.audioOffset + 5000) / 10000}ms \
            \r\n\tDuration: ${e.duration} \
            \r\n\tText: \"${e.text}\" \
            \r\n\tTextOffset: ${e.textOffset} \
            \r\n\tWordLength: ${e.wordLength}`;
        console.log(str);
    };

    // Synthesize the SSML
    console.log(`SSML to synthesize: \r\n ${ssml}`)
    console.log(`Synthesize to: ${audioFile}`);
    speechSynthesizer.speakSsmlAsync(ssml,
        function (result) {
      if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
        console.log("SynthesizingAudioCompleted result");
      } else {
        console.error("Speech synthesis canceled, " + result.errorDetails +
            "\nDid you set the speech resource key and region values?");
      }
      speechSynthesizer.close();
      speechSynthesizer = null;
    },
        function (err) {
      console.trace("err - " + err);
      speechSynthesizer.close();
      speechSynthesizer = null;
    });
}());

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(다운로드) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

필수 조건

Speech SDK 및 샘플 설치

Azure-Samples/cognitive-services-speech-sdk 리포지토리에는 Objective-C로 작성된 iOS 및 Mac용 샘플이 포함되어 있습니다. 각 샘플에 대한 설치 지침을 보려면 링크를 선택합니다.

사용자 지정 엔드포인트 사용

사용자 지정 엔드포인트는 텍스트 음성 변환 요청에 사용되는 표준 엔드포인트와 기능적으로 동일합니다.

한 가지 차이점은 Speech SDK를 통해 사용자 지정 음성을 사용하려면 EndpointId를 지정해야 한다는 것입니다. 텍스트 음성 변환 빠른 시작으로 시작한 다음 EndpointIdSpeechSynthesisVoiceName으로 코드를 업데이트할 수 있습니다.

SPXSpeechConfiguration *speechConfig = [[SPXSpeechConfiguration alloc] initWithSubscription:speechKey region:speechRegion];
speechConfig.speechSynthesisVoiceName = @"YourCustomVoiceName";
speechConfig.EndpointId = @"YourEndpointId";

SSML(Speech Synthesis Markup Language)을 통해 사용자 지정 음성을 사용하려면 모델 이름을 음성 이름으로 지정합니다. 이 예제에서는 YourCustomVoiceName 음성을 사용합니다.

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(다운로드) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

필수 조건

Speech SDK 및 샘플 설치

Azure-Samples/cognitive-services-speech-sdk 리포지토리에는 Swift로 작성된 iOS 및 Mac용 샘플이 포함되어 있습니다. 각 샘플에 대한 설치 지침을 보려면 링크를 선택합니다.

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

참조 설명서 | 패키지(PyPi) | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

합성 언어 및 음성 선택

Speech Service의 텍스트 음성 변환 기능은 400개가 넘는 음성과 140개가 넘는 언어 및 변형을 지원합니다. 전체 목록을 가져오거나 음성 갤러리에서 사용해 볼 수 있습니다.

입력 텍스트와 일치하도록 SpeechConfig의 언어 또는 음성을 지정하고 지정된 음성을 사용합니다.

# Set either the `SpeechSynthesisVoiceName` or `SpeechSynthesisLanguage`.
speech_config.speech_synthesis_language = "en-US" 
speech_config.speech_synthesis_voice_name ="en-US-AvaMultilingualNeural"

모든 인공신경망 음성은 다국어이며 모국어와 영어를 유창하게 구사합니다. 예를 들어 영어로 된 입력 텍스트가 “I'm excited to try text to speech”이고 es-ES-ElviraNeural로 설정하면 텍스트를 스페인 악센트 영어로 읽습니다.

음성이 입력 텍스트의 언어를 말하지 않는 경우 음성 서비스에서 합성된 오디오를 만들지 않습니다. 지원되는 인공신경망 음성의 전체 목록은 Speech Service에 대한 언어 및 음성 지원을 참조하세요.

참고 항목

기본 음성은 Voice List API에서 로캘에 따라 반환되는 첫 번째 음성입니다.

다음과 같이 우선 순위에 따라 사용할 음성이 결정됩니다.

  • SpeechSynthesisVoiceName 또는 SpeechSynthesisLanguage를 설정하지 않으면 en-US의 기본 음성이 사용됩니다.
  • SpeechSynthesisLanguage만 설정하면 지정된 로캘의 기본 음성이 사용됩니다.
  • SpeechSynthesisVoiceNameSpeechSynthesisLanguage를 모두 설정하면 SpeechSynthesisLanguage 설정이 무시됩니다. SpeechSynthesisVoiceName을 사용하여 지정한 음성이 사용됩니다.
  • SSML(Speech Synthesis Markup Language)을 사용하여 음성 요소가 설정된 경우 SpeechSynthesisVoiceNameSpeechSynthesisLanguage 설정이 무시됩니다.

음성을 파일로 합성

SpeechSynthesizer 개체를 만듭니다. 이 개체는 텍스트 음성 변환을 실행하고 스피커, 파일 또는 기타 출력 스트림으로 출력합니다. SpeechSynthesizer는 다음을 매개 변수로 허용합니다.

  1. 다음과 같이 filename 생성자 매개 변수를 사용하여 자동으로 .wav 파일에 출력을 기록하는 AudioOutputConfig 인스턴스를 만듭니다.

    audio_config = speechsdk.audio.AudioOutputConfig(filename="path/to/write/file.wav")
    
  2. speech_config 개체와 audio_config 개체를 매개 변수로 전달하여 SpeechSynthesizer를 인스턴스화합니다. 음성을 합성하고 파일에 쓰려면 텍스트 문자열을 사용하여 speak_text_async()를 실행합니다.

    speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
    speech_synthesizer.speak_text_async("I'm excited to try text to speech")
    

프로그램을 실행하면 사용자가 지정한 위치에 기록되는 합성된 .wav 파일이 만들어집니다. 이 결과는 가장 기본적인 사용법의 좋은 예입니다. 다음으로, 출력을 사용자 지정하고 출력 응답을 사용자 지정 시나리오에 잘 맞는 메모리 내 스트림으로 처리할 수 있습니다.

스피커 출력으로 합성

합성된 음성을 스피커와 같은 현재 활성 출력 디바이스에 출력하려면 AudioOutputConfig 인스턴스를 만들 때 use_default_speaker 매개 변수를 설정합니다. 예를 들면 다음과 같습니다.

audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)

결과를 메모리 내 스트림으로 가져오기

결과 오디오 데이터를 파일에 직접 쓰는 대신 메모리 내 스트림으로 사용할 수 있습니다. 메모리 내 스트림을 사용하면 다음을 비롯한 사용자 지정 동작을 빌드할 수 있습니다.

  • 결과 바이트 배열을 사용자 지정 다운스트림 서비스에 대한 검색 가능한 스트림으로 추상화합니다.
  • 결과를 다른 API 또는 서비스와 통합합니다.
  • 오디오 데이터를 수정하고, 사용자 지정 .wav 헤더를 작성하고, 관련 작업을 수행합니다.

이전 예제를 이와 같이 변경할 수 있습니다. 제어를 향상하기 위해 이 시점부터 출력 동작을 수동으로 관리할 예정이므로, 먼저 AudioConfig를 제거합니다. SpeechSynthesizer 생성자에서 AudioConfig에 대해 None을 전달합니다.

참고 항목

이전 스피커 출력 예제와 같이 생략하는 대신 AudioConfig에 대해 None을 전달하면 현재 활성 출력 디바이스에서 기본적으로 오디오가 재생되지 않습니다.

결과를 SpeechSynthesisResult 변수에 저장합니다. audio_data 속성에는 출력 데이터의 bytes 개체가 포함되어 있습니다. 이 개체를 수동으로 사용하거나 AudioDataStream 클래스를 사용하여 메모리 내 스트림을 관리할 수 있습니다.

이 예제에서는 AudioDataStream 생성자를 사용하여 결과에서 스트림을 가져옵니다.

speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
stream = AudioDataStream(result)

이 시점에서 최종 stream 개체를 사용하여 사용자 지정 동작을 구현할 수 있습니다.

오디오 형식 사용자 지정

다음을 포함하여 오디오 출력 특성을 사용자 지정할 수 있습니다.

  • 오디오 파일 형식
  • 샘플 속도
  • 비트 수준

오디오 형식을 변경하려면 SpeechConfig 개체에서 set_speech_synthesis_output_format() 함수를 사용합니다. 이 함수에는 SpeechSynthesisOutputFormat 형식의 enum 인스턴스가 필요합니다. enum을 사용하여 출력 형식을 선택합니다. 사용 가능한 형식은 오디오 형식 목록을 참조하세요.

요구 사항에 따라 다양한 파일 형식에 대한 다양한 옵션이 있습니다. 정의에 따라 Raw24Khz16BitMonoPcm과 같은 원시 형식에는 오디오 헤더가 포함되지 않습니다. 다음과 같은 상황에는 원시 형식만 사용합니다.

  • 다운스트림 구현에서 원시 비트스트림을 디코딩할 수 있다는 것을 알고 있습니다.
  • 비트 수준, 샘플 속도 및 채널 수와 같은 요인에 따라 헤더를 수동으로 빌드할 계획입니다.

이 예제에서는 SpeechConfig 개체에서 SpeechSynthesisOutputFormat을 설정하여 고화질 RIFF 형식인 Riff24Khz16BitMonoPcm을 지정합니다. 이전 섹션의 예제와 마찬가지로 AudioDataStream을 사용하여 결과의 메모리 내 스트림을 가져온 다음, 파일에 씁니다.

speech_config.set_speech_synthesis_output_format(speechsdk.SpeechSynthesisOutputFormat.Riff24Khz16BitMonoPcm)
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)

result = speech_synthesizer.speak_text_async("I'm excited to try text to speech").get()
stream = speechsdk.AudioDataStream(result)
stream.save_to_wav_file("path/to/write/file.wav")

이 프로그램을 실행하면 지정된 경로에 .wav 파일을 씁니다.

SSML을 사용하여 음성 특성 사용자 지정

SSML을 사용하여 XML 스키마에서 요청을 제출하여 피치, 발음, 말하기 속도, 볼륨 및 텍스트 음성 변환 출력의 다른 측면을 미세 조정할 수 있습니다. 이 섹션에서는 음성을 변경하는 예제를 보여줍니다. 자세한 내용은 Speech Synthesis Markup Language 개요를 참조하세요.

SSML을 사용자 지정에 사용하려면 음성을 전환하는 간단한 변경 작업을 수행합니다.

  1. SSML 구성에 대한 새 XML 파일을 루트 프로젝트 디렉터리에 만듭니다.

    <speak version="1.0" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="en-US">
      <voice name="en-US-AvaMultilingualNeural">
        When you're on the freeway, it's a good idea to use a GPS.
      </voice>
    </speak>
    

    이 예제에서 파일은 ssml.xml입니다. 루트 요소는 항상 <speak>입니다. 텍스트를 name 요소에 래핑하면 <voice> 매개 변수를 사용하여 음성을 변경할 수 있습니다. 지원되는 인공신경망 음성의 전체 목록은 지원되는 언어를 참조하세요.

  2. XML 파일을 참조하도록 음성 합성 요청을 변경합니다. 요청은 대부분 동일합니다. speak_text_async() 함수를 사용하는 대신 speak_ssml_async()를 사용합니다. 이 함수에는 XML 문자열이 필요합니다. 먼저 SSML 구성을 문자열로 읽습니다. 이 시점에서 결과 개체는 이전 예제와 정확히 동일합니다.

    참고 항목

    문자열의 시작 부분에 ssml_string에 포함된 경우 BOM 형식을 제거해야 합니다. 그렇지 않으면 서비스에서 오류가 반환됩니다. 이렇게 하려면 encoding 매개 변수를 open("ssml.xml", "r", encoding="utf-8-sig")와 같이 설정합니다.

    speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=None)
    
    ssml_string = open("ssml.xml", "r").read()
    result = speech_synthesizer.speak_ssml_async(ssml_string).get()
    
    stream = speechsdk.AudioDataStream(result)
    stream.save_to_wav_file("path/to/write/file.wav")
    

참고 항목

SSML을 사용하지 않고 음성을 변경하려면 speech_config.speech_synthesis_voice_name = "en-US-AvaMultilingualNeural"을 사용하여 SpeechConfig에서 속성을 설정하면 됩니다.

신시사이저 이벤트 구독

텍스트 음성 변환 처리 및 결과에 대한 더 많은 인사이트가 필요할 수 있습니다. 예를 들어 신시사이저가 시작되고 중지되는 시기를 알려고 하거나 합성 중에 발생하는 다른 이벤트에 대해 알려고 할 수도 있습니다.

SpeechSynthesizer를 텍스트 음성 변환에 사용하는 동안 다음 표의 이벤트를 구독할 수 있습니다.

이벤트 설명 사용 사례
BookmarkReached 책갈피에 도달했음을 알립니다. 책갈피 도달 이벤트를 트리거하려면 SSMLbookmark 요소가 필요합니다. 이 이벤트는 합성 시작과 bookmark 요소 사이의 출력 오디오 경과 시간을 보고합니다. 이벤트의 Text 속성은 책갈피의 mark 특성에서 설정한 문자열 값입니다. bookmark 요소는 음성으로 읽히지 않습니다. 오디오 스트림에서 각 표식의 오프셋을 가져오기 위해 bookmark 요소를 사용하여 사용자 지정 표식을 SSML에 삽입할 수 있습니다. bookmark 요소는 텍스트 또는 태그 시퀀스의 특정 위치를 참조하는 데 사용할 수 있습니다.
SynthesisCanceled 음성 합성이 취소되었음을 알립니다. 합성이 취소된 시기를 확인할 수 있습니다.
SynthesisCompleted 음성 합성이 완료되었음을 알립니다. 합성이 완료된 시기를 확인할 수 있습니다.
SynthesisStarted 음성 합성이 시작되었음을 알립니다. 합성이 시작된 시기를 확인할 수 있습니다.
Synthesizing 음성 합성이 진행 중임을 알립니다. 이 이벤트는 SDK에서 Speech Service로부터 오디오 청크를 받을 때마다 발생합니다. 합성이 진행 중인 시기를 확인할 수 있습니다.
VisemeReceived viseme 이벤트가 수신되었음을 알립니다. Visemes는 관찰된 음성에서 주요 포즈를 나타내는 데 자주 사용됩니다. 주요 포즈에는 특정 음소 생성 시 입술, 턱 및 혀의 위치가 포함됩니다. 음성 오디오가 재생될 때 viseme을 사용하여 애니메이션을 캐릭터의 얼굴에 적용할 수 있습니다.
WordBoundary 단어 경계가 수신되었음을 알립니다. 이 이벤트는 각각의 새로운 음성, 문장 부호 및 문장의 시작 부분에서 발생합니다. 이 이벤트는 출력 오디오의 시작 부분에서 현재 단어의 시간 오프셋(틱 단위)을 보고합니다. 또한 이 이벤트는 말하려는 단어 바로 앞에 있는 입력 텍스트 또는 SSML의 문자 위치를 보고합니다. 이 이벤트는 일반적으로 텍스트와 해당 오디오의 상대적 위치를 가져오는 데 사용됩니다. 새 단어에 대해 알고 타이밍에 따라 작업을 수행하려고 할 수 있습니다. 예를 들어 단어를 말할 때 강조 표시할 시기와 기간을 결정하는 데 도움이 되는 정보를 얻을 수 있습니다.

참고 항목

이벤트는 출력 오디오 데이터를 사용할 수 있게 될 때 발생하며, 출력 디바이스로 재생하는 것보다 빠릅니다. 호출자는 스트리밍과 실시간을 적절하게 동기화해야 합니다.

다음은 음성 합성을 위해 이벤트를 구독하는 방법을 보여 주는 예제입니다. 빠른 시작의 지침을 따르지만 해당 speech-synthesis.py 파일의 내용을 다음 Python 코드로 바꿀 수 있습니다.

import os
import azure.cognitiveservices.speech as speechsdk

def speech_synthesizer_bookmark_reached_cb(evt: speechsdk.SessionEventArgs):
    print('BookmarkReached event:')
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tText: {}'.format(evt.text))

def speech_synthesizer_synthesis_canceled_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisCanceled event')

def speech_synthesizer_synthesis_completed_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisCompleted event:')
    print('\tAudioData: {} bytes'.format(len(evt.result.audio_data)))
    print('\tAudioDuration: {}'.format(evt.result.audio_duration))

def speech_synthesizer_synthesis_started_cb(evt: speechsdk.SessionEventArgs):
    print('SynthesisStarted event')

def speech_synthesizer_synthesizing_cb(evt: speechsdk.SessionEventArgs):
    print('Synthesizing event:')
    print('\tAudioData: {} bytes'.format(len(evt.result.audio_data)))

def speech_synthesizer_viseme_received_cb(evt: speechsdk.SessionEventArgs):
    print('VisemeReceived event:')
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tVisemeId: {}'.format(evt.viseme_id))

def speech_synthesizer_word_boundary_cb(evt: speechsdk.SessionEventArgs):
    print('WordBoundary event:')
    print('\tBoundaryType: {}'.format(evt.boundary_type))
    print('\tAudioOffset: {}ms'.format((evt.audio_offset + 5000) / 10000))
    print('\tDuration: {}'.format(evt.duration))
    print('\tText: {}'.format(evt.text))
    print('\tTextOffset: {}'.format(evt.text_offset))
    print('\tWordLength: {}'.format(evt.word_length))

# This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
speech_config = speechsdk.SpeechConfig(subscription=os.environ.get('SPEECH_KEY'), region=os.environ.get('SPEECH_REGION'))

# Required for WordBoundary event sentences.
speech_config.set_property(property_id=speechsdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, value='true')

audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)

# Subscribe to events
speech_synthesizer.bookmark_reached.connect(speech_synthesizer_bookmark_reached_cb)
speech_synthesizer.synthesis_canceled.connect(speech_synthesizer_synthesis_canceled_cb)
speech_synthesizer.synthesis_completed.connect(speech_synthesizer_synthesis_completed_cb)
speech_synthesizer.synthesis_started.connect(speech_synthesizer_synthesis_started_cb)
speech_synthesizer.synthesizing.connect(speech_synthesizer_synthesizing_cb)
speech_synthesizer.viseme_received.connect(speech_synthesizer_viseme_received_cb)
speech_synthesizer.synthesis_word_boundary.connect(speech_synthesizer_word_boundary_cb)

# The language of the voice that speaks.
speech_synthesis_voice_name='en-US-AvaMultilingualNeural'

ssml = """<speak version='1.0' xml:lang='en-US' xmlns='http://www.w3.org/2001/10/synthesis' xmlns:mstts='http://www.w3.org/2001/mstts'>
    <voice name='{}'>
        <mstts:viseme type='redlips_front'/>
        The rainbow has seven colors: <bookmark mark='colors_list_begin'/>Red, orange, yellow, green, blue, indigo, and violet.<bookmark mark='colors_list_end'/>.
    </voice>
</speak>""".format(speech_synthesis_voice_name)

# Synthesize the SSML
print("SSML to synthesize: \r\n{}".format(ssml))
speech_synthesis_result = speech_synthesizer.speak_ssml_async(ssml).get()

if speech_synthesis_result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted:
    print("SynthesizingAudioCompleted result")
elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled:
    cancellation_details = speech_synthesis_result.cancellation_details
    print("Speech synthesis canceled: {}".format(cancellation_details.reason))
    if cancellation_details.reason == speechsdk.CancellationReason.Error:
        if cancellation_details.error_details:
            print("Error details: {}".format(cancellation_details.error_details))
            print("Did you set the speech resource key and region values?")

GitHub에서 더 많은 텍스트 음성 변환 샘플을 찾을 수 있습니다.

사용자 지정 엔드포인트 사용

사용자 지정 엔드포인트는 텍스트 음성 변환 요청에 사용되는 표준 엔드포인트와 기능적으로 동일합니다.

한 가지 차이점은 Speech SDK를 통해 사용자 지정 음성을 사용하려면 endpoint_id를 지정해야 한다는 것입니다. 텍스트 음성 변환 빠른 시작으로 시작한 다음 endpoint_idspeech_synthesis_voice_name으로 코드를 업데이트할 수 있습니다.

speech_config = speechsdk.SpeechConfig(subscription=os.environ.get('SPEECH_KEY'), region=os.environ.get('SPEECH_REGION'))
speech_config.endpoint_id = "YourEndpointId"
speech_config.speech_synthesis_voice_name = "YourCustomVoiceName"

SSML(Speech Synthesis Markup Language)을 통해 사용자 지정 음성을 사용하려면 모델 이름을 음성 이름으로 지정합니다. 이 예제에서는 YourCustomVoiceName 음성을 사용합니다.

<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
    <voice name="YourCustomVoiceName">
        This is the text that is spoken. 
    </voice>
</speak>

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

음성을 텍스트로 변환하는 REST API 참조 | 짧은 오디오 참조를 위한 음성을 텍스트로 변환하는 REST API | GitHub의 추가 샘플

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

필수 조건

텍스트 음성 변환

명령 프롬프트에서 다음 명령을 실행합니다. 다음 값을 명령에 삽입합니다.

  • Speech 리소스 키
  • Speech 리소스 지역

다음 값을 변경할 수도 있습니다.

  • 오디오 출력 형식을 제어하는 X-Microsoft-OutputFormat 헤더 값입니다. 지원되는 오디오 출력 형식 목록은 텍스트 음성 변환 REST API 참조에서 찾을 수 있습니다.
  • 출력 음성입니다. Speech Service 엔드포인트에 사용할 수 있는 음성 목록을 가져오려면 음성 목록 API를 참조하세요.
  • 출력 파일입니다. 이 예제에서는 서버의 응답을 output.mp3라는 파일로 보냅니다.
curl --location --request POST 'https://YOUR_RESOURCE_REGION.tts.speech.microsoft.com/cognitiveservices/v1' \
--header 'Ocp-Apim-Subscription-Key: YOUR_RESOURCE_KEY' \
--header 'Content-Type: application/ssml+xml' \
--header 'X-Microsoft-OutputFormat: audio-16khz-128kbitrate-mono-mp3' \
--header 'User-Agent: curl' \
--data-raw '<speak version='\''1.0'\'' xml:lang='\''en-US'\''>
    <voice name='\''en-US-AvaMultilingualNeural'\''>
        I am excited to try text to speech
    </voice>
</speak>' > output.mp3

이 방법 가이드에서는 텍스트 음성 변환을 합성하기 위한 일반적인 디자인 패턴을 알아봅니다.

다음 영역에 대한 자세한 내용은 텍스트 음성 변환이란?을 참조하세요.

  • 메모리 내 스트림으로 응답 가져오기
  • 출력 샘플 속도 및 비트 전송률 사용자 지정
  • SSML(Speech Synthesis Markup Language)을 사용하여 합성 요청 제출
  • 인공신경망 음성 사용
  • 이벤트를 구독하고 결과에 따라 작동합니다.

필수 조건

다운로드 및 설치

다음 단계를 수행합니다. 플랫폼에 대한 추가 요구 사항은 음성 CLI 빠른 시작을 참조하세요.

  1. 다음 .NET CLI 명령을 실행하여 음성 CLI를 설치합니다.

    dotnet tool install --global Microsoft.CognitiveServices.Speech.CLI
    
  2. 다음 명령을 실행하여 음성 리소스 키 및 지역을 구성합니다. SUBSCRIPTION-KEY를 음성 리소스 키로 바꾸고, REGION을 음성 리소스 지역으로 바꿉니다.

    spx config @key --set SUBSCRIPTION-KEY
    spx config @region --set REGION
    

스피커로 음성 합성

이제 Speech CLI를 실행하여 텍스트에서 음성을 합성할 준비가 되었습니다.

  • 콘솔 창에서 Speech CLI 이진 파일이 포함된 디렉터리로 변경합니다. 그런 후 다음 명령을 실행합니다.

    spx synthesize --text "I'm excited to try text to speech"
    

Speech CLI는 컴퓨터 스피커를 통해 영어로 자연어를 생성합니다.

음성을 파일로 합성

  • 다음 명령을 실행하여 스피커의 출력을 .wav 파일로 변경합니다.

    spx synthesize --text "I'm excited to try text to speech" --audio output greetings.wav
    

Speech CLI는 greetings.wav 오디오 파일에 영어로 자연어를 생성합니다.

컨테이너 실행 및 사용

음성 컨테이너는 음성 SDK 및 음성 CLI를 통해 액세스되는 websocket 기반 쿼리 엔드포인트 API를 제공합니다. 기본적으로 음성 SDK 및 음성 CLI는 공개 음성 서비스를 사용합니다. 컨테이너를 사용하려면 초기화 메서드를 변경해야 합니다. 키 및 지역 대신 컨테이너 호스트 URL을 사용합니다.

컨테이너에 대한 자세한 내용은 Docker를 사용하여 음성 컨테이너 설치 및 실행을 참조하세요.

다음 단계