使用腾讯语音合成技术生成有声书

  背景:不知是否在博客园看到的腾讯云平台广告,被AI接口几个项目吸引住了,其中有个   语音合成  接口在这里安利一下,还挺好玩。这个接口提供将一段文字转换成语音的功能,支持中文、英文,遗憾的是暂时无法通过自己的声音进行训练,推出自己独有声音的音频文件:) 不过总体来说,还是相当不错啦,附件中是我用这个接口转换的样例音频文件。

DEMO实测,代码案例简单概述:

首先,调用接口肯定得申请appkey,secrect等一堆东西,在这里申请

申请,完成后会获得公共请求参数必须的信息,然后接口调用分为直接http请求与使用官方版本的sdk调用2种方式,建议使用sdk调用的方式,避免还得自己加sign。sdk调用的方式很简单,测试demo如下:

 @Test
    public void testAi() throws TencentCloudSDKException, IOException, UnsupportedAudioFileException, LineUnavailableException {
        Credential cred = new Credential("你的ID", "你的key");

        AaiClient aaiClient = new AaiClient(cred, "ap-beijing");
        TextToVoiceRequest request = new TextToVoiceRequest();
        request.setProjectId(10144947);
        request.setModelType(1);
        request.setPrimaryLanguage(1);
//        request.setSampleRate();
        request.setSessionId("testsessionid");
        request.setSpeed(1F);
        request.setText("你好啊,你爱我么");
        request.setVoiceType(1);
        request.setVolume(1F);
        TextToVoiceResponse textToVoiceResponse = aaiClient.TextToVoice(request);
        String audio = textToVoiceResponse.getAudio();

        if (!StringUtils.isEmpty(audio)) {
            System.out.println(audio);


            BASE64Decoder decoder = new BASE64Decoder();
            try {
                byte[] data = decoder.decodeBuffer(audio);
                OutputStream out = new FileOutputStream("d://test1.wav");
                out.write(data);
                out.flush();
                out.close();
            } catch (Exception ex) {

            }
        }
    }

本人喜欢在喜马拉雅上听书,也听小说。看到有很多连普通话都不甚标准的作者有了大量的粉丝,还有打赏。在此我有了一个大胆的想法,在不涉及版权问题的前提下,我是否可以上传一大堆小说的音频内容,以量取胜,。实际测试中发现腾讯语音合成接口默认只支持300个字符,且生成的音频文件为BASE64的String字符串,需要进行拼接转换。当然拼接并不是说把api返回的string直接通过一个stringbuilder拼起来就行,因为wav文件结构中是有头尾标示的,拼接过程当中需要去头尾,拼接转换部分源码如下:

 @Scheduled(fixedDelay = 1000 * 60 * 60)
    public void toVoice() {
        String textFilePath="D://work/mywork/txt/孙子兵法/计篇.txt";
        String outputPath="D://work/mywork/voice/孙子兵法/计篇.wav";
        try {
            File output=new File(outputPath);
            logger.info("开始获取文件内文本数据");
            List<String> stringArray = fileManService.getStringArray(textFilePath, 100);
            if (stringArray != null) {
                List<String> voiceWaves=new ArrayList<String>();
                for(String tmpText :stringArray)
                {
                    voiceWaves.add(voiceManService.getWavString(tmpText));
                }
                WavBaseStringMergeUtil wavBaseStringMergeUtil=new WavBaseStringMergeUtil();
                File file=new File(outputPath);
                wavBaseStringMergeUtil.mergeWav(voiceWaves,file);
                logger.info("完成");
            } else {
                logger.info("获取到的文本内容为空");
            }

        } catch (Exception e) {
            logger.error("转换出现异常", e);
        }
    }
private static Header resolveHeader(byte[] Basebytes) throws IOException {
        InputStream fis = new ByteArrayInputStream(Basebytes);
        byte[] byte4 = new byte[4];
        byte[] buffer = new byte[2048];
        int readCount = 0;
        Header header = new Header();
        fis.read(byte4);//RIFF
        fis.read(byte4);
        readCount += 8;
        header.fileSizeOffset = 4;
        header.fileSize = byteArrayToInt(byte4);
        fis.read(byte4);//WAVE
        fis.read(byte4);//fmt
        fis.read(byte4);
        readCount += 12;
        int fmtLen = byteArrayToInt(byte4);
        fis.read(buffer, 0, fmtLen);
        readCount += fmtLen;
        fis.read(byte4);//data or fact
        readCount += 4;
        if (isFmt(byte4, 0)) {//包含fmt段
            fis.read(byte4);
            int factLen = byteArrayToInt(byte4);
            fis.read(buffer, 0, factLen);
            fis.read(byte4);//data
            readCount += 8 + factLen;
        }
        fis.read(byte4);// data size
        int dataLen = byteArrayToInt(byte4);
        header.dataSize = dataLen;
        header.dataSizeOffset = readCount;
        readCount += 4;
        header.dataOffset = readCount;
        header.dataInputStream = fis;
        return header;
    }

 

 

 至此,基本可以满足咱们转换小说的需要啦!!!今天也上传了第一套专辑《孙子兵法》 到喜马拉雅试试水,大家有感兴趣的可以去听一下语音合成的效果,如果给您带来帮助,请不要吝惜动下手指 帮忙点赞哟!

