干货 | 调用AI api 实现网页文字朗读

在这里插入图片描述

京东云上提供了足够多的人工智能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,依赖很少。

 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.wxapi.WxApiCall.WxApiCall;
 import com.wxapi.model.RequestModel;
  
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.GetMapping;      import org.springframework.web.bind.annotation.RequestHeader;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
 
 @Controller
 public class TTSControllerExample {
     //url appkey secretkey
     private static final String url = "https://aiapi.jdcloud.com/jdai/tts";
     private static final String appKey = "";
     private static final String secretKey = "";
 
   @GetMapping("/tts/stream/example")
     public void ttsStream(
             @RequestHeader(value = "Range", required = false) String range,
             HttpServletRequest req,
             HttpServletResponse resp) {
 //应对safari的第一次确认请求携带header Range:bytes=0-1,此时回写1byte数据,防止错误
         if ("bytes=0-1".equals(range)) {
             try {
                 byte[] temp = new byte['a'];
                 resp.setHeader("Content-Type", "audio/mp3");
                 OutputStream out = resp.getOutputStream();
                 out.write(temp); } catch (IOException e) {
                 e.printStackTrace();
             }
             return;
         }
        //封装输入参数
         Map queryMap = processQueryParam(req);
         String text = req.getParameter("text");
         //封装api调用请求报文
         RequestModel requestModel = getBaseRequestModel(queryMap, text);
         try {
          //回写音频数据给前端
             writeTtsStream(resp, requestModel);
             } catch (IOException e) {
             e.printStackTrace();
         }
     }
      /**
      * 将前端输入参数封装为api调用的请求对象,同时设置url appkey secaretKey
      * @param queryMap
      * @param bodyStr
      * @return
      */
     private RequestModel getBaseRequestModel(Map queryMap, String bodyStr) {
         RequestModel requestModel = new RequestModel();
         requestModel.setGwUrl(url);
         requestModel.setAppkey(appKey);
         requestModel.setSecretKey(secretKey);
         requestModel.setQueryParams(queryMap);
         requestModel.setBodyStr(bodyStr);
         return requestModel;
     }
 
     /**
      * 流式api调用,需要将sequenceId 依次递增,用该方法进行设置请求对象sequenceId
      * @param sequenceId
      * @param requestModel
      * @return
      */
     private RequestModel changeSequenceId(int sequenceId, RequestModel requestModel) {
         requestModel.getQueryParams().put("Sequence-Id", sequenceId);
         return requestModel;
     }
 
     /**
      * 将request中的请求参数封装为api调用请求对象中的queryMap
      * @param req
  * @return
  */
 private Map processQueryParam(HttpServletRequest req) {
     String reqid = req.getParameter("reqid");
     int tim = Integer.parseInt(req.getParameter("tim"));
     String sp = req.getParameter("sp");

     JSONObject parameters = new JSONObject(8);
     parameters.put("tim", tim);
     parameters.put("sr", 24000);
     parameters.put("sp", sp);
     parameters.put("vol", 2.0);
     parameters.put("tte", 0);
     parameters.put("aue", 3);

    JSONObject property = new JSONObject(4);
    property.put("platform", "Linux");
    property.put("version", "1.0.0");
    property.put("parameters", parameters);

    Map<String, Object> queryMap = new HashMap<>();
    //访问参数
    queryMap.put("Service-Type", "synthesis");
    queryMap.put("Request-Id", reqid);
    queryMap.put("Protocol", 1);
    queryMap.put("Net-State", 1);
    queryMap.put("Applicator", 1);
    queryMap.put("Property", property.toJSONString());

    return queryMap;
}

/**
 * 循环调用api,将音频数据回写到response对象
 * @param resp
 * @param requestModel
 * @throws IOException
 */
public void writeTtsStream(HttpServletResponse resp, RequestModel requestModel) throws IOException {
    //分段获取音频sequenceId从1递增
    int sequenceId = 1;
    changeSequenceId(sequenceId, requestModel);
    //设置返回报文头内容类型为audio/mp3
    resp.setHeader("Content-Type", "audio/mp3");
    //api请求sdk对象
    WxApiCall call = new WxApiCall();
    //获取输出流用于输出音频流
    OutputStream out = resp.getOutputStream();
    call.setModel(requestModel);
    //解析返回报文,获得status
    String response = call.request();
    JSONObject jsonObject = JSON.parseObject(response);
    JSONObject data = jsonObject.getJSONObject("result");
    //第一次请求增加校验,如果错误则向前端回写500错误码
    if (data.getIntValue("status") != 0) {
        resp.sendError(500, data.getString("message"));
        return;
    }
    //推送实际音频数据
    String audio = data.getString("audio");
    byte[] part = Base64.getDecoder().decode(audio);
    out.write(part);
    out.flush();
    //判断是否已结束,多次请求对应多个index,index<0 代表最后一个包
    if (data.getIntValue("index") < 0) {
        return;
    }
    //循环推送剩余部分音频
    while (data.getIntValue("index") >= 0) {
        //sequenceid 递增
        sequenceId = sequenceId + 1;
        changeSequenceId(sequenceId, requestModel);
        //请求api获得新的音频数据
        call.setModel(requestModel);
        response = call.request();
        jsonObject = JSON.parseObject(response);


          data = jsonObject.getJSONObject("result");
            audio = data.getString("audio");
            part = Base64.getDecoder().decode(audio);
            //回写新的音频数据
            out.write(part);
            out.flush();
        }
    }



前端audio播放朗读
前端部分给出在vue 模块化开发中的script部分,由于采用html5的audio进行语音播放,为了兼容性需要引用howler.js (npm install howler),主要逻辑为根据设置的参数和待朗读的文字拼接一个url,调用howler.js 中的api进行播放。

<script>
import {Howl, Howler} from 'howler'
export default {
  data() {
    return {
      news: { // 新闻内容
        ……
      },
      role: 1, // 0女声,1男声
      speed: 1, // 播放速度
      curIndex: -1, // 播放的段落在所有段落中的顺序,与用户交互显示相关,与流式播放无关
      sound: null, // 页面唯一的指向howler实例的变量
      status: 'empty' // load,pause,stop,empty 仅与用户交互显示相关,与流式播放显示无关
    }
  },
  methods: {
    generateUUID () { // 生成uuid
      let d = Date.now()
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
        let r = (d + Math.random() * 16) % 16 | 0
        d = Math.floor(d / 16)
        return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
      })
    },
    audioSrc (txt) { // 生成获取音频的链接
      let content = encodeURI(txt) // 文字编码
      return `http://neuhubdemo.jd.com/api/tts/streamv2?reqid=${
          this.generateUUID() // requestID
        }&text=${
          content // 编码后的文字内容
        }&tim=${
          this.role // 男声 or 女声
        }&sp=${
          this.speed // 播放速度
        }`
    },
    /** 
     * 获取文案对应的流式音频
     * 
     * 使用howler能够解决部分手机浏览器(eg:UC)的兼容问题,
     * 但解决ios上微信和safari的兼容问题,
     * 需要后端通过{range:bytes=0-1}这个header字段对请求进行控制
     *  @param {String 待转音频的文案} txt
    */
    howlerPlay(txt) { 
      if (this.sound) {
        this.sound.unload() // 若sound已有值,则销毁原对象
      }
      let self = this
      this.status = 'load'
      this.sound = new Howl({
        src: `${this.audioSrc(txt)}`,
        html5: true, // 必须!A live stream can only be played through HTML5 Audio.
        format: ['mp3', 'aac'],
        // 以下onplay、onpause、onend均为控制显示相关
        onplay() {
          self.status = 'pause'
        },
        onpause: function() {
          self.status = 'stop'
        },
        onend: function() {
          self.status = 'stop'
        }
      });
      this.sound.play()
    },
    // 控制用户交互
    play (txt, index) {
      if (this.curIndex === index) {
        if (this.status === 'stop') {
          this.sound.play()
        } else {
          this.sound.pause()
        }
      } else {
        this.curIndex = index
        this.howlerPlay(txt)
      }
    }
  }
}
</script>

看完这个操作文档是不是跃跃欲试?对AI也想了解更多?
本周六我们为大家准备了 【从“智慧零售”到“无人仓储”,揭秘京东人工智能技术的实践与应用】“京东云技术沙龙AI专场 ”!现场将会有技术专家为大家答疑解惑。

在这里插入图片描述

点击“阅读原文”即可免费报名参加哦!

欢迎点击“京东云”了解更多精彩内容。

在这里插入图片描述
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值