Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 10개 있습니다.)
.NET Framework: 388. 일반 닷넷 프로젝트에서 WinRT API를 호출하는 방법
; https://www.sysnet.pe.kr/2/0/1508

.NET Framework: 613. 윈도우 데스크톱 응용 프로그램(예: Console)에서 알림 메시지(Toast notifications) 띄우기
; https://www.sysnet.pe.kr/2/0/11073

.NET Framework: 623. C# - PeerFinder를 이용한 Wi-Fi Direct 데이터 통신 예제
; https://www.sysnet.pe.kr/2/0/11106

.NET Framework: 678. 데스크톱 윈도우 응용 프로그램에서 UWP 라이브러리를 이용한 비디오 장치 열람하는 방법
; https://www.sysnet.pe.kr/2/0/11284

.NET Framework: 715. C# - Windows 10 운영체제의 데스크톱 앱에서 TTS(SpeechSynthesizer) 사용하는 방법
; https://www.sysnet.pe.kr/2/0/11412

.NET Framework: 722. C# - Windows 10 운영체제의 데스크톱 앱에서 음성인식(SpeechRecognizer) 사용하는 방법
; https://www.sysnet.pe.kr/2/0/11420

.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법
; https://www.sysnet.pe.kr/2/0/11799

.NET Framework: 852. WPF/WinForm에서 UWP의 기능을 이용해 Bluetooth 기기와 Pairing하는 방법
; https://www.sysnet.pe.kr/2/0/12001

.NET Framework: 991. .NET 5 응용 프로그램에서 WinRT API 호출
; https://www.sysnet.pe.kr/2/0/12470

닷넷: 2157. C# - WinRT 기능을 이용해 윈도우에서 실행 중인 Media App 제어
; https://www.sysnet.pe.kr/2/0/13438




C# - Windows 10 운영체제의 데스크톱 앱에서 TTS(SpeechSynthesizer) 사용하는 방법

(업데이트: 2023-03-30) 이 글에 대한 질문은 더 이상 받지 않습니다. (하시다 보면, 제가 왜 이 기술에 대해 흥미를 못 느끼는 지 아시게 될 것입니다. ^^ SpeechRecognitionEngine에 특별한 업데이트가 없는 한 다루지 않을 것입니다.)




예전에도 이야기했지만,

데스크톱 윈도우 응용 프로그램에서 UWP 라이브러리를 이용한 비디오 장치 열람하는 방법
; https://www.sysnet.pe.kr/2/0/11284

데스크톱 응용 프로그램에서도 UWP 앱에 제공되는 타입들을 사용할 수 있습니다. 이번에는 그중에서 TTS(Text-to-speech) 기능을 제공하는 SpeechSynthesizer 타입을 알아보겠습니다.

SpeechSynthesizer Class (Namespace: Windows.Media.SpeechSynthesis)
; https://learn.microsoft.com/en-us/uwp/api/windows.media.speechsynthesis.speechsynthesizer

* 최소 요구 사항: Windows 10(v10.0.10240.0)

이 타입을 사용하는 예제 코드는 마이크로소프트의 UWP 앱 샘플에서,

Speech recognition and synthesis sample
; https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/SpeechRecognitionAndSynthesis

Windows-universal-samples/Samples/SpeechRecognitionAndSynthesis/cs/
; https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/SpeechRecognitionAndSynthesis/cs

.\Samples\SpeechRecognitionAndSynthesis\cs\SpeechAndTTS.sln 솔루션 파일을 열어 테스트할 수 있습니다.




자, 그럼 사용해 볼까요? ^^

우선 SpeechSynthesizer 정의를 비롯해 UWP의 기본 타입들을 담고 있는 라이브러리를 먼저 참조해야 합니다. 원래 UWP 용으로는 다음과 같은 식의 경로에 있는 Windows.Foundation.UniversalApiContract.winmd 파일을 참조하는데,

C:\Program Files (x86)\Windows Kits\10\References\10.0.16299.0\Windows.Foundation.UniversalApiContract\5.0.0.0\Windows.Foundation.UniversalApiContract.winmd

데스크톱 응용 프로그램은 저 파일을 참조해서는 안되고 대신 다음의 파일 2개를 참조합니다.

