语音合成

语音合成TTS,由微软提供的合成引擎,不过电脑上的语音引擎还是有点机械,效果不是很好

这几年,几大公司也退出了自己的语音引擎,之所以选择阿里云的语音引擎,是因为,可以提供3月免费语音合成功能,合成效果虽然不是非常好,但也可以了。

对于将一篇文章合成音频,本文分三部分进行分解:

1)阿里云SKD 合成音频demo

2)讲合成的多段wav音频,合成一整段完整音频

3)主进程调用SDK子进程, 并将多段音频合成一整段的 解决方案


目录

语音合成

前提

一、阿里云示例代码

1.1 TTS语音合成示例

1.2 获取TOKEN

二 合成多个音频

三 整体解决方案



前提

注册阿里云,选择 智能语音交互 ,

https://help.aliyun.com/document_detail/130555.html?spm=a2c4g.11186623.6.625.30102a7djABrmE

  • 已获取项目appkey,详情请参见创建项目。

  • 已获取Access Token,详情请参见获取Token。

一、阿里云示例代码

下载阿里云示例后,并不能直接运行,还必须自己处理一番,很是麻烦

自己整理了一份VS2013编译的可用的c++示例代码,可用下载尝试。

下面是重要的两个DEMO,tts demo      token demo

1.1 TTS语音合成示例

1)设置语速、朗读者等信息

int processGETRequest(string appKey, string token, string text, string audioSaveFile, string format, int sampleRate) {CURL* curl = NULL;CURLcode res;curl = curl_easy_init();if (curl == NULL) {return -1;}string url = "https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts";/*** 设置HTTPS URL请求参数*/ostringstream oss;oss << url;oss << "?appkey=" << appKey;oss << "&token=" << token;oss << "&text=" << text;oss << "&format=" << format;oss << "&sample_rate=" << sampleRate;// voice 发音人,可选,默认是xiaoyunoss << "&voice=" << speaker;// "siqi";// volume 音量,范围是0~100,可选,默认50oss << "&volume=" << 95;// speech_rate 语速,范围是-500~500,可选,默认是0oss << "&speech_rate=" << -300;// pitch_rate 语调,范围是-500~500,可选,默认是0// oss << "&pitch_rate=" << 0;string request = oss.str();cout << request << endl;curl_easy_setopt(curl, CURLOPT_URL, request.c_str());

2)合成的音频文件

        ofstream fs;fs.open(audioSaveFile.c_str(), ios::out | ios::binary);if (!fs.is_open()) {cout << "The audio save file can not open!";return -1;}fs.write(bodyContent.c_str(), bodyContent.size());fs.close();

1.2 获取TOKEN

因为免费的token有3小时限制,所以经常更新

代码如下(示例):

unsigned int expireTime = 0;
//获取访问令牌TokenId
int getTokenId(const char* keySecret, const char* keyId) {NlsToken nlsTokenRequest;/*设置阿里云账号KeySecret*/nlsTokenRequest.setKeySecret(keySecret);/*设置阿里云账号KeyId*/nlsTokenRequest.setAccessKeyId(keyId);/*获取token. 成功返回0, 失败返回-1*/if (-1 == nlsTokenRequest.applyNlsToken()) {cout << "Failed: " << nlsTokenRequest.getErrorMsg() << endl; /*获取失败原因*/return -1;} else {cout << "TokenId: " << nlsTokenRequest.getToken() << endl; /*获取TokenId*/cout << "TokenId expireTime: " << nlsTokenRequest.getExpireTime() << endl; /*获取Token有效期时间戳(秒)*/expireTime = nlsTokenRequest.getExpireTime();WritePrivateProfileString("Aliyun", "Token", nlsTokenRequest.getToken(), "control\\preSet.ini");return 0;}
}

二 合成多个音频

阿里云免费合成音频有300字限制,所以一篇长文章,要自己划分成小段,然后将生成的多个音频,再自己合成一个完整的音频。

合成wav音频,有网上有开源的程序

