Ali 语音合成 NlsClientFactory——代码优化版(包含使用@ConfigurationProperties来读取配置和失败重试)

3 篇文章 0 订阅

今日歌词分享:

生而为人无罪 你不需要抱歉

One day I will be you baby boy and you gon'be me

喧哗如果不停 让我陪你安静

I wish I could hug you till you're really really being free

哪朵玫瑰没有荆棘

最好的 报复是 美丽

最美的 盛开是 反击

别让谁去 改变了你

你是你 或是妳 都行

会有人 全心的 爱你

                                                                                                                                                —— 《玫瑰少年》 Mayday

目录

1.  SpeechClientFactory 

2. 使用CommandLineRunner来进行创建全局唯一的client

3.  service类 

4. impl

5. 总结

附录


1.  SpeechClientFactory 

import com.alibaba.nls.client.AccessToken;
import com.alibaba.nls.client.protocol.NlsClient;
import com.aliyuncs.exceptions.ClientException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Description : SpeechClientFactory
 * @Date : 2019/8/20
 * @Author : pmdream
 */
@Slf4j
@Component
public class SpeechClientFactory {

    private static final long TWO_HOURS = 60*60*2;
    private static final int RETRY_NUM = 3;
    private static final long RETRY_INTERVAL = 2000;

    @Autowired
    private SpeechProperties speechProperties;

    /**
     * 因为创建NlsClient相当消耗资源,所以全局创建一个即可,线程安全{@link com.situdata.situciticpru.startup.InitNlsClient#run(String...)}
     * */
    public static NlsClient client;

    public static volatile AccessToken accessToken;

    public String getToken(){
        if (null == SpeechClientFactory.accessToken || SpeechClientFactory.accessToken.getExpireTime() - System.currentTimeMillis() < TWO_HOURS) {
            synchronized (SpeechClientFactory.class){
                if (null == SpeechClientFactory.accessToken || SpeechClientFactory.accessToken.getExpireTime() - System.currentTimeMillis()/1000 < TWO_HOURS) {
                    int m = 0;
                    //获取token时会出现异常,增加重试
                    while (m< RETRY_NUM){
                        try {
                            SpeechClientFactory.accessToken = AccessToken.apply(speechProperties.getAkId(), speechProperties.getAkSecrete());
                            break;
                        } catch (ClientException e) {
                            try {
                                Thread.sleep(RETRY_INTERVAL);
                            } catch (InterruptedException ex) {}
                            m++;
                            if(m == RETRY_NUM){
                                log.info("[SpeechClientFactory] getAccessToken error = {} " + e.getMessage());
                                return "";
                            }
                        }
                    }
                }
            }
        }
        return SpeechClientFactory.accessToken.getToken();
    }
}

2. 使用CommandLineRunner来进行创建全局唯一的client

import com.alibaba.nls.client.protocol.NlsClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value=1)
public class InitNlsClient implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        SpeechClientFactory.client = new NlsClient("");
    }
}

这样的好处就是启动的时候,把这个类当做组件来进行IOC

3.  service类 

/**
 * @Description : SpeechService
 * @Date : 2019/8/20
 * @Author : pmdream
 */
public interface SpeechService {

    boolean speechSynthesizer(String content, String mp3Path);
}

4. impl

import com.alibaba.nls.client.protocol.NlsClient;
import com.alibaba.nls.client.protocol.OutputFormatEnum;
import com.alibaba.nls.client.protocol.SampleRateEnum;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizer;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerListener;
import com.alibaba.nls.client.protocol.tts.SpeechSynthesizerResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * @Description : SpeechServiceImpl
 * @Date : 2019/8/20
 * @Author : pmdream
 */
@Slf4j
@Service
public class SpeechServiceImpl implements SpeechService {

    private static final int RETRY_NUM = 3;
    private static final long RETRY_INTERVAL = 2000;

    @Autowired
    private SpeechClientFactory speechClientFactory;

    @Autowired
    private SpeechProperties speechProperties;