C:\Program Files (x86)\Windows Kits\10\UnionMetadata\Windows.winmd
C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll

그럼 소스 코드에서 다음과 같이 SpeechSynthesizer 클래스를 생성하고, 텍스트가 음성으로 바뀌어 출력될 Stream을 얻을 수 있습니다.

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Windows.ApplicationModel.Resources.Core;
using Windows.Media.SpeechSynthesis;

namespace Listener10
{
    public partial class MainWindow : Window
    {
        private SpeechSynthesizer synthesizer = new SpeechSynthesizer();

        public MainWindow()
        {
            InitializeComponent();

            Speak("test is good");
        }

        private async void Speak(string text)
        {
            if (!string.IsNullOrEmpty(text))
            {
                try
                {
                    SpeechSynthesisStream synthesisStream = await synthesizer.SynthesizeTextToStreamAsync(text);
                }
                catch (Exception e1)
                {
                    System.Diagnostics.Trace.WriteLine(e1.ToString());
                }
            }
        }
    }
}

문제는 여기서부터입니다. 음성이 출력될 synthesisStream을 재생해야 하는데 UWP 앱 예제에서는 이를 위해 (Windows.UI.Xaml.Controls.)MediaPlayerElement 타입을 사용합니다. 그래서 UWP 앱에서와 같이 WPF의 xaml에서 이를 추가하면,

<Grid>
    <MediaPlayerElement x:Name="media"/>
</Grid>

오류 선이 그어지면서 다음과 같은 메시지를 볼 수 있습니다.

MediaPlayerElement is not supported in a Windows Presentation Foundation (WPF) project.

또는 웹 상에서 검색하면 (Windows.UI.Xaml.Controls.)MediaElement 타입을 사용하는 예제를 볼 수 있는데,

<Grid>
    <MediaElement x:Name="media"/>
</Grid>

mediaElement.SetSource(stream, synthesisStream.ContentType);
mediaElement.Play();

역시 WPF에서는 사용할 수 없습니다. 문제는 UWP 앱의 컨트롤들이 WPF 구조와는 맞지 않기 때문인데 이러한 제약은 UI 컨트롤이 아니면 해당하지 않습니다. 따라서 (Windows.Media.Playback.)MediaPlayer 타입을 이용해 다음과 같이 직접 음성 출력을 해야 합니다.

Windows.Media.Playback.MediaPlayer mp = new Windows.Media.Playback.MediaPlayer();
mp.SetStreamSource(synthesisStream);
mp.Play();

또는 (System.Media.)SoundPlayer 타입을 이용해서도 가능합니다.

System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
sp.Stream = = synthesisStream.AsStream();
sp.Play();




참고로 SSML 구문을 사용하면 보다 더 세밀한 음성 제어가 가능합니다. "Windows-universal-samples/Samples/SpeechRecognitionAndSynthesis/cs/" 예제에 보면 다음과 같은 SSML 텍스트가 리소스로 포함되어 있는데,

<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' 
                             xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 
                             xsi:schemaLocation='http://www.w3.org/2001/10/synthesis  http://www.w3.org/TR/speech-synthesis/synthesis.xsd' 
                             xml:lang='en-US'>

<mark name='phonetic'/>This is an example of a phonetic pronunciation:
<phoneme alphabet='x-microsoft-ups' ph='S1 W AA T . CH AX . M AX . S2 K AA L . IH T'> whatchamacallit </phoneme>.

<mark name='date'/>This is an example of a date:
<say-as interpret-as='date' format='mdy'> 04/30/2013 </say-as>.

<mark name='number'/>This is an example of an ordinal number:
<say-as interpret-as='ordinal'> 4 </say-as>.
<mark name='end'/>
</speak>

보는 바와 같이 발음 기호, 날짜, 숫자를 인식하도록 출력을 제어할 수 있습니다. 이를 위해 SynthesizeTextToStreamAsync가 아닌 SynthesizeSsmlToStreamAsync 메서드를 이용해 출력해야 합니다.

SpeechSynthesisStream synthesisStream = await synthesizer.SynthesizeTextToStreamAsync(text);

Windows.Media.Playback.MediaPlayer mp = new Windows.Media.Playback.MediaPlayer();
mp.SetStreamSource(synthesisStream);
mp.Play();