bool CombineWaveFile(int argc, char argv[][256], char *pOutFileName)
{if (argc <= 0 || 0 == strlen(pOutFileName)){return false;}char strTemp[1024] = { 0 };RIFF_HEADER *pRiff_Header = new RIFF_HEADER[argc];FMT_BLOCK *pFmt_Block = new FMT_BLOCK[argc];FACT_BLOCK *pFact_Block = new FACT_BLOCK[argc];DATA_BLOCK *pData_Block = new DATA_BLOCK[argc];int *pFileHandle = new int[argc];for (int i = 0; i < argc; i++){memset(pRiff_Header + i, 0, sizeof(RIFF_HEADER));memset(pFmt_Block + i, 0, sizeof(FMT_BLOCK));memset(pFact_Block + i, 0, sizeof(FACT_BLOCK));memset(pData_Block + i, 0, sizeof(DATA_BLOCK));memset(pFileHandle + i, 0, sizeof(int));*(pFileHandle + i) = (int)(fopen(argv[i], "rb"));if (NULL == (pFileHandle + i)){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}}for (int i = 0; i < argc; i++){int nRead = fread(pRiff_Header + i, 1, sizeof(RIFF_HEADER), (FILE*)(pFileHandle[i]));if (nRead != sizeof(RIFF_HEADER)){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}nRead = fread(pFmt_Block + i, 1, 8, (FILE*)(*(pFileHandle + i)));if (8 != nRead){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}nRead = fread(&((pFmt_Block + i)->wavFormat), 1, (pFmt_Block + i)->dwFmtSize, (FILE*)(*(pFileHandle + i)));if ((pFmt_Block + i)->dwFmtSize != nRead){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}nRead = fread(pFact_Block + i, 1, 8, (FILE*)(*(pFileHandle + i)));if (8 != nRead){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}//判断是fact字段还是data字段if (0 == strncmp((pFact_Block + i)->szFactID, "fact", 4)){nRead = fread(&((pFact_Block + i)->dwData), 1, sizeof((pFact_Block + i)->dwData), (FILE*)(*(pFileHandle + i)));if (sizeof((pFact_Block + i)->dwData) != nRead){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}nRead = fread(pData_Block + i, 1, sizeof(DATA_BLOCK), (FILE*)(*(pFileHandle + i)));if (nRead != sizeof(DATA_BLOCK)){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}}else if (0 == strncmp((pFact_Block + i)->szFactID, "data", 4))//如果没有fact段就判断是不是data段{memcpy(pData_Block + i, pFact_Block + i, sizeof(DATA_BLOCK));memset(pFact_Block + i, 0, sizeof(FACT_BLOCK));}else{DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}}//开始合并for (int i = 1; i < argc; i++){pRiff_Header[0].dwRiffSize += pData_Block[i].dwDataSize;pData_Block[0].dwDataSize += pData_Block[i].dwDataSize;}FILE *pOutFile = fopen(pOutFileName, "wb");if (NULL == pOutFile){DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return false;}int nWrite = fwrite(&(pRiff_Header[0]), 1, sizeof(RIFF_HEADER), pOutFile);nWrite = fwrite(&(pFmt_Block[0]), 1, 8, pOutFile);nWrite = fwrite(&(pFmt_Block[0].wavFormat), 1, pFmt_Block[0].dwFmtSize, pOutFile);if (pFact_Block[0].dwFactSize){nWrite = fwrite(&(pFact_Block[0]), 1, sizeof(FACT_BLOCK), pOutFile);}nWrite = fwrite(&(pData_Block[0]), 1, sizeof(DATA_BLOCK), pOutFile);for (int i = 0; i < argc; i++){memset(strTemp, 0, sizeof(strTemp));int nRet = fread(strTemp, 1, sizeof(strTemp) - 1, (FILE*)(*(pFileHandle + i)));while (0 != nRet){fwrite(strTemp, 1, nRet, pOutFile);nRet = fread(strTemp, 1, sizeof(strTemp) - 1, (FILE*)(*(pFileHandle + i)));}fclose((FILE*)(*(pFileHandle + i)));}fclose(pOutFile);DelArray(pRiff_Header);DelArray(pFmt_Block);DelArray(pFact_Block);DelArray(pData_Block);DelArray(pFileHandle);return 1;
}

三 整体解决方案

SDK有300字限制,所以一篇文章,使用SDK合成后,会生成多段音频,

这不是我们所希望的

我们希望的是: 输入文字,然后生成一整段音频。

要实现这个,可以这样解决:

1)输入文章

2)解析文章,生成300字以内小段音频

3)合成各个音频

