微软(TTS)文本转语音服务API实现

此博客实现与java实现微软文本转语音(TTS)经验总结_java tts_${简简单单}的博客-CSDN博客之上,首先感谢博客源码的提供,本人在上面添加了一些详细的注释,方便大家跟好的理解和使用,毕竟我已经用原文调试了一下午才调通,一些细节的问题给大家标注出来,免得浪费大家的时间,下面直接开始代码吧!

首先大家需要去微软官网获取到密钥,方便调用时可以使用,大家注意看下图,我们一定要注意给我们分配到的区域,我这里是分配到eastus ,就是east us(美国东部)的意思,大家一定需要注意一下,后面会使用到的,然后终结点里面的地址就是我们获取token的地址

下面我们准备几个类,方便后面使用,大家把代码都复制到自己项目中,不要有遗漏:

package com.daoversal.util;

public class ByteArray {
    private byte[] data;
    private int length;
 
    public ByteArray(){
        length = 0;
        data = new byte[length];
    }
 
    public ByteArray(byte[] ba){
        data = ba;
        length = ba.length;
    }
 
    /**
    合并数组
     */
    public  void cat(byte[] second, int offset, int length){
 
        if(this.length + length > data.length) {
            int allocatedLength = Math.max(data.length, length);
            byte[] allocated = new byte[allocatedLength << 1];
            System.arraycopy(data, 0, allocated, 0, this.length);
            System.arraycopy(second, offset, allocated, this.length, length);
            data = allocated;
        }else {
            System.arraycopy(second, offset, data, this.length, length);
        }
 
        this.length += length;
    }
 
    public  void cat(byte[] second){
        cat(second, 0, second.length);
    }
 
    public byte[] getArray(){
        if(length == data.length){
            return data;
        }
 
        byte[] ba = new byte[length];
        System.arraycopy(data, 0, ba, 0, this.length);
        data = ba;
        return ba;
    }
 
    public int getLength(){
        return length;
    }
}
package com.daoversal.util;

import javax.net.ssl.HttpsURLConnection;
import java.net.URL;

public class HttpsConnection {
 
    public static HttpsURLConnection getHttpsConnection(String connectingUrl) throws Exception {
 
        URL url = new URL(connectingUrl);
        return (HttpsURLConnection) url.openConnection();
    }
}
package com.daoversal.util;

import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;

@Slf4j
public class XmlDom {
    public static String createDom(String locale, String genderName, String voiceName, String textToSynthesize){
        Document doc = null;
        Element speak, voice;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = dbf.newDocumentBuilder();
            doc = builder.newDocument();
            if (doc != null){
                speak = doc.createElement("speak");
                speak.setAttribute("version", "1.0");
                speak.setAttribute("xml:lang", "en-US");
                voice = doc.createElement("voice");
                voice.setAttribute("xml:lang", locale);
                voice.setAttribute("xml:gender", genderName);
                voice.setAttribute("name", voiceName);
                voice.appendChild(doc.createTextNode(textToSynthesize));
                speak.appendChild(voice);
                doc.appendChild(speak);
            }
        } catch (ParserConfigurationException e) {
            log.error("Create ssml document failed: {}",e.getMessage());
            return null;
        }
        return transformDom(doc);
    }
 
    private static String transformDom(Document doc){
        StringWriter writer = new StringWriter();
        try {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer;
            transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(new DOMSource(doc), new StreamResult(writer));
        } catch (TransformerException e) {
            log.error("Transform ssml document failed: {}",e.getMessage());
            return null;
        }
        return writer.getBuffer().toString().replaceAll("\n|\r", "");
    }
}

 下面这个类我给大家重点讲一下,大家去下面网址看看自己的参数Text to speech API reference (REST) - Speech service - Azure AI services | Microsoft Learn

AUDIO_24KHZ_48KBITRATE_MONO_MP3 :语言类型,这个不重要,那个声音好听用那个,去下图找:

ACCESS_TOKEN_URI :就是本文章的第一张图里面,里面获取token的地址,直接将地址复制进来就好了。

API_KEY :自己的api key,就是密钥。

 TTS_SERVICE_URI : 这个地址一定要对应分配的区域才行,不然会报权限错误