정리해 보면, 다음의 코드는 첨부 파일에 포함된 예제 프로젝트의 xaml, xaml.cs 내용입니다.

<Window x:Class="Listener10.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Listener10"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ListBox x:Name="voiceList">
        </ListBox>
    </Grid>
</Window>

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Windows.Media.SpeechSynthesis;

namespace Listener10
{
    public partial class MainWindow : Window
    {
        private SpeechSynthesizer _synthesizer = new SpeechSynthesizer();

        public MainWindow()
        {
            InitializeComponent();

            InitializeListboxVoiceChooser();

            Speak();
        }

        private async void Speak()
        {
            string text = "test is good";
            if (!string.IsNullOrEmpty(text))
            {
                try
                {
                    SpeechSynthesisStream synthesisStream = await _synthesizer.SynthesizeTextToStreamAsync(text);
                    // SpeechSynthesisStream synthesisStream = await synthesizer.SynthesizeSsmlToStreamAsync(Listener10.Properties.Resources.TTSText);

                    //Windows.Media.Playback.MediaPlayer mp = new Windows.Media.Playback.MediaPlayer();
                    //mp.SetStreamSource(synthesisStream);
                    //mp.Play();

                    System.Media.SoundPlayer sp = new System.Media.SoundPlayer();
                    sp.Stream = synthesisStream.AsStream();
                    sp.Play();
                }
                catch (Exception e)
                {
                    System.Diagnostics.Trace.WriteLine(e.ToString());
                }
            }
        }

        private void InitializeListboxVoiceChooser()
        {
            var voices = SpeechSynthesizer.AllVoices;

            VoiceInformation currentVoice = _synthesizer.Voice;

            foreach (VoiceInformation voice in voices.OrderBy(p => p.Language))
            {
                ListBoxItem item = new ListBoxItem();
                item.Content = voice.DisplayName + " (Language: " + voice.Language + ")";
                voiceList.Items.Add(item);

                if (currentVoice.Id == voice.Id)
                {
                    item.IsSelected = true;
                    voiceList.SelectedItem = item;
                }
            }
        }
    }
}




그럼 오류 유형을 한번 알아볼까요? ^^

(Windows.UI.Xaml.Controls.)MediaElement나 (Windows.UI.Xaml.Controls.)MediaPlayerElement가 .xaml 파일에서 생성할 수 없기 때문에 .xaml.cs에서 new를 이용해 생성하려고 시도하면 다음과 같은 예외가 발생합니다.

System.Windows.Controls.MediaElement me = new System.Windows.Controls.MediaElement();

/*

{"The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))"}

*/

분명히 UI 스레드인데도 불구하고 저런 오류가 발생합니다. 실제로 UWP의 Dispatcher 역시 WPF에서 사용하게 되면 0x8000000e 예외가 발생합니다.

Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
    // Your UI update code goes here!
});

{"A method was called at an unexpected time.\r\n\r\nCould not create a new view because the main window has not yet been created"}

왜냐하면 WPF의 UI 엔진이 UWP의 UI 엔진과 호환하지 않기 때문입니다. 이 외에도 UWP에서만 가능한 구성 요소들은 WPF에서 사용할 수 없습니다. 안 그러면 다음과 같은 식의 예외가 발생합니다.

ResourceContext context = ResourceContext.GetForCurrentView();

{"Unsupported MRT profile type. (Exception from HRESULT: 0x80073B20)"}




MediaElement 타입의 경우 WPF에서 System.Windows.Controls 네임스페이스를 통해 제공되긴 합니다. 하지만 UWP의 것과 완전히 다르기 때문에 UWP 예제에 나온 식으로 사용할 수 없습니다. 가령 UWP의 Windows.UI.Xaml.Controls.MediaElement의 경우 IRandomAccessStream을 인자로 받는 SetSource 메서드가 있지만 WPF에서 제공하는 System.Windows.Controls.MediaElement에는 Uri 타입만을 받는 Source 속성이 있을 뿐입니다.




참고로, Windows 10 미만의 버전이라면 System.Speech.Synthesis.SpeechSynthesizer를 사용하는 것도 방법입니다.

C#으로 만드는 음성인식/TTS 프로그램
; https://www.sysnet.pe.kr/2/0/1228





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/30/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2017-12-20 04시02분
음성인식/TTS를 위해서는 환경 설정이 필요합니다.

