京东云上提供了足够多的人工智能api,并且都使用了http的方式进行了封装,用户可以方便在自己的系统中接入京东云的ai能力。今天就是介绍一下如何编写很少的代码就能使用京东云的语音合成api在网页中实现文字朗读,最终实现效果,延迟小,支持主流设备,声调优美,还能男女生切换。

最终效果

最终效果,微信打开链接,点击播放按钮则可以进行文字朗读。

Api介绍

京东云AI API使用Restful接口风格,同时提供了java和python的sdk,使用sdk能够方便的封装参数,调用api获得数据。

为了提升调用方的响应速度,语音合成api采用了分段合成的模式,所以调用的时候在后端逻辑中按顺序多次调用,将音频数据以数据流的形式回写给前端。

获取AK/SK

访问京东云api需要获取 ak sk ,配合sdk使用;

进入京东云控制台-账号管理-Access Key管理,创建并获取Access Key。

后端音频流合成

这里给出后端部分源码,实现一个controller,开发一个get请求方法,参数封装的逻辑全都提炼出单独的方法,代码逻辑结构简单易懂。代码使用fastJson处理参数,另外引用了京东云sdk,其余都是jdk自带的api,依赖很少。

  1. 1import com.alibaba.fastjson.JSON;
  2. 2import com.alibaba.fastjson.JSONObject;
  3. 3import com.wxapi.WxApiCall.WxApiCall;
  4. 4import com.wxapi.model.RequestModel;
  5. 5
  6. 6import org.springframework.stereotype.Controller;
  7. 7import org.springframework.web.bind.annotation.GetMapping;
  8. 8import org.springframework.web.bind.annotation.RequestHeader;
  9. 9
  10. 10import javax.servlet.http.HttpServletRequest;
  11. 11import javax.servlet.http.HttpServletResponse;
  12. 12import java.io.IOException;
  13. 13import java.io.OutputStream;
  14. 14import java.util.Base64;
  15. 15import java.util.HashMap;
  16. 16import java.util.Map;
  17. 17
  18. 18@Controller
  19. 19public class TTSControllerExample {
  20. 20 //url appkey secretkey
  21. 21 private static final String url = "https://aiapi.jdcloud.com/jdai/tts";
  22. 22 private static final String appKey = "";
  23. 23 private static final String secretKey = "";
  24. 24
  25. 25 @GetMapping("/tts/stream/example")
  26. 26 public void ttsStream(
  27. 27 @RequestHeader(value = "Range", required = false) String range,
  28. 28 HttpServletRequest req,
  29. 29 HttpServletResponse resp) {
  30. 30
  31. 31 //应对safari的第一次确认请求携带header Range:bytes=0-1,此时回写1byte数据,防止错误
  32. 32 if ("bytes=0-1".equals(range)) {
  33. 33 try {
  34. 34 byte[] temp = new byte['a'];
  35. 35 resp.setHeader("Content-Type", "audio/mp3");
  36. 36 OutputStream out = resp.getOutputStream();
  37. 37 out.write(temp);
  38. 38} catch (IOException e) {
  39. 39 e.printStackTrace();
  40. 40 }
  41. 41 return;
  42. 42 }
  43. 43 //封装输入参数
  44. 44 Map queryMap = processQueryParam(req);
  45. 45 String text = req.getParameter("text");
  46. 46//封装api调用请求报文
  47. 47 RequestModel requestModel = getBaseRequestModel(queryMap, text);
  48. 48 try {
  49. 49//回写音频数据给前端
  50. 50 writeTtsStream(resp, requestModel);
  51. 51} catch (IOException e) {
  52. 52 e.printStackTrace();
  53. 53 }
  54. 54 }
  55. 55
  56. 56 /**
  57. 57 * 将前端输入参数封装为api调用的请求对象,同时设置url appkey secaretKey
  58. 58 * @param queryMap
  59. 59 * @param bodyStr
  60. 60 * @return
  61. 61 */
  62. 62 private RequestModel getBaseRequestModel(Map queryMap, String bodyStr) {
  63. 63 RequestModel requestModel = new RequestModel();
  64. 64 requestModel.setGwUrl(url);
  65. 65 requestModel.setAppkey(appKey);
  66. 66 requestModel.setSecretKey(secretKey);
  67. 67 requestModel.setQueryParams(queryMap);
  68. 68 requestModel.setBodyStr(bodyStr);
  69. 69 return requestModel;
  70. 70 }
  71. 71
  72. 72 /**
  73. 73 * 流式api调用,需要将sequenceId 依次递增,用该方法进行设置请求对象sequenceId
  74. 74 * @param sequenceId
  75. 75 * @param requestModel
  76. 76 * @return
  77. 77 */
  78. 78 private RequestModel changeSequenceId(int sequenceId, RequestModel requestModel) {
  79. 79 requestModel.getQueryParams().put("Sequence-Id", sequenceId);
  80. 80 return requestModel;
  81. 81 }
  82. 82
  83. 83 /**
  84. 84 * 将request中的请求参数封装为api调用请求对象中的queryMap
  85. 85 * @param req
  86. 86 * @return
  87. 87 */
  88. 88 private Map processQueryParam(HttpServletRequest req) {
  89. 89 String reqid = req.getParameter("reqid");
  90. 90 int tim = Integer.parseInt(req.getParameter("tim"));
  91. 91 String sp = req.getParameter("sp");
  92. 92
  93. 93 JSONObject parameters = new JSONObject(8);
  94. 94 parameters.put("tim", tim);
  95. 95 parameters.put("sr", 24000);
  96. 96 parameters.put("sp", sp);
  97. 97 parameters.put("vol", 2.0);
  98. 98 parameters.put("tte", 0);
  99. 99 parameters.put("aue", 3);
  100. 100
  101. 101 JSONObject property = new JSONObject(4);
  102. 102 property.put("platform", "Linux");
  103. 103 property.put("version", "1.0.0");
  104. 104 property.put("parameters", parameters);
  105. 105
  106. 106 Map<String, Object> queryMap = new HashMap<>();
  107. 107//访问参数
  108. 108 queryMap.put("Service-Type", "synthesis");
  109. 109 queryMap.put("Request-Id", reqid);
  110. 110 queryMap.put("Protocol", 1);
  111. 111 queryMap.put("Net-State", 1);
  112. 112 queryMap.put("Applicator", 1);
  113. 113 queryMap.put("Property", property.toJSONString());
  114. 114
  115. 115 return queryMap;
  116. 116 }
  117. 117
  118. 118 /**
  119. 119 * 循环调用api,将音频数据回写到response对象
  120. 120 * @param resp
  121. 121 * @param requestModel
  122. 122 * @throws IOException
  123. 123 */
  124. 124 public void writeTtsStream(HttpServletResponse resp, RequestModel requestModel) throws IOException {
  125. 125 //分段获取音频sequenceId从1递增
  126. 126 int sequenceId = 1;
  127. 127 changeSequenceId(sequenceId, requestModel);
  128. 128 //设置返回报文头内容类型为audio/mp3
  129. 129 resp.setHeader("Content-Type", "audio/mp3");
  130. 130 //api请求sdk对象
  131. 131 WxApiCall call = new WxApiCall();
  132. 132 //获取输出流用于输出音频流
  133. 133 OutputStream out = resp.getOutputStream();
  134. 134 call.setModel(requestModel);
  135. 135 //解析返回报文,获得status
  136. 136 String response = call.request();
  137. 137 JSONObject jsonObject = JSON.parseObject(response);
  138. 138 JSONObject data = jsonObject.getJSONObject("result");
  139. 139 //第一次请求增加校验,如果错误则向前端回写500错误码
  140. 140 if (data.getIntValue("status") != 0) {
  141. 141 resp.sendError(500, data.getString("message"));
  142. 142 return;
  143. 143 }
  144. 144 //推送实际音频数据
  145. 145 String audio = data.getString("audio");
  146. 146 byte[] part = Base64.getDecoder().decode(audio);
  147. 147 out.write(part);
  148. 148 out.flush();
  149. 149 //判断是否已结束,多次请求对应多个index,index<0 代表最后一个包
  150. 150 if (data.getIntValue("index") < 0) {
  151. 151 return;
  152. 152 }
  153. 153 //循环推送剩余部分音频
  154. 154 while (data.getIntValue("index") >= 0) {
  155. 155 //sequenceid 递增
  156. 156 sequenceId = sequenceId + 1;
  157. 157 changeSequenceId(sequenceId, requestModel);
  158. 158 //请求api获得新的音频数据
  159. 159 call.setModel(requestModel);
  160. 160 response = call.request();
  161. 161 jsonObject = JSON.parseObject(response);
  162. 162 data = jsonObject.getJSONObject("result");
  163. 163 audio = data.getString("audio");
  164. 164 part = Base64.getDecoder().decode(audio);
  165. 165 //回写新的音频数据
  166. 166 out.write(part);
  167. 167 out.flush();
  168. 168 }
  169. 169 }
  170. 170
  171. 171
  172. 172
  173. 173前端audio播放朗读
  174. 174前端部分给出在vue 模块化开发中的script部分,由于采用html5audio进行语音播放,为了兼容性需要引用howler.js (npm install howler),主要逻辑为根据设置的参数和待朗读的文字拼接一个url,调用howler.js 中的api进行播放。
  175. 175
  176. 176<script>
  177. 177import {Howl, Howler} from 'howler'
  178. 178export default {
  179. 179 data() {
  180. 180 return {
  181. 181 news: { // 新闻内容
  182. 182 ……
  183. 183 },
  184. 184 role: 1, // 0女声,1男声
  185. 185 speed: 1, // 播放速度
  186. 186 curIndex: -1, // 播放的段落在所有段落中的顺序,与用户交互显示相关,与流式播放无关
  187. 187 sound: null, // 页面唯一的指向howler实例的变量
  188. 188 status: 'empty' // load,pause,stop,empty 仅与用户交互显示相关,与流式播放显示无关
  189. 189 }
  190. 190 },
  191. 191 methods: {
  192. 192 generateUUID () { // 生成uuid
  193. 193 let d = Date.now()
  194. 194 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
  195. 195 let r = (d + Math.random() * 16) % 16 | 0
  196. 196 d = Math.floor(d / 16)
  197. 197 return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
  198. 198 })
  199. 199 },
  200. 200 audioSrc (txt) { // 生成获取音频的链接
  201. 201 let content = encodeURI(txt) // 文字编码
  202. 202 return `http://neuhubdemo.jd.com/api/tts/streamv2?reqid=${
  203. 203 this.generateUUID() // requestID
  204. 204 }&text=${
  205. 205 content // 编码后的文字内容
  206. 206 }&tim=${
  207. 207 this.role // 男声 or 女声
  208. 208 }&sp=${
  209. 209 this.speed // 播放速度
  210. 210 }`
  211. 211 },
  212. 212 /**
  213. 213 * 获取文案对应的流式音频
  214. 214 *
  215. 215 * 使用howler能够解决部分手机浏览器(eg:UC)的兼容问题,
  216. 216 * 但解决ios上微信和safari的兼容问题,
  217. 217 * 需要后端通过{range:bytes=0-1}这个header字段对请求进行控制
  218. 218 * @param {String 待转音频的文案} txt
  219. 219 */
  220. 220 howlerPlay(txt) {
  221. 221 if (this.sound) {
  222. 222 this.sound.unload() // 若sound已有值,则销毁原对象
  223. 223 }
  224. 224 let self = this
  225. 225 this.status = 'load'
  226. 226 this.sound = new Howl({
  227. 227 src: `${this.audioSrc(txt)}`,
  228. 228 html5: true, // 必须!A live stream can only be played through HTML5 Audio.
  229. 229 format: ['mp3', 'aac'],
  230. 230 // 以下onplay、onpause、onend均为控制显示相关
  231. 231 onplay() {
  232. 232 self.status = 'pause'
  233. 233 },
  234. 234 onpause: function() {
  235. 235 self.status = 'stop'
  236. 236 },
  237. 237 onend: function() {
  238. 238 self.status = 'stop'
  239. 239 }
  240. 240 });
  241. 241 this.sound.play()
  242. 242 },
  243. 243 // 控制用户交互
  244. 244 play (txt, index) {
  245. 245 if (this.curIndex === index) {
  246. 246 if (this.status === 'stop') {
  247. 247 this.sound.play()
  248. 248 } else {
  249. 249 this.sound.pause()
  250. 250 }
  251. 251 } else {
  252. 252 this.curIndex = index
  253. 253 this.howlerPlay(txt)
  254. 254 }
  255. 255 }
  256. 256 }
  257. 257}
  258. 258</script>