Synthesis tts speech failed Server returned HTTP response code: 401 for URL: https://.........

我这里是 east us(美国东部),所以就使用美国东部里面的地址即可。

package com.daoversal.util;

public class TtsConst {
    /**
     * 音频合成类型(亲测这种效果最佳,其他的你自己去试试)
     * 里面有很多类型,可以去里面找自己需要的
     * https://learn.microsoft.com/en-us/azure/ai-services/speech-service/rest-text-to-speech?tabs=streaming
     */
    public static final String AUDIO_24KHZ_48KBITRATE_MONO_MP3 = "audio-24khz-48kbitrate-mono-mp3";
    /**
     * 授权url   获取密钥页面 终结点 里面的地址,我们使用这个获取token
     */
    public static final String ACCESS_TOKEN_URI = "token获取地址";
    /**
     * api key
     */
    public static final String API_KEY = "自己的密钥";
    /**
     * tts服务url,这里一定要根据自己分配的地区找相应的地址才行
     */
    public static final String TTS_SERVICE_URI = "https://eastus.tts.speech.microsoft.com/cognitiveservices/v1/";


}

下面参数给大家讲一下:

textToSynthesize : 传入的合成语音文本内容

locale:语言类型,大家可以参考,中文在嵌入式语音里面,大家可以在两个页面找到自己需要的语言。

Embedded Speech - Speech service - Azure AI services | Microsoft Learn

Language support - Speech service - Azure AI services | Microsoft Learn

gender:为发声人性别,Male表示男性

 voiceName :发声者名称,大家可以去下图找出对应的,比如中文的话:

package com.daoversal.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.net.ssl.HttpsURLConnection;
import java.io.DataOutputStream;
import java.io.InputStream;

@Slf4j
@Component
public class TtsService {
 
    @Resource
    private Authentication authentication;

    /**
     * 合成音频
     * @param textToSynthesize 传入需要翻译的文本
     * @param locale    要合成的语言类型
     * @param gender    性别
     * @param voiceName 发音者名称
     * @return
     */
    public byte[] genAudioBytes(String textToSynthesize, String locale, String gender, String voiceName) {
        String accessToken = authentication.genAccessToken();
        if (StringUtils.isEmpty(accessToken)) {
            return new byte[0];
        }
        try {
            HttpsURLConnection webRequest = HttpsConnection.getHttpsConnection(TtsConst.TTS_SERVICE_URI);
            webRequest.setRequestProperty("Host", "eastus.tts.speech.microsoft.com");
            webRequest.setRequestProperty("Content-Type", "application/ssml+xml");
            webRequest.setRequestProperty("X-Microsoft-OutputFormat", TtsConst.AUDIO_24KHZ_48KBITRATE_MONO_MP3);
            webRequest.setRequestProperty("Authorization", "Bearer " + accessToken);
            webRequest.setRequestProperty("Ocp-Apim-Subscription-Key", TtsConst.API_KEY);
            webRequest.setRequestProperty("User-Agent", "Mozilla/5.0");
            webRequest.setRequestProperty("Accept", "*/*");
            webRequest.setDoInput(true);
            webRequest.setDoOutput(true);
            webRequest.setConnectTimeout(5000);
            webRequest.setReadTimeout(300000);
            webRequest.setRequestMethod("POST");
 
            String body = XmlDom.createDom(locale, gender, voiceName, textToSynthesize);
            if (StringUtils.isEmpty(body)) {
                return new byte[0];
            }
            byte[] bytes = body.getBytes();
            webRequest.setRequestProperty("content-length", String.valueOf(bytes.length));
            webRequest.connect();
            DataOutputStream dop = new DataOutputStream(webRequest.getOutputStream());
            dop.write(bytes);
            dop.flush();
            dop.close();
            InputStream inSt = webRequest.getInputStream();
            ByteArray ba = new ByteArray();
            int rn2 = 0;
            int bufferLength = 4096;
            byte[] buf2 = new byte[bufferLength];
            while ((rn2 = inSt.read(buf2, 0, bufferLength)) > 0) {
                ba.cat(buf2, 0, rn2);
            }
            inSt.close();
            webRequest.disconnect();
            return ba.getArray();
        } catch (Exception e) {
            log.error("Synthesis tts speech failed {}", e.getMessage());
        }
        return null;
    }

}
package com.daoversal.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

