65.9K
CodeProject 正在变化。 阅读更多。
Home

audio_ostream - 文本到语音的 ostream

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (25投票s)

2007 年 3 月 6 日

CPOL

7分钟阅读

viewsIcon

191385

downloadIcon

5326

一篇关于如何使用 ostream 接口将文本转语音添加到应用程序的文章

Screenshot - audio_ostream.png

引言

在这篇文章中,我将向您展示如何为您的程序添加文本转语音 (TTS) 功能。

您将能够使用熟悉的标准 ostream 语法,基本上用一行代码就能完成。

此外,我还将展示如何使用开源 C++ 工具使您的代码更简短(我所有的代码不到 50 行)、更可靠、更健壮,并且比原始 API 更通用。

我将展示什么

  • 如何为您的程序添加简单的 TTS。
  • 简单使用 COMSTL 和各种其他 STLSoft 组件。
  • 一个如何使用 boost::iostreams 的简单示例

背景

我最近不得不在一个程序(在 Windows 上运行)中添加音频输出。

Microsoft 的 SAPI SDK 提供了一个 COM 接口,可以通过该接口使用 SAPI 的 TTS 引擎朗读宽字符字符串。Code Project 有许多文章解释了如何使用 SAPI,其复杂程度各不相同。那么为什么还要再写一篇呢?

嗯,我想要的一些附加功能在那些文章中并没有实现。

  1. 尽量减少或不进行 COM 操作。理想情况下,它应该能在最简单的控制台应用程序中工作。
  2. 对除宽字符外的其他类型进行完全(透明)支持。例如 char*std::string 甚至 intfloat 等。
  3. 直观(或至少熟悉)的语法

为了实现这些目标,我开发了 audio_ostream

audio_ostream 是一个功能齐全的 std::ostream,它支持任何具有 operator<<() 的类型。

您可以根据需要创建任意数量的 audio_ostream,它们可以并行工作。

为了处理 COM 问题,我使用了出色的 COMSTL 库,它负责处理所有微妙而脆弱的 COM 复杂问题,例如(初始化)、资源(分配)和引用计数等。

boost::iostreams 用于以很少的样板代码编写工作量,提供完整的 std::ostream 支持。

由于 boost::iostreams 和 COMSTL 都是纯头文件库,所以我决定我的类也是纯头文件。这个决定的一个小代价是,SAPI 头文件将被包含在任何使用 audio_ostream 的文件中。

使用代码

使用代码再简单不过了

#include "audiostream.hpp"
using namespace std;
using namespace audiostream;
int main()
{
   audio_ostream aout;
   aout << "Hello World!"  << endl;
   // some more code...
   return 0;
}

这个小程序会,顾名思义,说 "Hello World!"。