윈도우 10 - TTS 및 음성 인식을 위한 환경 설정
; http://www.sysnet.pe.kr/2/0/11413

(2017-12-21 기준으로 아직 한글은 음성 인식을 지원하지 않습니다.)
정성태

... 151  152  153  154  155  156  157  158  159  [160]  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1048정성태5/27/201132186개발 환경 구성: 123. Apache 소스를 윈도우 환경에서 빌드하기
1047정성태5/27/201126055.NET Framework: 217. Firebird ALinq Provider - 날짜 필드에 대한 낙관적 동시성 쿼리 오류
1046정성태5/26/201130685.NET Framework: 216. 라이선스까지도 뛰어넘는 .NET Profiler [5]
1045정성태5/24/201131781.NET Framework: 215. 닷넷 System.ComponentModel.LicenseManager를 이용한 라이선스 적용 [1]파일 다운로드1
1044정성태5/24/201132339오류 유형: 122. zlib 빌드 오류 - inflate.obj : error LNK2001: unresolved external symbol _inflate_fast
1043정성태5/24/201131274.NET Framework: 214. 무료 Linq Provider - DbLinq를 이용한 Firebird 접근파일 다운로드1
1042정성태5/23/201137627개발 환경 구성: 122. PHP 소스를 윈도우 환경에서 빌드하기
1041정성태5/22/201128551.NET Framework: 213. Linq To SQL - ALinq Provider를 이용하여 Firebird 사용파일 다운로드1
1040정성태5/21/201138893개발 환경 구성: 121. .NET 개발자가 처음 설치해 본 Apache + PHP [2]
1039정성태5/17/201131584.NET Framework: 212. Firebird 데이터베이스와 ADO.NET [2]파일 다운로드1
1038정성태5/16/201133540개발 환경 구성: 120. .NET 프로그래머에게도 유용한 Firebird 무료 데이터베이스 [2]
1037정성태5/11/201128381개발 환경 구성: 119. Visual Studio Professional 이하 버전에서도 TFS의 정적 코드 분석 정책 연동이 가능할까? [3]
1036정성태5/7/201194222오류 유형: 121. Access DB에 대한 32bit/64bit OLE DB Provider 관련 오류 [11]
1035정성태5/7/201128910오류 유형: 120. File cannot be opened. Ensure it is a valid Data Link file.
1034정성태5/2/201125989.NET Framework: 211. 파일 잠금 없이 .NET 어셈블리의 버전을 구하는 방법 [2]파일 다운로드1
1033정성태5/1/201131716웹: 19. IIS Express - appcmd.exe를 이용한 applicationHost.config 변경 [2]
1032정성태5/1/201128345웹: 18. IIS Express를 NT 서비스로 변경
1031정성태4/30/201129505웹: 17. IIS Express - "IIS Installed Versions Manager Interface"의 IIISExpressProcessUtility 구하는 방법 [1]파일 다운로드1
1030정성태4/30/201151783개발 환경 구성: 118. IIS Express - localhost 이외의 호스트 이름으로 접근하는 방법 [4]파일 다운로드1
1029정성태4/28/201140896개발 환경 구성: 117. XCopy에서 파일/디렉터리 확인 질문 없애기 [2]
1028정성태4/27/201138278오류 유형: 119. Visual Studio 2010 SP1 설치 후 Windows Phone 개발자 도구로 인한 재설치 문제 [3]
1027정성태4/25/201127458디버깅 기술: 40. 상황별 GetFunctionPointer 반환값 정리 - x86파일 다운로드1
1026정성태4/25/201145727디버깅 기술: 39. DebugDiag 1.1을 사용한 덤프 분석 [7]
1025정성태4/24/201127807개발 환경 구성: 116. IIS 7 관리자 - Active Directory Certification Authority로부터 SSL 사이트 인증서 받는 방법 [2]
1024정성태4/22/201129182오류 유형: 118. Windows 2008 서버에서 Event Viewer / PowerShell 실행 시 비정상 종료되는 문제 [1]
1023정성태4/20/201130044.NET Framework: 210. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 [1]
... 151  152  153  154  155  156  157  158  159  [160]  161  162  163  164  165  ...