/**
 * 此类获取token,每次调用都需要使用到token的
 * token的有效期是10分钟,但是不建议大家10分钟调一次,免得使用了失效的token
 */
@Component
@Slf4j
public class Authentication {

    @Autowired
    private RedissonClient redisson;

    public String genAccessToken() {
        InputStream inSt;
        HttpsURLConnection webRequest;
        try {
            //先从redis里面取缓存的token,如果没有就远程拉取,有的话就直接使用,大家可根据自己的业务调整
            Object ob = redisson.getBucket("accessToken").get();
            String accessToken = ob == null ? null : ob.toString();
            if (StringUtils.isEmpty(accessToken)) {
                webRequest = HttpsConnection.getHttpsConnection(TtsConst.ACCESS_TOKEN_URI);
                webRequest.setDoInput(true);
                webRequest.setDoOutput(true);
                webRequest.setConnectTimeout(5000);
                webRequest.setReadTimeout(5000);
                webRequest.setRequestMethod("POST");
 
                byte[] bytes = new byte[0];
                webRequest.setRequestProperty("content-length", String.valueOf(bytes.length));
                //api的key,取微软官网获取
                webRequest.setRequestProperty("Ocp-Apim-Subscription-Key", TtsConst.API_KEY);
                webRequest.connect();
 
                DataOutputStream dop = new DataOutputStream(webRequest.getOutputStream());
                dop.write(bytes);
                dop.flush();
                dop.close();
 
                inSt = webRequest.getInputStream();
                InputStreamReader in = new InputStreamReader(inSt);
                BufferedReader bufferedReader = new BufferedReader(in);
                StringBuilder strBuffer = new StringBuilder();
                String line = null;
                while ((line = bufferedReader.readLine()) != null) {
                    strBuffer.append(line);
                }
 
                bufferedReader.close();
                in.close();
                inSt.close();
                webRequest.disconnect();
 
                accessToken = strBuffer.toString();
                //获取到了token,缓存到redis里面,5分钟失效
                redisson.getBucket("accessToken").set(accessToken,5L, TimeUnit.MINUTES);
                //设置accessToken的过期时间为5分钟
                log.info("New tts access token {}", accessToken);
            }
            return accessToken;
        } catch (Exception e) {
            log.error("Generate tts access token failed {}", e.getMessage());
        }
        return null;
    }
}

最后就是调用了,大家可以测试了:

package com.daoversal.web;


import com.daoversal.framework.http.Response;
import com.daoversal.task.DvWeekCountTask;
import com.daoversal.task.RechargeTask;
import com.daoversal.task.UserGradeCountTask;
import com.daoversal.task.WindControlMsgTask;
import com.daoversal.util.TtsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import okhttp3.*;
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

/**
 * <p>
 * 套餐价值释放记录表 前端控制器
 * </p>
 *
 * @author HayDen
 * @since 03 22 10:44:13
 */
@RestController
@RequestMapping("/test")
@Api(value = "test")
public class TestController {

    @Resource
    private TtsService testService;

    @PostMapping("/ttsService")
    @ApiOperation(value = "获取ttsService", httpMethod = "POST" )
    public void ttsService(String text) {
       // byte[] bte = testService.genAudioBytes(res,"en-US","Male","en-US-JennyNeural");
        byte[] bte = testService.genAudioBytes(text,"zh-CN","Male","zh-CN-YunxiNeural");
        String value = "hllo.mp3";
        convertByteArrayToFile(bte,value);
        System.out.println("213213123");
    }

