用极简方法玩转 Google 文本转语音电子书阅读器






4.74/5 (31投票s)
让 Google 用出色的语音朗读你的电子书或转换为 MP3!Android 手机使用的这个免费 TTS 引擎听起来很棒,所以这里有一个简单的电子书阅读器应用程序,带有 C++ 源代码
引言
很高兴看到谷歌凭借其庞大的云计算能力,开始向人们提供如此多的免费工具。
我对声音质量印象深刻,所以创建了这个简单的程序来大声朗读我最喜欢的电子书。到目前为止,有 7 种不同语言的语音,质量出色
英语 法语 意大利语 西班牙语 德语 捷克语 海地克里奥尔语 印地语
不幸的是,最近通过第三方开源 ESpeech 引擎集成了 27 种 [质量较差] 的语音。
随着时间的推移,谷歌会用质量更好的版本替换它们。最近增加了捷克语。南非荷兰语、阿尔巴尼亚语、加泰罗尼亚语、中文(普通话)、克罗地亚语、丹麦语、荷兰语、芬兰语、希腊语、匈牙利语、冰岛语、印度尼西亚语、拉脱维亚语、马其顿语、挪威语、波兰语、葡萄牙语、罗马尼亚语、俄语、塞尔维亚语、斯洛伐克语、斯瓦希里语、瑞典语、土耳其语、越南语、威尔士语。
让 Google TTS 通过简单的 URL 说出您选择语言的文本
http://translate.google.com/translate_tts?tl=en&q=hello+world
是的。这是集成到 Google 的 Android 并为 Google 翻译中的发音提供动力的同一服务。
无论如何,即使它是一个基于网络的**服务**。它是免费的,它会向您发送 MP3,其 TTS 对于某些语言来说,比大多数付费 TTS 引擎领先很多。
让 Google 翻译检测您的文本的语言
http://translate.google.com/translate_a/t?client=t&sl=auto&text=hello+world
我们收到的是检测到的语言,我们反过来用它来让 TTS 知道我们想听哪种语音。注意 sl=auto。它表示“源语言”参数自动检测
是的,正如您在**官方 Google 翻译页面**上看到的那样,Google 语言检测通常不可靠。所以您最好在您的应用程序中手动设置语言,但无论如何这是一个有趣的测试功能。
代码
代码稍微大一些,因为我们需要逐行检测语言 + 将文本拆分为最多 100 个字符的块,并将其作为 URL 编码的 HTTP GET 请求发送。Google 发回 MP3 文件,由于 DirectShow 流媒体的性质和已安装的 mp3 编解码器,我们会在接收到 MP3 文件时对其进行流式传输。这是一个最小的示例,因此您可以专注于它的工作方式。不重要的代码,如钩子,以代码片段形式折叠,但请随意展开并以您喜欢的方式格式化代码。如果您计划安全地使用代码,请替换所有静态缓冲区,并省略了清理和更健壮的错误处理,这样您可以专注于重要的部分,但仍然有很多乐趣。
所以请享受 ;)
#include <windows.h>
#include <shlwapi.h>
#include <Richedit.h>
#include <dshow.h>
#include <winsock.h>
#pragma comment(lib,"Strmiids.lib")
#pragma comment(lib,"Shlwapi.lib")
#pragma comment(lib,"wsock32.lib")
#define DsHook(a,b,c) if (!c##_) {
INT_PTR* p=b+*(INT_PTR**)a; VirtualProtect(&c##_,4,PAGE_EXECUTE_READWRITE,&no);
*(INT_PTR*)&c##_=*p; VirtualProtect(p,4,PAGE_EXECUTE_READWRITE,&no); *p=(INT_PTR)c; }
HRESULT ( __stdcall * SyncReadAlligned_ ) ( void* inst, IMediaSample *smp ) ; HANDLE out;
HRESULT __stdcall SyncReadAlligned ( void* inst, IMediaSample *smp ) {
HRESULT ret = SyncReadAlligned_ ( inst, smp );
BYTE* buf; smp->GetPointer(&buf);
DWORD len = smp->GetActualDataLength(),no; WriteFile(out,buf,len,&no,0);
return ret;
}
int WINAPI WinMain(HINSTANCE inst,HINSTANCE prev,LPSTR cmd,int show) {
MSG msg={0}; WSADATA wsa; DWORD no; HRESULT hr;
CoInitialize(0); WSAStartup(MAKEWORD(1,1),&wsa); LoadLibraryA("RichEd20");
// connect to google translate for text language autodetection
SOCKET s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); sockaddr_in addr={AF_INET,htons(80)};
HOSTENT* dns=gethostbyname("translate.google.com"); memcpy(&addr.sin_addr.s_addr,dns->h_addr,4);
if(connect(s,(sockaddr*)&addr,sizeof(addr)) != 0) return 0;
HWND hwnd = CreateWindowA("RICHEDIT20W",0,WS_SIZEBOX|ES_MULTILINE|WS_VISIBLE|ES_AUTOVSCROLL|ES_AUTOHSCROLL|WS_SYSMENU|WS_CAPTION|WS_MINIMIZE|WS_HSCROLL|WS_VSCROLL,500,500,500,300,0,0,0,0);
while ( IsWindowVisible(hwnd) ) {
if( PeekMessage(&msg,0,0,0,1) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); }
if( msg.wParam==VK_RETURN && msg.message == WM_KEYDOWN ) {
DWORD len = 2+GetWindowTextLength(hwnd)*2; CHARRANGE ch={0,-1}; SendMessage(hwnd,EM_EXSETSEL,0,(LPARAM)&ch);
WCHAR* Txt = (WCHAR*)calloc(len,1),*e,*txt=Txt; SendMessage(hwnd,EM_GETSELTEXT,0,(LPARAM)Txt); ch.cpMin=-1;
SendMessage(hwnd,EM_EXSETSEL,0,(LPARAM)&ch);
out = CreateFile("c:/out.mp3",GENERIC_WRITE,FILE_SHARE_READ,0,CREATE_ALWAYS,0,0);
while(*txt) {
// since sended text can not be larger than 100 we try to break sentences
if((e=wcschr(txt,L'.'))) *e=0;
if(wcslen(txt)>100 &&(e=wcschr(txt,L','))) *e=0;
if(wcslen(txt)>100) { e=txt+100; while(*e!=L' ') e--; *e=0; }
// detect language by asking google translate service so we can switch voice language per sentence as needed
char utf[1000],esc[1000]={0},*a,*b=utf; WideCharToMultiByte(CP_UTF8,0,txt,-1,utf,1000,0,0);
while(*b) sprintf(esc+strlen(esc),"%%%0.2x",*(BYTE*)b++); txt+=wcslen(txt)+1; //escape utf-8 chars
char buf[1000]; sprintf(buf,"GET /translate_a/t?client=t&sl=auto&text=%s HTTP/1.1\r\nUser-Agent: Mozilla/5.0\r\n\r\n\r\n\r\n",esc);
send(s,buf,strlen(buf),0); // we send text sentence to google translate server
recv(s,buf,sizeof(buf),0); // and receive detected language
char lng[3]={"en"}; if((a=strstr(buf,"]],,\""))) memcpy(lng,a+5,2);
// This triplet with RenderFile is all you need to play anything with aprropriate codec on windows.
IGraphBuilder* graph= 0; CoCreateInstance( CLSID_FilterGraph, 0, CLSCTX_INPROC,IID_IGraphBuilder, (void **)&graph );
IMediaControl* ctrl = 0; graph->QueryInterface( IID_IMediaControl, (void **)&ctrl );
IMediaEvent* event= 0; graph->QueryInterface( IID_IMediaEventEx, (void **)&event );
// This sends text (sentence) encoded in get request and progressively plays mp3 stream from google as it is received.
// So all TTS is done on server and this is only work that client does
WCHAR url[1000]; wsprintfW(url,L"http://translate.google.com/translate_tts?tl=%S&q=%S",lng,esc);
if((hr=ctrl->RenderFile(url))) continue;
// we hook the source filter and append to global mp3 file on disk
IBaseFilter* filter; graph->FindFilterByName(url,&filter);
IPin* pin; filter->FindPin(L"Output",&pin);
IAsyncReader* reader; pin->QueryInterface(IID_IAsyncReader,(void**)&reader);
// redirect 7th member func of IAsyncReader (SyncReadAlligned) to grab mp3 data from output pin of source filter
DsHook(reader,6,SyncReadAlligned);
// we run and wait for mp3 to finish before we ask another sentence
hr=ctrl->Run(); long code=0,c;
while( code != EC_COMPLETE ) {
if( PeekMessage(&msg,0,0,0,1) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } event->GetEvent(&code, &c, &c, 0);
Sleep(1);
}
ctrl->Release(); event->Release(); graph->Release();
}
free(Txt);
CloseHandle(out);
}
}
}
关注点
请注意,我们正在将网址直接传递给 DirectShow。RenderFile() 调用实际上会生成整个图形,包括流拆分器、mp3 解码器和输出到声音设备。
这个简单的技巧允许我们,例如,在无需太多工作的情况下收听互联网广播等。它要求您至少安装了一些 mp3 编解码器。你们大多数人可能都有。如果没有。那么请安装 ffdshow,它是一个免费的多编解码器,可以播放您扔给它的几乎所有东西。
另一件事是,抓取接收到的数据的正确方法是在源和拆分器过滤器之间实现并连接样本抓取器过滤器。但那样的话,将需要您使用 DirectShow SDK,这是一个非常复杂的事情,很难编译,并且很难超越仅实现一个过程。
未知语言不会播放,但您可以进行很多替换,例如“nl”做“de”等。混合不同语言的句子只是为了好玩 ;)
历史
- 28.3 第一个版本
- 31.3 用于手动语言选择的组合框被自动语言检测(按句子)替换
- 3.4 添加了将接收到的流捕获到磁盘上的 mp3 文件的功能
- 15.5 添加了 29 种新语言现已合成的信息。质量差。
- 16.5 将代码更改为 Unicode 并上传了固定的 exe,因此中文、印地语等现在可以工作
- 2.6.2011 使用 DNS 而不是 IP + 更新了语言检测以反映 Google 的更改。添加了 zip 的源代码