Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for Windows OneCore voices included in Windows 10. (PR #7110,…
… issue #6159) This uses a C++/CX dll to access the UWP SpeechSynthesizer class. There are other UWP APIs we might like to access in future (e.g. OCR), so rather than making this dll specific to OneCore speech, it's called nvdaHelperLocalWin10. The build system for this dll makes it easy to add other components in future. In addition, this required code to generate balanced XML from an NVDA speech sequence. Although we use SSML for eSpeak, eSpeak happily accepts unbalanced (malformed) XML. OneCore speech does not. This code is in the speechXml module. This might eventually be reused to replace the ugly balanced XML code in the SAPI5 driver. Note that NVDA can no longer be built with Visual Studio 2015 Express. You must use Visual Studio 2015 Community, as you also need the Windows 10 Tools and SDK. See the updated dependencies in the readme for details. Also, we now bundle the VC 2015 runtime, as some systems don't have it and it is needed for OneCore Speech.
- Loading branch information
Showing
14 changed files
with
1,138 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,3 +54,4 @@ EXPORTS | |
dllImportTableHooks_hookSingle | ||
dllImportTableHooks_unhookSingle | ||
audioDucking_shouldDelay | ||
logMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
/* | ||
Code for C dll bridge to Windows OneCore voices. | ||
This file is a part of the NVDA project. | ||
URL: http://www.nvaccess.org/ | ||
Copyright 2016-2017 Tyler Spivey, NV Access Limited. | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License version 2.0, as published by | ||
the Free Software Foundation. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
This license can be found at: | ||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
*/ | ||
|
||
#include <string> | ||
#include <collection.h> | ||
#include <ppltasks.h> | ||
#include <wrl.h> | ||
#include <robuffer.h> | ||
#include <common/log.h> | ||
#include "oneCoreSpeech.h" | ||
|
||
using namespace std; | ||
using namespace Platform; | ||
using namespace Windows::Media::SpeechSynthesis; | ||
using namespace concurrency; | ||
using namespace Windows::Storage::Streams; | ||
using namespace Microsoft::WRL; | ||
using namespace Windows::Media; | ||
using namespace Windows::Foundation::Collections; | ||
|
||
byte* getBytes(IBuffer^ buffer) { | ||
// We want direct access to the buffer rather than copying it. | ||
// To do this, we need to get to the IBufferByteAccess interface. | ||
// See http://cm-bloggers.blogspot.com/2012/09/accessing-image-pixel-data-in-ccx.html | ||
ComPtr<IInspectable> insp = reinterpret_cast<IInspectable*>(buffer); | ||
ComPtr<IBufferByteAccess> bufferByteAccess; | ||
if (FAILED(insp.As(&bufferByteAccess))) { | ||
LOG_ERROR(L"Couldn't get IBufferByteAccess from IBuffer"); | ||
return nullptr; | ||
} | ||
byte* bytes = nullptr; | ||
bufferByteAccess->Buffer(&bytes); | ||
return bytes; | ||
} | ||
|
||
OcSpeech* __stdcall ocSpeech_initialize() { | ||
auto instance = new OcSpeech; | ||
instance->synth = ref new SpeechSynthesizer(); | ||
return instance; | ||
} | ||
|
||
void __stdcall ocSpeech_terminate(OcSpeech* instance) { | ||
delete instance; | ||
} | ||
|
||
void __stdcall ocSpeech_setCallback(OcSpeech* instance, ocSpeech_Callback fn) { | ||
instance->callback = fn; | ||
} | ||
|
||
void __stdcall ocSpeech_speak(OcSpeech* instance, char16 *text) { | ||
String^ textStr = ref new String(text); | ||
auto markersStr = make_shared<wstring>(); | ||
task<SpeechSynthesisStream ^> speakTask; | ||
try { | ||
speakTask = create_task(instance->synth->SynthesizeSsmlToStreamAsync(textStr)); | ||
} catch (Platform::Exception ^e) { | ||
LOG_ERROR(L"Error " << e->HResult << L": " << e->Message->Data()); | ||
instance->callback(NULL, 0, NULL); | ||
return; | ||
} | ||
speakTask.then([markersStr] (SpeechSynthesisStream^ speechStream) { | ||
// speechStream->Size is 64 bit, but Buffer can only take 32 bit. | ||
// We shouldn't get values above 32 bit in reality. | ||
const unsigned int size = static_cast<unsigned int>(speechStream->Size); | ||
Buffer^ buffer = ref new Buffer(size); | ||
IVectorView<IMediaMarker^>^ markers = speechStream->Markers; | ||
for (auto&& marker : markers) { | ||
if (markersStr->length() > 0) { | ||
*markersStr += L"|"; | ||
} | ||
*markersStr += marker->Text->Data(); | ||
*markersStr += L":"; | ||
*markersStr += to_wstring(marker->Time.Duration); | ||
} | ||
auto t = create_task(speechStream->ReadAsync(buffer, size, Windows::Storage::Streams::InputStreamOptions::None)); | ||
return t; | ||
}).then([instance, markersStr] (IBuffer^ buffer) { | ||
// Data has been read from the speech stream. | ||
// Pass it to the callback. | ||
byte* bytes = getBytes(buffer); | ||
instance->callback(bytes, buffer->Length, markersStr->c_str()); | ||
}).then([instance] (task<void> previous) { | ||
// Catch any unhandled exceptions that occurred during these tasks. | ||
try { | ||
previous.get(); | ||
} catch (Platform::Exception^ e) { | ||
LOG_ERROR(L"Error " << e->HResult << L": " << e->Message->Data()); | ||
instance->callback(NULL, 0, NULL); | ||
} | ||
}); | ||
} | ||
|
||
// We use BSTR because we need the string to stay around until the caller is done with it | ||
// but the caller then needs to free it. | ||
// We can't just use malloc because the caller might be using a different CRT | ||
// and calling malloc and free from different CRTs isn't safe. | ||
BSTR __stdcall ocSpeech_getVoices(OcSpeech* instance) { | ||
wstring voices; | ||
for (unsigned int i = 0; i < instance->synth->AllVoices->Size; ++i) { | ||
VoiceInformation^ info = instance->synth->AllVoices->GetAt(i); | ||
voices += info->Id->Data(); | ||
voices += L":"; | ||
voices += info->DisplayName->Data(); | ||
if (i != instance->synth->AllVoices->Size - 1) { | ||
voices += L"|"; | ||
} | ||
} | ||
return SysAllocString(voices.c_str()); | ||
} | ||
|
||
const char16* __stdcall ocSpeech_getCurrentVoiceId(OcSpeech* instance) { | ||
return instance->synth->Voice->Id->Data(); | ||
} | ||
|
||
void __stdcall ocSpeech_setVoice(OcSpeech* instance, int index) { | ||
instance->synth->Voice = instance->synth->AllVoices->GetAt(index); | ||
} | ||
|
||
const char16 * __stdcall ocSpeech_getCurrentVoiceLanguage(OcSpeech* instance) { | ||
return instance->synth->Voice->Language->Data(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
/* | ||
Header for C dll bridge to Windows OneCore voices. | ||
This file is a part of the NVDA project. | ||
URL: http://www.nvaccess.org/ | ||
Copyright 2016-2017 Tyler Spivey, NV Access Limited. | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License version 2.0, as published by | ||
the Free Software Foundation. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
This license can be found at: | ||
http://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
*/ | ||
|
||
#pragma once | ||
#define export __declspec(dllexport) | ||
|
||
typedef void (*ocSpeech_Callback)(byte* data, int length, const char16* markers); | ||
typedef struct { | ||
Windows::Media::SpeechSynthesis::SpeechSynthesizer ^synth; | ||
ocSpeech_Callback callback; | ||
} OcSpeech; | ||
|
||
extern "C" { | ||
export OcSpeech* __stdcall ocSpeech_initialize(); | ||
export void __stdcall ocSpeech_terminate(OcSpeech* instance); | ||
export void __stdcall ocSpeech_setCallback(OcSpeech* instance, ocSpeech_Callback fn); | ||
export void __stdcall ocSpeech_speak(OcSpeech* instance, char16 *text); | ||
export BSTR __stdcall ocSpeech_getVoices(OcSpeech* instance); | ||
export const char16* __stdcall ocSpeech_getCurrentVoiceId(OcSpeech* instance); | ||
export void __stdcall ocSpeech_setVoice(OcSpeech* instance, int index); | ||
export const char16* __stdcall ocSpeech_getCurrentVoiceLanguage(OcSpeech* instance); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
### | ||
#This file is a part of the NVDA project. | ||
#URL: http://www.nvaccess.org/ | ||
#Copyright 2016-2017 NV Access Limited | ||
#This program is free software: you can redistribute it and/or modify | ||
#it under the terms of the GNU General Public License version 2.0, as published by | ||
#the Free Software Foundation. | ||
#This program is distributed in the hope that it will be useful, | ||
#but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
#This license can be found at: | ||
#http://www.gnu.org/licenses/old-licenses/gpl-2.0.html | ||
### | ||
|
||
import os | ||
|
||
Import( | ||
'env', | ||
'sourceDir', | ||
'libInstallDir', | ||
'localLib', | ||
) | ||
|
||
TARGET_ARCH=env['TARGET_ARCH'] | ||
debug=env['nvdaHelperDebugFlags'] | ||
release=env['release'] | ||
signExec=env['signExec'] if env['certFile'] else None | ||
progFilesX86 = os.getenv("ProgramFiles(x86)") | ||
|
||
env.Append(CPPDEFINES=[ | ||
'UNICODE', '_CRT_SECURE_NO_DEPRECATE', | ||
('LOGLEVEL','${nvdaHelperLogLevel}')]) | ||
env.Append(CCFLAGS=['/W3', '/WX']) | ||
env.Append(CXXFLAGS=['/EHsc', '/ZW', | ||
r'/AI%s\Microsoft Visual Studio 14.0\VC\vcpackages' % progFilesX86, | ||
r'/AI%s\Windows Kits\10\UnionMetadata' % progFilesX86]) | ||
env.Append(CPPPATH=[Dir('..').abspath]) | ||
env.Append(LINKFLAGS=['/incremental:no', '/WX']) | ||
env.Append(LINKFLAGS='/release') # We always want a checksum in the header | ||
|
||
if not release: | ||
env.Append(CCFLAGS=['/Od']) | ||
else: | ||
env.Append(CCFLAGS='/O2') | ||
env.Append(CCFLAGS='/GL') | ||
env.Append(LINKFLAGS=['/LTCG']) | ||
|
||
if 'RTC' in debug: | ||
env.Append(CCFLAGS=['/RTCsu']) | ||
|
||
# We always want debug symbols | ||
env.Append(PDB='${TARGET}.pdb') | ||
env.Append(LINKFLAGS='/OPT:REF') #having symbols usually turns this off but we have no need for unused symbols | ||
|
||
localWin10Lib = env.SharedLibrary( | ||
target="nvdaHelperLocalWin10", | ||
source=[ | ||
env['projectResFile'], | ||
'oneCoreSpeech.cpp', | ||
], | ||
LIBS=["oleaut32", localLib[2]], | ||
) | ||
if signExec: | ||
env.AddPostAction(localWin10Lib[0], [signExec]) | ||
env.Install(libInstallDir, localWin10Lib) | ||
|
||
# UWP dlls can only be dynamically linked with the CRT, | ||
# but some systems might not have this version of the CRT. | ||
# Therefore, we must include it. | ||
vcRedist = os.path.join(progFilesX86, r"Microsoft Visual Studio 14.0\VC\redist\onecore\x86\Microsoft.VC140.CRT") | ||
for fn in ("msvcp140.dll", "vccorlib140.dll", "vcruntime140.dll"): | ||
fn = os.path.join(vcRedist, fn) | ||
env.Install(sourceDir, fn) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.