    @Override
    public boolean speechSynthesizer(String content, String mp3Path) {

        String token = speechClientFactory.getToken();
        if(StringUtils.isBlank(token)){
            return false;
        }
        SpeechClientFactory.client.setToken(token);
        int i = 0;
        while (i< RETRY_NUM){
            int result = process(SpeechClientFactory.client, content, mp3Path);
            if(result == 1){
                break;
            }
            i++;
            try {
                Thread.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {}
        }
        return i!= RETRY_NUM;
    }

    private SpeechSynthesizerListener getSynthesizerListener(String mp3Path) {
        SpeechSynthesizerListener listener = null;
        try {
            listener = new SpeechSynthesizerListener() {
                File f = new File(mp3Path);
                FileOutputStream fout = new FileOutputStream(f);

                // 语音合成结束
                @Override
                public void onComplete(SpeechSynthesizerResponse response) {
                    // 状态码 20000000 表示识别成功
                    log.info("[SpeechServiceImpl] onComplete 语音合成结束; name = {};  status = {}; output file = {}",
                            response.getName(), response.getStatus(), f.getAbsolutePath());
                }

                // 语音合成的语音二进制数据
                @Override
                public void onMessage(ByteBuffer message) {
                    try {
                        byte[] bytesArray = new byte[message.remaining()];
                        message.get(bytesArray, 0, bytesArray.length);
                        fout.write(bytesArray);
                    } catch (IOException e) {
                        log.error("[SpeechServiceImpl] onMessage error ; Msg = {};" + e.getMessage());
                    }
                }
            };
        } catch (Exception e) {
            log.error("[SpeechServiceImpl] getSynthesizerListener error ; Msg = {};" + e.getMessage());
        }
        return listener;
    }

    private int process(NlsClient client, String content, String mp3Path) {
        SpeechSynthesizer synthesizer = null;
        try {
            // Step1 创建实例,建立连接
            synthesizer = new SpeechSynthesizer(client, getSynthesizerListener(mp3Path));
            synthesizer.setAppKey(speechProperties.getAppKey());
            // 设置返回音频的编码格式
            synthesizer.setFormat(OutputFormatEnum.MP3);
            // 设置返回音频的采样率
            synthesizer.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
            // 设置用于语音合成的文本
            synthesizer.setText(content);
//            synthesizer.setText("合同中列示的产品、租金、租赁期限等内容已知悉");
            synthesizer.setVolume(100);
            // Step2 此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认
            synthesizer.start();
            // Step3 等待语音合成结束
            synthesizer.waitForComplete();
        } catch (Exception e) {
            log.info("[SpeechServiceImpl] ====语音合成失败==== ", e.getMessage());
            return 0;
        } finally {
            // Step4 关闭连接
            if (null != synthesizer) {
                synthesizer.close();
            }
        }
        return 1;
    }
}

为了脱敏;所以有些import的类就删掉了,放在上述代码中;

5. 读取yml文件的两种方式

@ConfigurationProperties与@value的对比:

最大的优势,也就是业务中主要是@ConfigurationProperties可以进行批量的读取配置;

只需要属性名与yml配置进行对应就好了~

6. 总结

阿里官方api,还是应该多看SDK的文档;

文档总因为建议如果发生语音合成失败,某些错误时偶现的,建议再次尝试;

所以等待两秒再次尝试这样的方式;

获取ali的token的时候,也采用尝试三次,睡眠两秒的方式

还有一点,yml文件读取其实是晚于static方法的,所以如果使用懒汉式单例会出现问题;

所以思想还是lazyloading

 

附录

阿里sdk地址: https://help.aliyun.com/document_detail/84437.html

阿里的错误中也会包含偶现类的错误,上次一次业务异常就是发生了这个问题;

50000000默认的服务端错误如果偶现可以忽略,重复出现请提交工单
50000001内部调用错误如果偶现可以忽略,重复出现请提交工单

阿里获取token:https://help.aliyun.com/document_detail/72153.html?spm=a2c4g.11186623.2.19.2d99259eCsZsCJ

阿里的错误状态说明:https://www.alibabacloud.com/help/zh/doc-detail/43906.htm?spm=a2c4g.11186623.2.17.27987229J6gDTj

服务端错误码表,HttpCode是5xx,表示服务不可用。此时一般建议重试或联系商品页面的API服务商。

错误代码Http 状态码语义解决方案
Internal Error500API 网关内部错误建议重试。
Failed To Invoke Backend Service500底层服务错误API 提供者底层服务错误,建议重试,如果重试多次仍然不可用,可联系 API 服务商解决。
Service Unavailable503服务不可用建议稍后重试。
Async Service504后端服务超时建议稍后重试。

所以如果失败,进行重试还是很有必要的。(不过一般错误都是因为欠费hhh) 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值