音频流是异步的,所以程序会继续运行,即使文本正在被朗读(这就是为什么有 // some more code... 这一行,以便它能完成朗读)。这在概念上类似于 std::ostream 如何缓冲结果,直到内部缓冲区满,然后才显示文本。

使用该类

  1. #include 包含 audiostream.hpp 头文件。
  2. 创建一个 audio_ostream(或 waudio_ostream)实例
  3. 像使用任何 std::ostream 一样使用该流。

这就是开始使用该类所需的所有操作。

先决条件

为了让代码编译和运行,您需要 3 个库

  1. 对于 TTS 引擎,您需要安装 Microsoft Speech SDK(我使用的是 5.1 版)。
  2. 对于 COMSTL,您需要 STLSoft 库(您需要 STLSoft 版本 1.9.1 beta 44 或更高版本)。
  3. Boost Iostreams 库。您可以在 这里 下载 Boost。

相应地设置您的编译器和链接器路径(Boost 和 STLSOft 都是纯头文件)。

高级用法

可以使用 SAPI 文本转语音 (TTS) XML 标签更改语音的性别、语速、语言以及更多参数。

只需将相关的 XML 标签插入流中即可进行更改。可能的 XML 标签的完整列表可以在 这里 找到。

例如

audio_ostream aout;
// Select a male voice.
aout << "<voice required='Gender=Male'>Hello World!" << endl; 
aout << "Five hundred milliseconds of silence" << flush << 
    "<silence msec='500'/> just occurred." << endl;

出于某种原因,XML 标签必须是 SAPI 朗读字符串中的第一个项目,前面不能有任何文本。像示例中那样刷新流,以方便实现这一点。

您还可以调用 SetRate() 并传入 [-10,10] 之间的值来控制语音的语速。

神奇之处

核心类

代码的核心是 audio_sink

template < class SinkType >
class audio_sink: public SinkType
{
public:
   audio_sink()
   {      
      // Initialize the COM libraries
      static comstl::com_initializer coinit;                         
      // Get SAPI Speech COM object
      HRESULT hr;
      if(FAILED(hr = comstl::co_create_instance(CLSID_SpVoice, _pVoice))) 
          throw comstl::com_exception(
              "Failed to create SpVoice COM instance",hr); 
   } 
   
   // speak a character string
   std::streamsize write(const char* s, std::streamsize n)
   {
      // make a null terminated string.
      std::string str(s,n);                        
      // convert to wide character and call the actual speak method.
      return write(winstl::a2w(str), str.size());  
   }
   
   // speak a wide character string
   std::streamsize write(const wchar_t* s, std::streamsize n)
   {
      // make a null terminated wstring.
      std::wstring str(s,n);                       
      // The actual COM call to Speak.
      _pVoice->Speak(str.c_str(), SPF_ASYNC, 0);   
      return n;
   }
   
   // Set the speech speed.
   void setRate(long n) { _pVoice->SetRate(n); }   

private:      
   // COM object smart pointer.
   stlsoft::ref_ptr< ISpVoice > _pVoice;             
};

这个小类里有很多东西。让我们逐一剖析。

COMSTL、stlsoft::ref_ptr<> 和 ISpVoice

该类的唯一成员是 stlsoft::ref_ptr< ISpVoice > _pVoice

这是智能指针,它将为我们处理所有 COM 相关的事情。STLSoft 类 stlsoft::ref_ptr<> 提供 RAII 安全的引用计数接口 (RCI) 处理。具体来说,它非常适合处理 COM 对象。

我们正在使用 ISpVoice 接口。来自 Microsoft 的 网站

ISpVoice 接口使应用程序能够执行文本合成操作。应用程序可以通过此接口朗读字符串和文本文件,或播放音频文件。所有这些都可以同步或异步完成。

在构造函数中,我们首先通过 comstl::com_initializer 初始化 COM 使用。这只发生一次(因为它是一个静态对象),并且不再需要我们担心。为了初始化 _pVoice,我们使用 CLSID_SpVoice ID 调用 comstl::co_create_instance()。如果一切顺利,我们现在就持有 ISpVoice 对象句柄。所有引用计数问题都将由 stlsoft::ref_ptr<> 处理。如果调用失败,将抛出 comstl::com_exception 异常,并且类实例将不会被创建。

要朗读一些文本,我们只需要用宽字符字符串调用 _pVoice->Speak()

要“朗读文本”,我们只需要用宽字符字符串调用 _pVoice->Speak()

然而,我们希望支持其他字符类型,如 char*std::string 等。事实上,我们希望支持任何可以通过 operator<<() 转换为字符串或宽字符串的类型。

Boost Iostreams

boost::iostreams 可以轻松创建标准的 C++ 流和流缓冲区,用于访问新的源和接收器。从 网站 转述

Sink 提供对给定类型字符序列的写访问。Sink 可以通过定义一个名为 write 的成员函数来公开此序列,该函数通过 boost::iostreams::write 函数间接调用,由 Iostreams 库调用。

有两个预定义的 sink:boost::iostreams::sinkboost::iostreams::wsink,分别用于写入窄字符和宽字符字符串。

为了使我们的类成为 Sink 并获得其所有功能,我们只需从这些类之一派生我们的类(取决于我们想要窄字符还是宽字符输出)。因此,audio_sink 是一个派生自其模板参数的模板类。

要使用我们的 sink 并创建一个具体的 ostream,我们需要使用 boost::iostreams::stream 类。

支持类是 audio_ostream_t

template < class SinkType >
class audio_ostream_t: public boost::iostreams::stream< SinkType >, 
public SinkType
{
public:
   audio_ostream_t()
   {
      // Connect to Sink
      open(*this);
   }
};
typedef audio_ostream_t< audio_sink< boost::iostreams::sink  > >  
    audio_ostream ;
typedef audio_ostream_t< audio_sink< boost::iostreams::wsink > > 
    waudio_ostream;

此类允许我们将 sink 和 stream 对象组合成一个单一的实体。

派生自 boost::iostreams::stream 使我们拥有了所有的 ostream 功能。此流对象需要用 sink 对象实例进行初始化。因此,我们也从 SinkType(模板参数)派生,并用 *this 初始化 boost::iostreams::stream。从 SinkType 派生的另一个好处是它允许我们直接访问 sink 对象。直接访问允许我们,例如,直接访问 SetRate() 方法来更改语音速度。

朗读文本

boost::iostreams 机制将负责所有类型转换和 ostream 语法。最终,audio_sink::write 将被调用。尽管我们同时提供了窄字符和宽字符字符串 ostream,但 SAPI 只支持宽字符字符串。此外,Sink 的 write() 方法接受空终止的字符串以及要从流中使用的字符数。

为了解决这两个问题,我们将连续流+大小转换为一个空终止的(w)字符串,使用适当的 std::(w)string 构造函数。

为了朗读窄字符字符串,我们调用宽 write 版本,使用 STLSoft 的 winstl::a2w() 来轻松地从窄转换为宽。winstl::a2w() 将负责任何必需的临时缓冲区的分配和释放,以及转换本身。

可能的扩展

在实现了我的设计目标后,一些可能的扩展浮现在脑海中。

通过使用区域设置进行语言选择,进一步扩展 ostream 支持可能会很有趣。将一些 XML 标签包装成 ostream 操纵符,将提供更自然(或至少更熟悉)的语法。当然,类似的扩展可以将 SAPI 语音识别接口转换为 istream,但这又是另一回事了。

支持同步(阻塞)语音也可能是可取的。

修订历史

  • 2007 年 3 月 30 日 通过使用 wchar_t 而非 unsigned short,修复了代码以便在 MSVS 2005 上编译和运行。
    感谢 Jochen Berteld 指出问题,感谢 Matthew Wilson 指出解决方案。
© . All rights reserved.