代码、文字文本交流可以私信也可以评论中留言,

想听书的再也不用担心没书可听了,有想听书的朋友可以私信我有版权的文本内容,帮你转换哦。走路、吃饭、开车,想听就听……

 

百度语音合成SDK

接着上次的内容,又找了下百度和阿里的语音合成sdk,发现百度和阿里的相对腾讯的语音合成貌似更加成熟,使用方法什么的就不赘述了,直接上API地址,懂的自然懂。

不论是百度的还是腾讯的语音合成接口单次请求的字符数是有限制的,而我们的小说文档都很长,在之前的处理当中我是定长100处理的。会造成明明连着的一个成语或者词组,比如 “你好啊,我亲爱的朋友”,如果按照定长2个字符来处理,就会变成“你好”、“啊,我”等等,在生成音频文件合成后,连续的读起来时会发现这种断句有明显的停顿,体验很不好,因此增加如下方式,默认按照定长字符处理,但是会智能化的去寻找离定长最近的逗号、或者句号来进行断句。这样就不会出现生硬的分开本应连在一起读的词语了

处理代码如下:

  /**
     * 按照给定的字符串长度在指定文本中查找最接近指定长度的逗号或者句号的endindex。若找不到则以指定长度作为endindex
     * @param inputString
     * @param length
     * @return
     */
    private int getEndIndex(String inputString,int length)
    {
        if(length>inputString.length())
        {
            return inputString.length();
        }
        int retIndex= length;
        for(int i=retIndex-1;i>0;i--)
        {
            if(inputString.charAt(i)=='.' || inputString.charAt(i)=='。' || inputString.charAt(i)==',' || inputString.charAt(i)==',')
            {
                retIndex =i;
                break;
            }
        }
        return retIndex;
    }

    /**
     * 智能拆分文本
     * @param inputString
     * @param length
     * @return
     */
    private List<String> getStrListIntelligence(String inputString,int length)
    {
        List<String> StrList=new ArrayList<String>();
        int indexStart=0;
        while (indexStart<inputString.length())
        {
            //查找endIndex
            int endIndex = this.getEndIndex(inputString.substring(indexStart,indexStart+length<inputString.length()?indexStart+length:inputString.length()),length);
            String tmpString = inputString.substring(indexStart,indexStart+ endIndex);
            StrList.add(tmpString);
            indexStart+=endIndex;
        }
        return  StrList;
    }

 

转载于:https://www.cnblogs.com/falcon-fei/p/9461423.html

以下是一个使用腾讯语音合成的 Node.js 示例代码: const fs = require('fs'); const crypto = require('crypto'); const request = require('request'); // 替换为你的 SecretId 和 SecretKey const SecretId = 'your-secret-id'; const SecretKey = 'your-secret-key'; // 替换为你要语音合成的文本和语音参数 const text = '你好,欢迎使用腾讯语音合成服务!'; const voiceType = 1; // 语音类型,1为女声,2为男声 const speed = 0; // 语速,范围:-2到2,默认为0 const volume = 0; // 音量,范围:0到10,默认为0 const aht = 0; // Aht(降低说话的速度和语调),范围:-24到24,默认为0 const apc = 58; // APC(调整音高),范围:-96到96,默认为58 // 生成签名 function sign(str, secretKey) { const hmac = crypto.createHmac('sha1', secretKey); return hmac.update(str).digest('base64'); } // 发送请求 function sendRequest(url, params, callback) { const options = { url: url, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, form: params }; request(options, function(err, res, body) { if (err) { callback(err); } else { callback(null, body); } }); } // 获取当前时间戳 const timestamp = Math.floor(Date.now() / 1000); // 构造请求参数 const params = { Action: 'TextToVoice', Text: text, SessionId: 'test', ModelType: 1, VoiceType: voiceType, Speed: speed, Volume: volume, Aht: aht, Apc: apc, RequestData: JSON.stringify({ Text: text, SessionId: 'test', ModelType: 1, VoiceType: voiceType, Speed: speed, Volume: volume, Aht: aht, Apc: apc }), Version: '2019-08-23', Timestamp: timestamp, Nonce: Math.round(Math.random() * 65535) }; // 生成签名并添加到请求参数中 const strToSign = Object.keys(params) .sort() .map(key => `${key}=${encodeURIComponent(params[key])}`) .join('&'); params.Signature = sign(strToSign, SecretKey); // 发送请求并保存语音文件 sendRequest( 'https://tts.tencentcloudapi.com/', params, function(err, res) { if (err) { console.error(err); } else { const result = JSON.parse(res); if (result.Response && result.Response.Audio) { const audioData = Buffer.from(result.Response.Audio, 'base64'); fs.writeFileSync('output.mp3', audioData); console.log('语音文件已保存为 output.mp3'); } else { console.error(result.Response.Error || '语音合成失败'); } } } );
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值