看完这个操作文档是不是跃跃欲试?对AI也想了解更多?

本周六我们为大家准备了【从“智慧零售”到“无人仓储”,揭秘京东人工智能技术的实践与应用】“京东云技术沙龙AI专场 ”!现场将会有技术专家为大家答疑解惑。

欢迎点击“链接”了解更多精彩内容

阅读原文

干货 | 调用AI api 实现网页文字朗读的更多相关文章

  1. python3下搜狗AI API实现

    1.背景 a.搜狗也发布了自己的人工智能 api,包括身份证ocr.名片ocr.文本翻译等API,初试感觉准确率一般般. b.基于python3. c.也有自己的签名生成这块,有了鹅厂的底子,相对写起 ...

  2. WebApi系列~通过HttpClient来调用Web Api接口

    回到目录 HttpClient是一个被封装好的类,主要用于Http的通讯,它在.net,java,oc中都有被实现,当然,我只会.net,所以,只讲.net中的HttpClient去调用Web Api ...

  3. 以短链服务为例,探讨免AppKey、免认证、Ajax跨域调用新浪微博API

    新浪微博的API官方提供了很多种调用方式,支持编程的,归根结底就是两种: 1.基于Oauth协议,使用Open API.(http://open.weibo.com/wiki/%E6%8E%88%E6 ...

  4. 通过HttpClient来调用Web Api接口

    回到目录 HttpClient是一个被封装好的类,主要用于Http的通讯,它在.net,java,oc中都有被实现,当然,我只会.net,所以,只讲.net中的HttpClient去调用Web Api ...

  5. C#调用Geocoding API进行地理编码与逆编码

    使用C#调用Geocoding API来将地址转为经纬度,或者将经纬度转变为具体的地址. Geocoding API的详细介绍参见:http://developer.baidu.com/map/web ...

  6. PHP:基于百度大脑api实现OCR文字识别

    有个项目要用到文字识别,网上找了很多资料,效果不是很好,偶然的机会,接触到百度大脑.百度大脑提供了很多解决方案,其中一个就是文字识别,百度提供了三种文字识别,分别是银行卡识别.身份证识别和通用文字识别 ...

  7. ajax调用.net API项目跨域问题解决

    ajax调用.net API项目,经常提示跨域问题.添加如下节点代码解决:httpProtocol <system.webServer> <handlers> <remo ...

  8. Android Studio快速集成讯飞SDK实现文字朗读功能

    今天,我们来学习一下怎么在Android Studio快速集成讯飞SDK实现文字朗读功能,先看一下效果图: 第一步 :了解TTS语音服务 TTS的全称为Text To Speech,即“从文本到语音” ...

  9. 对TControl和TWinControl相同与不同之处的深刻理解(每一个WinControl就相当于扮演了整个Windows的窗口管理角色,主要是窗口显示和窗口大小)——TWinControl就两个作用(管理子控件的功能和调用句柄API的功能)

    TControl是图形控件,它本身没有句柄,所以不能直接使用WINAPI显示,调整位置,发消息等等,只能想办法间接取得想要的效果,但是可以直接使用一些不需要句柄的API,比如InvalidateRec ...

随机推荐

  1. 【WPF学习】第二十四章 基于范围的控件

    WPF提供了三个使用范围概念的控件.这些控件使用在特定最小值和最大值之间的数值.这些控件——ScrollBar.ProgressBar以及Slider——都继承自RangeBase类(该类又继承自Co ...

  2. kNN.py源码及注释(python3.x)

    import numpy as npimport operatorfrom os import listdirdef CerateDataSet():        group = np.array( ...

  3. C# 借助CommandLine 写命令行工具 在数据库中创建job

    首先需要用到  CommandLine.dll 提供两个下载链接,云盘是我自己上传的,也就是我在用的 http://commandline.codeplex.com/ https://pan.baid ...

  4. CF1209A Paint the Numbers

    You are given a sequence of integers a1,a2,…,an. You need to paint elements in colors, so that: If w ...

  5. Vim中的基本操作

    Vim中的基本操作 vim介绍.实验知识点.Vim中的六种基本模式 2.1 vim 6种模式介绍 从vi衍生出来的Vim具有多种模式,这种独特的设计容易使初学者产生混淆.几乎所有的编辑器都会有插入和执 ...

  6. 自定义checkbox,redio等

    直接上代码: 看的懂看,看不懂拉到. .messageState li {list-style: none;float: left;padding-right:30px;font-size: 16px ...

  7. Android Studio Madual作为application的使用以及工作空间和modual的区别

    Android Studio Madual作为application的使用以及工作空间和modual的区别 前言: 写这篇文章的目的是因为自己使用Android Studio开发时进入了一个误区,后面 ...

  8. WebSocket实现简易聊天室

    前台页面: <html> <head> <meta http-equiv="Content-Type" content="text/html ...

  9. Caused by: com.mysql.cj.exceptions.DataReadException: Zero date value prohibited

    原因:数据库日期出现零值,即0000-00-00 属于一个无效日期. 解决方案:重新赋值,或者在jdbc链接后加参数zeroDateTimeBehavior=convertToNull

  10. [转]Linux命令行上传文件到 百度网盘 bypy

    安装软件工具: apt-get install python-pip pip install requests pip install bypy 授权登陆: 执行 bypy info,显示下边信息,根 ...