    /**
     * 此文件是将byte[] 转换成文件存储到指定路径的
     * @param arr
     * @param value
     */
    public static void convertByteArrayToFile(byte[] arr,String value) {
        try (
                BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream(arr));
                //这里是转换以后的文件存储的路径
                FileOutputStream fileOutputStream = new FileOutputStream("/Users/recovery/Downloads/"+value);
                BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)
        ) {
            int data;
            while ((data = bis.read()) != -1) {
                bos.write(data);
            }
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

最后大家需要注意一下就是如果你选的是英文en-US,但是输入的文本是中文的话他是不会翻译的,所以大家一定要注意自己的语言类型不要弄错了,如果有疑问可以留言哦,我看到肯定会毫无保留的给大家说明的。

如果这篇文章在你一筹莫展的时候帮助到了你,可以请作者吃个棒棒糖🙂,如果有啥疑问或者需要完善的地方欢迎大家在下面留言或者私信作者优化改进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/557093.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python入门之简洁安装VS保姆版安装(含虚拟环境)

11、保姆版安装 Anoconda安装&#xff08;python的一个发行版本&#xff09; 优点&#xff1a;集成了许多关于python科学计算的第三方库&#xff0c;保姆级别 下载&#xff1a;www.anaconda.com/download/ 版本默认64位&#xff0c;py37 √&#xff1a;add anaconda to my…

教程 | 亚组分析森林图模块使用介绍

本周风暴统计平台最新更新了亚组森林图板块&#xff01;界面与功能进行了全新升级&#xff0c;今天就通过这篇教程为大家详细介绍&#xff0c;亚组森林图模块各种细节的设置与使用方式&#xff01; 教程将从以下方面开展&#xff1a; 1. 亚组分析使用介绍2. 不同回归分析中亚组…

Java 数据类型

一 Java 的数据类型 二 整数类型 类型占用存储空间范围byte[字节]1字节-127~127short[短整型]2字节-215~215-1 即 -32768~ 32767int[整型]4字节-231~231-1 即 -2147483648~2147483647long[长整型]8字节-263~263-1 字节 byte是计算机存储单位的基本单元&#xff0c;通常由8个比…

Redis: 集群

文章目录 一、单点Redis的问题二、主从架构1、概述2、集群结构3、主从数据同步原理&#xff08;1&#xff09;全量同步&#xff08;2&#xff09;增量同步 4、总结&#xff08;1&#xff09;全量同步和增量同步的区别&#xff08;2&#xff09;什么时候执行全量同步&#xff08…

面试经典150题——跳跃游戏 II

面试经典150题 day10 题目来源我的题解方法一 动态规划方法二 贪心 题目来源 力扣每日一题&#xff1b;题序&#xff1a;45 我的题解 方法一 动态规划 动态规划&#xff0c;当j位置可达i位置时&#xff1a;dp[i]Math.min(dp[i],dp[j]1); 时间复杂度&#xff1a;O( n 2 n^2 n…

SpringBlade dict-biz/list SQL 注入漏洞复现

0x01 产品简介 SpringBlade 是一个由商业级项目升级优化而来的 SpringCloud 分布式微服务架构、SpringBoot 单体式微服务架构并存的综合型项目。 0x02 漏洞概述 SpringBlade 后台框架 /api/blade-system/dict-biz/list 路径存在SQL注入漏洞,攻击者除了可以利用 SQL 注入漏洞…

chromedriver最新版下载地址

地址1.百度网盘 链接(提取码&#xff1a;2vo3)&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com…

微信域名防封/QQ域名防封/域名状态检测/域名防红防封API平台源码

下载地址&#xff1a;API平台源码 这套源码是使用thinkphp3.1.3开发的&#xff0c;可以在PHP5.3-5.6下运行&#xff0c;程序是有一点老了&#xff0c;但是思路仍在&#xff01;然后&#xff0c;这套源码我已经成功搭建起来了&#xff0c;后台、个人&#xff08;用户&#xff0…

基于Material Design风格开源、易用、强大的WPF UI控件库

前言 今天大姚给大家分享一款基于Material Design风格开源、免费&#xff08;MIT License&#xff09;、易于使用、强大的WPF UI控件库&#xff1a;MaterialDesignInXamlToolkit。 项目介绍 MaterialDesignInXamlToolkit 是一个开源、易于使用、强大的 WPF UI 控件库&#x…

【opencv】示例-videocapture_starter.cpp 从视频文件、图像序列或连接到计算机的摄像头中捕获帧...

/** * file videocapture_starter.cpp * brief 一个使用OpenCV的VideoCapture与捕获设备&#xff0c;视频文件或图像序列的入门示例 * 就像CV_PI一样简单&#xff0c;对吧&#xff1f; * * 创建于: 2010年11月23日 * 作者: Ethan Rublee * * 修改于: 2013年4月17日 * …

mysql 查询实战3-解答

对mysql 查询实战3-题目&#xff0c;进行一个解答 11、查询每⽉产品交易与退款情况 目标&#xff1a;查询每⽉产品交易&#xff08;交易总额&#xff0c;交易数&#xff09;与退款情况&#xff08;退款总额&#xff0c;退款数&#xff09; 1&#xff0c;先把日期格式化 使用 E…

Savina Mx 高級的無塵擦拭布系列產品,吸水吸油性極強,不磨損原件

Savina Mx是日本KBSEIREN株式會社&#xff08;原KANEBO&#xff09;開發的目前*高級的無塵擦拭布系列產品&#xff0c;吸水吸油性極強&#xff0c;不磨損原件。廣氾用於光學鏡頭製造&#xff0c;辦公器材保養&#xff0c;10級以上的無塵車間淨化室&#xff0c;半導體生產線車間…

美易官方:以色列袭击伊朗!原油、黄金走势上涨?

以色列突然袭击伊朗的消息震惊了全球市场&#xff0c;引发了一场原油和黄金价格的飙升。这一事件不仅令投资者感到紧张&#xff0c;也引发了国际社会对于中东地区紧张局势的担忧。 以色列此次袭击的目标据说是伊朗的一处军事基地&#xff0c;据称该基地涉及到伊朗的核武器研发计…

Network: wirehark: 解包问题:乱序重组

如果一个大的TCP数据被分成几个segment&#xff0c;而每个segment如果走的路由途径不同的化&#xff0c;会导致下面这个解析上错误。从下面这个图里看&#xff0c;第一片和第二片的顺序的&#xff0c;但是第三片跑到了第二片的前面&#xff0c;wirehark就解析不出来了&#xff…

安卓apk文件签名

一、环境准备 链接: https://pan.baidu.com/s/1D3WxIL5M5ewyFNTqJzARPw 提取码: pd6w 上篇博文编译的apk文件 1、docker build -t android-build:v1.0.1 . 直接制作镜像 2、docker run -it android-build:v1.0.1 /bin/bash 运行进入容器 指定sdk的路径&#xff0c;然后直接…

华为欧拉系统(openEuler-22.03)安装深信服EasyConnect软件(图文详解)

欧拉镜像下载安装 iso镜像官网下载地址 选择最小化安装&#xff0c;标准模式 换华为镜像源 更换华为镜像站&#xff0c;加速下载&#xff1a; sed -i "s#http://repo.openeuler.org#https://mirrors.huaweicloud.com/openeuler#g" /etc/yum.repos.d/openEuler.r…

使用Termux在Android设备上编译运行SpecCPU2006

Spec CPU 2006 的使用说明&#xff08;曲线救国版&#xff09; 因本部分实验用到的Spec CPU2006依赖于多个编译工具包&#xff0c;因此对源码的编译要在配置好环境的Linux设备上运行&#xff0c;根据实验发现&#xff0c;现有的环境&#xff08;包括adb和termux&#xff09;都不…

通过实例学C#之FileStream类

简介 可以通过此类进行文件读取。 首先在项目所在文件夹的Bin文件中新建一个test.txt文件&#xff0c;里面输入内容“hello world!”。 构造函数 FileStream (string path, FileMode mode&#xff0c;FileAccess access) 通过路径文件path&#xff0c;打开文件模式mode以及读写…

Arcgis Pro2.5安装教程(内含安装文件)

​最近处理的数据量大&#xff0c;发现arcmap这种老产品属实是不行了&#xff0c;相比于下一代的Arcgis Pro,不但运行速度慢&#xff0c;也容易遇到突然关闭的问题&#xff0c;之前基于团队的选择也没办法&#xff0c;最近实在是被数据搞得无语了&#xff0c;一鼓作气装上了Arc…

Java序列流和打印流、对象序列化

目录 1、序列流 1.1 SequenceInputStream 1.2 案例:切割mp3并合并 2、 对象的序列化 2.1 ObjectOutputStream与ObjectInputStream 2.2 Serializable 3、Properties. 4、打印流 4.1 PrintStream 5、操作基本数据类型的流对象 5.1 DataInputStream以及DataOutputStrea…