主进程以管道方式调用控制台程序的的示例:

 BOOL ExcuteCmd(wstring path,wstring &content) {const long MAX_COMMAND_SIZE = 10000; // 命令行输出缓冲大小   CString nOptionFile;wchar_t szTemp[256] = {0};nOptionFile.Format(L"%s%s", g_strPath, L"control\\preSet.ini");GetPrivateProfileString(L"Aliyun", L"Token", L"", szTemp, 256, nOptionFile);if (_tclen(szTemp) > 0){g_AliyunToken = szTemp;}CString szFetCmd = g_strPath+ L"aliyun\\tts.exe ";szFetCmd.Append(g_AliyunToken+L" ");CString  pathC(path.c_str());CString  contentC(content.c_str());pathC.Replace(L" ", L"");pathC.Replace(L" ", L"");pathC.Replace(L" ", L"");szFetCmd.Append(pathC);szFetCmd.Append(L" ");szFetCmd.Append(g_AliyunSpeaker + L" ");szFetCmd.Append(contentC);const std::string strEnSearch = "UUID"; // 主板序列号的前导信息BOOL   bret = FALSE;HANDLE hReadPipe = NULL; //读取管道HANDLE hWritePipe = NULL; //写入管道  PROCESS_INFORMATION pi; //进程信息  memset(&pi, 0, sizeof(pi));STARTUPINFOA si; //控制命令行窗口信息memset(&si, 0, sizeof(si));SECURITY_ATTRIBUTES sa; //安全属性memset(&sa, 0, sizeof(sa));char szBuffer[MAX_COMMAND_SIZE + 1] = { 0 }; // 放置命令行结果的输出缓冲区unsigned long count = 0;long ipos = 0;pi.hProcess = NULL;pi.hThread = NULL;si.cb = sizeof(STARTUPINFO);sa.nLength = sizeof(SECURITY_ATTRIBUTES);sa.lpSecurityDescriptor = NULL;sa.bInheritHandle = TRUE;//1.创建管道bret = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);if (!bret) {CloseHandle(hWritePipe);CloseHandle(hReadPipe);return bret;}//2.设置命令行窗口的信息为指定的读写管道GetStartupInfoA(&si);si.hStdError = hWritePipe;si.hStdOutput = hWritePipe;si.wShowWindow = SW_HIDE; //隐藏命令行窗口si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;CStringA sA(szFetCmd.GetBuffer());//3.创建获取命令行的进程bret = CreateProcessA(NULL, sA.GetBuffer(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);if (!bret) {CloseHandle(hWritePipe);CloseHandle(hReadPipe);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return bret;}CloseHandle(hWritePipe); //先关闭输入管道  否则while (ReadFile....)会无限等待下去//4.读取返回的数据WaitForSingleObject(pi.hProcess, 200);//读取命令行返回值std::string  strRet;while (ReadFile(hReadPipe, szBuffer, MAX_COMMAND_SIZE, &count, NULL)){strRet.append(szBuffer, count);}//5.查找主板ID//CloseHandle(hWritePipe);CloseHandle(hReadPipe);CloseHandle(pi.hProcess);CloseHandle(pi.hThread);return TRUE;
}

有合成音频方面的需求的话,可以私信,目前已实现了 TXT小说到分章节音频的 完整流程

实用小工具 之 阿里云语音合成相关推荐

  1. 基于 JavaFx 搭建的实用小工具集合 xJavaFxTool

    项目简介: gitee地址:xJavaFxTool GitHub地址:xJavaFxTool xJavaFxTool交流QQ群:== 387473650 == xJavaFxTool是使用javaFx ...

  2. 【物联网】微信小程序接入阿里云物联网平台

    微信小程序接入阿里云物联网平台 一 阿里云平台端 1.登录阿里云 阿里云物联网平台 点击进入公共实例,之前没有的点进去申请 2.点击产品->创建产品 3.产品名称自定义,按项目选择类型,节点类型 ...

  3. 端口开通情况检测工具的阿里云服务器

    阿里云服务器端口开通是在安全组中设置的,如何查看阿里云服务器端口是否连同?在云服务器ECS控制台自助问题排查工具可以一键检测云服务器端口开放情况,阿里云百科分享阿里云服务器端口检测工具: 阿里云服务器 ...

  4. [AI创造营]Wechaty实用小工具---证件照助手

    [AI创造营]Wechaty实用小工具-证件照助手 项目地址: https://aistudio.baidu.com/aistudio/projectdetail/2253862 你是否苦恼于没有条件 ...

  5. 阿里云语音合成价格是多少?

    阿里云语音合成价格从几十元-几万元不等,看您买多大的资源包,还可以再给15个点的优惠,现在各大平台都有语音转文字,文字转语音的转换,越来越普遍,咱们平时用的视频小软件都有这个功能,各位准备上架app的 ...

  6. JavaFx 实用小工具超 60+ 大集合

    怎么JavaFx的资料那么少?是不是已经被时代淘汰了呢? 淘不淘汰不敢断言,但是在知乎上曾经看过有人这么说过,我觉得也是蛮有道理的: 所以说,如果小伙伴们是自己感兴趣想做一些GUI小工具的话,用用Ja ...

  7. 人生时间计算器_工具拯救效率,高效拯救人生!70个全国工程人实用小工具+算量表格...

    工具拯救效率,高效拯救人生.对在线工具的选取与运用,并不是炫技,而是实实在在提升工作效率.而这份工程量自动计算表+使用小工具对于造价人员而言,都是非常好的实用工具,让你比别人更高效的完成工作. 内容简 ...

  8. WPF开发的实用小工具 - 快捷悬浮菜单

    WPF开发的实用小工具 - 快捷悬浮菜单 ❝ 本文由网友投稿,Dotnet9站长整理.站长觉得这小工具很实用,站长家里.公司也在尝试使用了. 行文目录: 这工具有什么用? 正文 源码获取及应用下载体验 ...

  9. 安卓mqtt调试工具_MOTT工具调试阿里云物联网平台

    MOTT工具调试阿里云物联网平台 1.注册账号并进入物联网模块 2.创建产品.设备 3.获取设备三元素 ProductKey:a1XZkC0rJrP DeviceName:ludeng01 Devic ...

  10. 通过命令行工具使用阿里云资源编排服务

    资源编排ROS 是一种简单易用的云计算资源管理和自动化运维服务.用户通过模板描述多个云计算资源的依赖关系.配置等,并自动完成所有资源的创建和配置,以达到自动化部署.运维等目的. 了解更多 通过命令行工 ...

最新文章

  1. Calico在Docker中的搭建
  2. 收到淘宝实习生转化面试的通知
  3. Git常用命令和Git团队使用规范指南
  4. Chapter7-10_Deep Learning for Question Answering (1/2)
  5. OJ1036: 某年某月有多少天
  6. 如何下载matlab,如何下载MATLAB?
  7. 我的世界服务器怎么弄vip系统,我的世界vip插件怎么用?我的世界vip插件使用方法...
  8. 怎么手动修改域名服务器地址,如何修改域名dns服务器地址
  9. 大陆车牌识别算法的背景与技术
  10. 年龄和血压对照表,有必要了解!
  11. linux 文件名 自动补全,用Linux自动补全怎么补全命令?
  12. BigDecimal.ROUND_HALF_EVEN (银行家算法)
  13. 打开cmd命令提示不能输入命令
  14. 2015-2016-1 学期《软件工程》第4次作业
  15. LAD原理(1)知识储备之函数和分布
  16. R | 可视化 | 热图(Heatmap)
  17. 大数据NiFi(五):NiFi分布式安装
  18. 【GAN实战项目:DCGAN in Tensorflow生成动漫人物头像】代码学习
  19. 数据挖掘项目1:泰坦尼克号生存率预测
  20. 科技类PPT制作总结

热门文章

  1. cpci无法检索_CPCI检索是什么意思
  2. php除数不能为零,0为什么不能做除数(为什么0不能作为除数)
  3. HDU - 1728 逃离迷宫 【BFS】
  4. html+css 背景图片铺满并居中
  5. Jeff Dean执笔:一文看尽2018谷歌AI重大突破
  6. 腾讯CJ放出大招!次世代手游大作曝光,画面堪比3A大作
  7. 每日一词20190301——比例尺和地图比例尺
  8. 信息安全领域四大顶会
  9. J2me 开源的wap浏览器源代码分析
  10. linux命令中参数的作用,Linux 命令中的单引号,不加任何参数以及双引号的作用...