Netty对接阿里云语音识别和录音识别

阿里云实时语音识别:https://help.aliyun.com/document_detail/84430.html?spm=a2c4g.324262.0.0.564f73e9O6yq25

阿里云录音识别:https://help.aliyun.com/document_detail/90727.html?spm=a2c4g.90726.0.0.662d73e9qr8DqE

语音识别的流程为:前端和后端构建websocket连接,然后传二进制音频流给后端,后端拿到音频流,后阿里云构建websocket连接,转发音频流,阿里云收到后进行翻译,再返回给后端,后端再返回给前端

录音识别流程为:前端上传一段录音到阿里云oss上,返回录音的url,然后调用阿里云的录音识别拿到录音并解析,将结果返回给后端,后端再将结果返回给前端

实现

pom.xml

<dependencies>
    <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.76.Final</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.18</version>
    </dependency>

<!--        <dependency>-->
<!--            <groupId>com.aliyun</groupId>-->
<!--            <artifactId>aliyun-java-sdk-core</artifactId>-->
<!--            <version>3.7.1</version>-->
<!--        </dependency>-->
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.72</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--阿里云语音识别-->
    <dependency>
        <groupId>com.alibaba.nls</groupId>
        <artifactId>nls-sdk-transcriber</artifactId>
        <version>2.2.1</version>
    </dependency>

    <!--swagger3-->
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-boot-starter</artifactId>
        <version>3.0.0</version>
    </dependency>

    <!-- 阿里云OSS -->
    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>

    <!-- guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>31.0.1-jre</version>
    </dependency>


<!--        <dependency>-->
<!--            <groupId>org.springblade</groupId>-->
<!--            <artifactId>blade-core-boot</artifactId>-->
<!--            <version>3.0.1.RELEASE</version>-->
<!--            <exclusions>-->
<!--                <exclusion>-->
<!--                    <groupId>org.springblade</groupId>-->
<!--                    <artifactId>blade-core-cloud</artifactId>-->
<!--                </exclusion>-->
<!--            </exclusions>-->
<!--        </dependency>-->

    <!--redis客户端-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- lettuce连接池 -->
    <dependency>
        <groupId>io.lettuce</groupId>
        <artifactId>lettuce-core</artifactId>
        <version>6.1.6.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>

</dependencies>

ApplicationService 启动时构建阿里云的nls连接,并获取token放入redis中

@Slf4j
@Service
public class ApplicationService implements ApplicationListener<ContextRefreshedEvent> {

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    public static ApplicationService application = null;

    @Value("${aliyun.nls.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.nls.accessKeySecret}")
    private String accessKeySecret;
    @Value("${aliyun.nls.url}")
    private String url;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //启动客户端
        if (contextRefreshedEvent.getApplicationContext().getParent() == null) {
            log.info("客户端启动-------------------------->");
            synchronized (this) {
                ApplicationService.application = this;
                new NlsClientService(accessKeyId, accessKeySecret, url);
                log.info("阿里云 NlsClient 初始化完毕");
                AccessToken accessToken = NlsClientService.getAccessToken();
                redisTemplate.opsForValue().set("nlp:token", accessToken.getToken(), accessToken.getExpireTime(), TimeUnit.SECONDS);
            }
        }
    }
}

构建nls和获取token的具体实现

@Slf4j
public class NlsClientService {

    private static NlsClient client;

    private static AccessToken accessToken;

    public NlsClientService(String accessKeyId, String accessKeySecret, String url) {

        //创建NlsClient实例,应用全局创建一个即可,生命周期可和整个应用保持一致,默认服务地址为阿里云线上服务地址
        applyAccessToken(accessKeyId, accessKeySecret);
        if (url.isEmpty()) {
            client = new NlsClient(accessToken.getToken());
        } else {
            client = new NlsClient(url, accessToken.getToken());
        }
    }

    public static AccessToken getAccessToken() {
        return accessToken;
    }

    public static void applyAccessToken(String accessKeyId, String accessKeySecret) {
        accessToken = new AccessToken(accessKeyId, accessKeySecret);
        try {
            accessToken.apply();
            log.info("get token: " + accessToken.getToken() + ", expire time: " + accessToken.getExpireTime());
        } catch (IOException e) {
            log.error("https获取accessToken失败!" + e.getMessage());
        }
    }

    public static NlsClient getNlsClient() {
        return client;
    }
}

netty服务端

@Slf4j
@Configuration
public class NettyWebSocketServer {
    public static final int WEB_SOCKET_PORT = 9000;
    // 创建线程池执行器
    private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    private EventLoopGroup workerGroup = new NioEventLoopGroup(NettyRuntime.availableProcessors());

    /**
     * 启动 ws server
     *
     * @return
     * @throws InterruptedException
     */
    @PostConstruct
    public void start() throws InterruptedException {
        run();
    }

    /**
     * 销毁
     */
    @PreDestroy
    public void destroy() {
        Future<?> future = bossGroup.shutdownGracefully();
        Future<?> future1 = workerGroup.shutdownGracefully();
        future.syncUninterruptibly();
        future1.syncUninterruptibly();
        log.info("关闭 ws server 成功");
    }

    public void run() throws InterruptedException {
        // 服务器启动引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        //ws升级为wss
                        SslContext sslCtx = SslUtil.createSSLContext();
                        pipeline.addLast(sslCtx.newHandler(socketChannel.alloc()));
                        //10秒客户端没有向服务器发送心跳则关闭连接
                        pipeline.addLast(new IdleStateHandler(10, 10, 0));
                        // 因为使用http协议,所以需要使用http的编码器,解码器
                        pipeline.addLast(new HttpServerCodec());
                        // 以块方式写,添加 chunkedWriter 处理器
                        pipeline.addLast(new ChunkedWriteHandler());
                        /**
                         * 说明:
                         *  1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来;
                         *  2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因
                         */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        /**
                         * 说明:
                         *  1. 对于 WebSocket,它的数据是以帧frame 的形式传递的;
                         *  2. 可以看到 WebSocketFrame 下面有6个子类
                         *  3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri
                         *  4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接;
                         *      是通过一个状态码 101 来切换的
                         */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));
                        // 自定义handler ,处理业务逻辑
                        pipeline.addLast(new NettyWebSocketServerHandler());
                    }
                });
        // 启动服务器,监听端口,阻塞直到启动成功
        serverBootstrap.bind(WEB_SOCKET_PORT).sync();
    }

}

语音识别handler

/**
 * 自定义handler
 */
@Slf4j
@Component
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {

    private NlpService nlpService;

    // 当web客户端连接后,触发该方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.nlpService = getService();
    }

    private NlpService getService() {
        return SpringUtil.getBean(NlpService.class);
    }

    /**
     * netty断联
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        SpeechTranscriber transcriber = (SpeechTranscriber) transcriberMap.get(ctx.channel());
        if (Objects.nonNull(transcriber)) {
            //阿里云netty10s断开,连接状态变为STATE_CLOSED
            if (SpeechReqProtocol.State.STATE_CLOSED.toString() != transcriber.getState().toString()) {
                transcriber.stop();
            }
            transcriberMap.remove(ctx.channel());
            transcriber.close();
        }
        log.warn("{} 已经断开", ctx.channel());
    }


    /**
     * 处理异常
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.warn("{} 已经异常断开 异常是{}", ctx.channel(), cause.getMessage());
        ctx.channel().close();
    }

    /**
     * 心跳检查
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            // 心跳检测超时事件
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                log.warn("{} 已经 10s 没有读到数据了,关闭连接", ctx.channel());
            } else if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
                log.warn("{} 已经 10s 没有写出数据了,关闭连接", ctx.channel());
            }
            ctx.channel().close();
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
        if (msg instanceof BinaryWebSocketFrame) {
            //读取音频二进制流
            ByteBuf byteBuf = msg.content();
            byte[] byteArray = new byte[byteBuf.readableBytes()];
            byteBuf.readBytes(byteArray);

            //调用阿里云语音翻译
            nlpService.speechTranslation(ctx.channel(), byteArray);
        }
    }
}

音频流格式,3200B的字节数组

byte[] byteArray = {-1, -2, -51, -4, 105, -7, 84, -9, -2, -11, -56, -12, -64, -13, 47, -13, -28, -13, 110, -12, 25, -11, 78, -10, -95, -9, -21, -7, -123, -4, 106, -2, -109, 0, 85, 3, -119, 5, -63, 6, -19, 7, -33, 9, -35, 10, 72, 11, -72, 11, -75, 11, 34, 11, 103, 10, 89, 9, 74, 7, -99, 5, 11, 4, -59, 1, -109, -1, 66, -2, -47, -4, 39, -5, -111, -6, -46, -7, 71, -7, -127, -7, 116, -7, -105, -7, 93, -6, 118, -5, -111, -4, 25, -2, -23, -1, -119, 1, 103, 3, -4, 4, 45, 6, 32, 7, -41, 7, 70, 8, -91, 8, 116, 8, -25, 7, -94, 7, -96, 6, 52, 5, -79, 3, -41, 1, -38, -1, 65, -2, 100, -4, 42, -6, 113, -8, 33, -9, -118, -11, 12, -12, 77, -13, 23, -13, 75, -13, -99, -13, 37, -12, 9, -11, 40, -10, -85, -9, 9, -7, 2, -5, 92, -3, 126, -1, -47, 1, -25, 3, -19, 5, 112, 7, -106, 8, -37, 9, -85, 10, 93, 11, -77, 11, 72, 11, 2, 11, 32, 10, -4, 8, -72, 7, -33, 5, 123, 4, -51, 2, -125, 0, 59, -2, 48, -4, -57, -6, -27, -8, 106, -9, -70, -10, 51, -10, -27, -11, -53, -11, -10, -11, -91, -10, -59, -9, -26, -8, -8, -7, -121, -5, -120, -3, 88, -1, -44, 0, -60, 2, -80, 4, 21, 6, 90, 7, 92, 8, 3, 9, -97, 9, 115, 9, -69, 8, 1, 8, 70, 7, 12, 6, 84, 4, -34, 2, 127, 1, -6, -1, -20, -3, 8, -4, -28, -6, -21, -7, 18, -7, 71, -8, 22, -8, 123, -8, -53, -8, 12, -7, -88, -7, 46, -5, -28, -4, 17, -2, -75, -1, -77, 1, 94, 3, -28, 4, 69, 6, 85, 7, -124, 8, -110, 9, 4, 10, 5, 10, -83, 9, 34, 9, 24, 8, 124, 6, 37, 5, -111, 3, 17, 2, -115, 0, -65, -2, -2, -4, 83, -5, -100, -7, -63, -9, -6, -11, 35, -11, 120, -12, 29, -12, 67, -12, -96, -12, 106, -11, 60, -10, -86, -9, 61, -7, -81, -6, -31, -4, -32, -2, 120, 0, 65, 2, 30, 4, -19, 5, 80, 7, -91, 8, -14, 9, -33, 10, -122, 11, 99, 11, -56, 10, 19, 10, 104, 9, 28, 8, 100, 6, -20, 4, 103, 3, -86, 1, 109, -1, 122, -3, 8, -4, -116, -6, -12, -8, 123, -9, 114, -10, 18, -10, -104, -11, 1, -11, 81, -11, 64, -10, 29, -9, -81, -9, 105, -8, -60, -7, 109, -5, -18, -4, 53, -2, -123, -1, 127, 1, 6, 3, -113, 3, -114, 4, -90, 5, 119, 6, -77, 6, 91, 6, 73, 6, 101, 6, -18, 5, -11, 4, 33, 4, -76, 3, 50, 3, -39, 1, -66, 0, 72, 0, 114, -1, -121, -2, -18, -3, 100, -3, 39, -3, 74, -3, 49, -3, 32, -3, -92, -3, 118, -2, -45, -2, 64, -1, 27, 0, -21, 0, -48, 1, 112, 2, -4, 2, -21, 3, -60, 4, 63, 5, -94, 5, 32, 6, 99, 6, -35, 5, 76, 5, -25, 4, 27, 4, 86, 3, -104, 2, 93, 1, 18, 0, 60, -1, 82, -2, -24, -4, 55, -4, -19, -5, 31, -5, -65, -6, 125, -6, 34, -6, 126, -6, 1, -5, 98, -5, 52, -4, -92, -3, -58, -2, 105, -1, 118, 0, -40, 1, -23, 2, 0, 4, 11, 5, -118, 5, 18, 6, 118, 6, 111, 6, 52, 6, -59, 5, 116, 5, -61, 4, -97, 3, -91, 2, -122, 1, 51, 0, -30, -2, -63, -3, -87, -4, -54, -5, -18, -6, -25, -7, 77, -7, 14, -7, 24, -7, -27, -8, 2, -7, -78, -7, 80, -6, -2, -6, -6, -5, 0, -3, 49, -2, 51, -1, -13, -1, -61, 0, -65, 1, -102, 2, 58, 3, -43, 3, 64, 4, 72, 4, 38, 4, -23, 3, -117, 3, -24, 2, 87, 2, -104, 1, -100, 0, -117, -1, -121, -2, -93, -3, -104, -4, -72, -5, 65, -5, -49, -6, 109, -6, 53, -6, 57, -6, 119, -6, -82, -6, 40, -5, -24, -5, -70, -4, -114, -3, -80, -2, 16, 0, -58, 0, -28, 1, 104, 3, 83, 4, 81, 5, 49, 6, -45, 6, 78, 7, 108, 7, 85, 7, -22, 6, -77, 6, 109, 6, 77, 5, 91, 4, -74, 3, -121, 2, 67, 1, 44, 0, 36, -1, 79, -2, -56, -3, 2, -3, 63, -4, -41, -5, -20, -5, -53, -5, -121, -5, 75, -4, 32, -3, -68, -3, 125, -2, 32, -1, -7, -1, 1, 1, 17, 2, -23, 2, -49, 3, -57, 4, 95, 5, 87, 5, 125, 5, -82, 5, -109, 5, 110, 5, 44, 5, -102, 4, 3, 4, 72, 3, 43, 2, 69, 1, 96, 0, 81, -1, 92, -2, -106, -3, -15, -4, -42, -5, 40, -5, -30, -6, 111, -6, 55, -6, 46, -6, 70, -6, -127, -6, -50, -6, 64, -5, -45, -5, -109, -4, 85, -3, -4, -3, -85, -2, -111, -1, 104, 0, 6, 1, 113, 1, -14, 1, 101, 2, -81, 2, -83, 2, -101, 2, -77, 2, -105, 2, 39, 2, -96, 1, 32, 1, -85, 0, -22, -1, -32, -2, 57, -2, -85, -3, -22, -4, 53, -4, 117, -5, 5, -5, -68, -6, 115, -6, 85, -6, 57, -6, -126, -6, -20, -6, 46, -5, -95, -5, 52, -4, -30, -4, 119, -3, 16, -2, -11, -2, -59, -1, 107, 0, 21, 1, -90, 1, 83, 2, -33, 2, 79, 3, -93, 3, -17, 3, 58, 4, 67, 4, 87, 4, 90, 4, 123, 4, -117, 4, 71, 4, 84, 4, 80, 4, 26, 4, -20, 3, -81, 3, 101, 3, 48, 3, -34, 2, 96, 2, 11, 2, -91, 1, 23, 1, -88, 0, 92, 0, -5, -1, -118, -1, 54, -1, -7, -2, -108, -2, 99, -2, 84, -2, 66, -2, 54, -2, 51, -2, 121, -2, -88, -2, -58, -2, 65, -1, -95, -1, 13, 0, -62, 0, 21, 1, 70, 1, -86, 1, -4, 1, 9, 2, 37, 2, 78, 2, 85, 2, 100, 2, 82, 2, -21, 1, -84, 1, -110, 1, 27, 1, 115, 0, 61, 0, -4, -1, 76, -1, -68, -2, 121, -2, 53, -2, -28, -3, -74, -3, 116, -3, 118, -3, -84, -3, -103, -3, -115, -3, -44, -3, 58, -2, -56, -2, 4, -1, 68, -1, -53, -1, 27, 0, 110, 0, -122, 0, -77, 0, -25, 0, -46, 0, -77, 0, 104, 0, 59, 0, 35, 0, -85, -1, 46, -1, -49, -2, 34, -2, -108, -3, 19, -3, 126, -4, 25, -4, -89, -5, 70, -5, -43, -6, 126, -6, 118, -6, 98, -6, 73, -6, -110, -6, -13, -6, 101, -5, -15, -5, -126, -4, 43, -3, -42, -3, -53, -2, -85, -1, -110, 0, -70, 1, -97, 2, 105, 3, 55, 4, 13, 5, -77, 5, 96, 6, 16, 7, 101, 7, -60, 7, 13, 8, 44, 8, 53, 8, 29, 8, -28, 7, -116, 7, 81, 7, 9, 7, 124, 6, -24, 5, 119, 5, -25, 4, 59, 4, -128, 3, -65, 2, -14, 1, 51, 1, 80, 0, 115, -1, -58, -2, 1, -2, 57, -3, -117, -4, -15, -5, 127, -5, 10, -5, -106, -6, 87, -6, 60, -6, 72, -6, 83, -6, 113, -6, -49, -6, 78, -5, -43, -5, -116, -4, 66, -3, 9, -2, -46, -2, -111, -1, 119, 0, 85, 1, 46, 2, 11, 3, -97, 3, 59, 4, -65, 4, 9, 5, 87, 5, 112, 5, 111, 5, 90, 5, 15, 5, -112, 4, -3, 3, 79, 3, -109, 2, -66, 1, -76, 0, -62, -1, -42, -2, -76, -3, -104, -4, -96, -5, -41, -6, 20, -6, 107, -7, 8, -7, -82, -8, -109, -8, -86, -8, -61, -8, 30, -7, -64, -7, 102, -6, -12, -6, -87, -5, -99, -4, -123, -3, 76, -2, 21, -1, -32, -1, -100, 0, 68, 1, -83, 1, -10, 1, 67, 2, 92, 2, 63, 2, 43, 2, -13, 1, -118, 1, -18, 0, 56, 0, -113, -1, -2, -2, 117, -2, -58, -3, 44, -3, -64, -4, 79, -4, 17, -4, -18, -5, -18, -5, 44, -4, -97, -4, 48, -3, -35, -3, -51, -2, -57, -1, -78, 0, -32, 1, 62, 3, 127, 4, -43, 5, -18, 6, -37, 7, -41, 8, -68, 9, 101, 10, -42, 10, 63, 11, 103, 11, 40, 11, -108, 10, -40, 9, -3, 8, 7, 8, -17, 6, -123, 5, 34, 4, -73, 2, 46, 1, -100, -1, 32, -2, -7, -4, -52, -5, -61, -6, -23, -7, 49, -7, -37, -8, -64, -8, -36, -8, 68, -7, -31, -7, -80, -6, -91, -5, -107, -4, -60, -3, 29, -1, 98, 0, -96, 1, -35, 2, 27, 4, 30, 5, -15, 5, -86, 6, 41, 7, 108, 7, -108, 7, 122, 7, 5, 7, 119, 6, -38, 5, -43, 4, -110, 3, 106, 2, 20, 1, -96, -1, 50, -2, -73, -4, 54, -5, 5, -6, 4, -7, -18, -9, 59, -9, -28, -10, -109, -10, 123, -10, -92, -10, -6, -10, -116, -9, 88, -8, 61, -7, 72, -6, 119, -5, -80, -4, -61, -3, -43, -2, 18, 0, 37, 1, 13, 2, -28, 2, 104, 3, -62, 3, 20, 4, 41, 4, 6, 4, -74, 3, 83, 3, -83, 2, -34, 1, 30, 1, 68, 0, 89, -1, 126, -2, -128, -3, -112, -4, -38, -5, 62, -5, -97, -6, 31, -6, -1, -7, -16, -7, -18, -7, 37, -6, -118, -6, 34, -5, -66, -5, 124, -4, 70, -3, 35, -2, 28, -1, 2, 0, -45, 0, -88, 1, 124, 2, 62, 3, -39, 3, 104, 4, -21, 4, 47, 5, 94, 5, -99, 5, -61, 5, -46, 5, -24, 5, -27, 5, -56, 5, -72, 5, -105, 5, 77, 5, -3, 4, -26, 4, -88, 4, 83, 4, 44, 4, -46, 3, 113, 3, 23, 3, -93, 2, 79, 2, 1, 2, -87, 1, 67, 1, -38, 0, 116, 0, 23, 0, -76, -1, 75, -1, -12, -2, -93, -2, 113, -2, 43, -2, -17, -3, 0, -2, -5, -3, -4, -3, 46, -2, 98, -2, -74, -2, 43, -1, -125, -1, -32, -1, 83, 0, -32, 0, 98, 1, -71, 1, 45, 2, -116, 2, -47, 2, 22, 3, 41, 3, 41, 3, 39, 3, -50, 2, 52, 2, -92, 1, -21, 0, 41, 0, 87, -1, 120, -2, -83, -3, -53, -4, -17, -5, 66, -5, -102, -6, 41, -6, -37, -7, -86, -7, -87, -7, -98, -7, -66, -7, 27, -6, -109, -6, 54, -5, -5, -5, -37, -4, -55, -3, -72, -2, -82, -1, -87, 0, -116, 1, 82, 2, 10, 3, -107, 3, -19, 3, 14, 4, 14, 4, -5, 3, -74, 3, 65, 3, -83, 2, -22, 1, -1, 0, 7, 0, 5, -1, 14, -2, 30, -3, 33, -4, 67, -5, 115, -6, -57, -7, 62, -7, -65, -8, -128, -8, -120, -8, -86, -8, -24, -8, 85, -7, -9, -7, -46, -6, -75, -5, -68, -4, -13, -3, 39, -1, 98, 0, -102, 1, -35, 2, 23, 4, 23, 5, 9, 6, -6, 6, -83, 7, 60, 8, -87, 8, -16, 8, 22, 9, -10, 8, -74, 8, 91, 8, -59, 7, 27, 7, 80, 6, 76, 5, 97, 4, 103, 3, 67, 2, 58, 1, 54, 0, 52, -1, 69, -2, 127, -3, -44, -4, 40, -4, -63, -5, -115, -5, 106, -5, 103, -5, -104, -5, -24, -5, 82, -4, -31, -4, 121, -3, 44, -2, -15, -2, -72, -1, 127, 0, 63, 1, 25, 2, -48, 2, 84, 3, -35, 3, 55, 4, 115, 4, -116, 4, 101, 4, 47, 4, -35, 3, 95, 3, -71, 2, -27, 1, 8, 1, 28, 0, 51, -1, 86, -2, 101, -3, -118, -4, -33, -5, 64, -5, -64, -6, 116, -6, 77, -6, 74, -6, 116, -6, -55, -6, 63, -5, -44, -5, -124, -4, 74, -3, 31, -2, 18, -1, 6, 0, -40, 0, -85, 1, 109, 2, 33, 3, -70, 3, 32, 4, 106, 4, -116, 4, -116, 4, 110, 4, 17, 4, -102, 3, 6, 3, 65, 2, 107, 1, 123, 0, -124, -1, -125, -2, 110, -3, 87, -4, 94, -5, 114, -6, -93, -7, -1, -8, -117, -8, 60, -8, -14, -9, -37, -9, 16, -8, 86, -8, -53, -8, 108, -7, 5, -6, -64, -6, -93, -5, -124, -4, 105, -3, 75, -2, 66, -1, 61, 0, 27, 1, 1, 2, -41, 2, -108, 3, 78, 4, -28, 4, 100, 5, -29, 5, 79, 6, -94, 6, -40, 6, 15, 7, 81, 7, 87, 7, 57, 7, 34, 7, -10, 6, -77, 6, 91, 6, -31, 5, 117, 5, -13, 4, 86, 4, -80, 3, -9, 2, 58, 2, 115, 1, -96, 0, -18, -1, 51, -1, 108, -2, -71, -3, 6, -3, 111, -4, 2, -4, -72, -5, -123, -5, 98, -5, 115, -5, -66, -5, 20, -4, 113, -4, -28, -4, 118, -3, 39, -2, -43, -2, -114, -1, 90, 0, 29, 1, -36, 1, -111, 2, 34, 3, -103, 3, -13, 3, 26, 4, 36, 4, -20, 3, -114, 3, 13, 3, 72, 2, -122, 1, -60, 0, -35, -1, -6, -2, 17, -2, 55, -3, -121, -4, -32, -5, 97, -5, 36, -5, -4, -6, -17, -6, 9, -5, 97, -5, -13, -5, -116, -4, 73, -3, 53, -2, 28, -1, 6, 0, -26, 0, -83, 1, -126, 2, 58, 3, -50, 3, 62, 4, 109, 4, 126, 4, 95, 4, 11, 4, -92, 3, 0, 3, 34, 2, 52, 1, 50, 0, 46, -1, 28, -2, 6, -3, 11, -4, 29, -5, 57, -6, 113, -7, -57, -8, 75, -8, -2, -9, -50, -9, -27, -9, 48, -8, -99, -8, 56, -7, -16, -7, -40, -6, -30, -5, -26, -4, 11, -2, 51, -1, 57, 0, 48, 1, 14, 2, -37, 2, -98, 3, 60, 4, -58, 4, 56, 5, -84, 5, 9, 6, 66, 6, -127, 6, -77, 6, -62, 6, -30, 6, 11, 7, 23, 7, 21, 7, 0, 7, -35, 6, -92, 6, 91, 6, 20, 6, -67, 5, 80, 5, -49, 4, 52, 4, -107, 3, -33, 2, 20, 2, 70, 1, 110, 0, -114, -1, -74, -2, -25, -3, 59, -3, -83, -4, 34, -4, -61, -5, -94, -5, -98, -5, -57, -5, 21, -4, 114, -4, 1, -3, -84, -3, 111, -2, 86, -1, 60, 0, 52, 1, 44, 2, 26, 3, 8, 4, -47, 4, 120, 5, -32, 5, 5, 6, 0, 6, -77, 5, 48, 5, 116, 4, 126, 3, 113, 2, 57, 1, -30, -1, -95, -2, 112, -3, 77, -4, 69, -5, 93, -6, -99, -7, 18, -7, -56, -8, -76, -8, -71, -8, -9, -8, 118, -7, 4, -6, -80, -6, -121, -5, 107, -4, 93, -3, 83, -2, 73, -1, 28, 0, -54, 0, 108, 1, -20, 1, 55, 2, 86, 2, 67, 2, -6, 1, -106, 1, 13, 1, 115, 0, -48, -1, 21, -1, 89, -2, -111, -3, -46, -4, 55, -4, -111, -5, 16, -5, -67, -6, 118, -6, 80, -6, 40, -6, 37, -6, 82, -6, -115, -6, -13, -6, 117, -5, 13, -4, -88, -4, 62, -3, 1, -2, -65, -2, 82, -1, -38, -1, 100, 0, -32, 0, 64, 1, -102, 1, 3, 2, 121, 2, -34, 2, 70, 3, -56, 3, 93, 4, -10, 4, 120, 5, 15, 6, -73, 6, 58, 7, -71, 7, 45, 8, 118, 8, -84, 8, -77, 8, 116, 8, 29, 8, -98, 7, -9, 6, 58, 6, 72, 5, 71, 4, 57, 3, 8, 2, -50, 0, -112, -1, 90, -2, 60, -3, 46, -4, 85, -5, -82, -6, 69, -6, 38, -6, 34, -6, 98, -6, -14, -6, -86, -5, -100, -4, -81, -3, -38, -2, 32, 0, 90, 1, -109, 2, -54, 3, -24, 4, -23, 5, -51, 6, -123, 7, 22, 8, 98, 8, 90, 8, 19, 8, -112, 7, -75, 6, -112, 5, 34, 4, -126, 2, -73, 0, -51, -2, -37, -4, -5, -6, 82, -7, -32, -9, -111, -10, -92, -11, 6, -11, -84, -12, -94, -12, -24, -12, 126, -11, 78, -10, 82, -9, -121, -8, -37, -7, 85, -5, -49, -4, 72, -2, -58, -1, 49, 1, -124, 2, -77, 3, -84, 4, 94, 5, -45, 5, -9, 5, -64, 5, 62, 5, -127, 4, -96, 3, -127, 2, 73, 1, 25, 0, -24, -2, -62, -3, -80, -4, -79, -5, -53, -6, -5, -7, 47, -7, 127, -8, -3, -9, -87, -9, -124, -9, -114, -9, -42, -9, 102, -8, 46, -7, 49, -6, 82, -5, 124, -4, -54, -3, 24, -1, 67, 0, 90, 1, 75, 2, 20, 3, -87, 3, 20, 4, 116, 4, -76, 4, -30, 4, -2, 4, 22, 5, 73, 5, -125, 5, -70, 5, -8, 5, 73, 6, -89, 6, -26, 6, 13, 7, 54, 7, 82, 7, 88, 7, 59, 7, -2, 6, -84, 6, 75, 6, -71, 5, 18, 5, 79, 4, 105, 3, 107, 2, 63, 1, 18, 0, -20, -2, -75, -3, -112, -4, -118, -5, -78, -6, 18, -6, -92, -7, -127, -7, -81, -7, 31, -6, -52, -6, -101, -5, -97, -4, -41, -3, 11, -1, 76, 0, -109, 1, -47, 2, -2, 3, 1, 5, -40, 5, -107, 6, 29, 7, 104, 7, -126, 7, 62, 7, -60, 6, 3, 6, -28, 4, -110, 3, -5, 1, 47, 0, 78, -2, 85, -4, 119, -6, -66, -8, 65, -9, 38, -10, 78, -11, -22, -12, -11, -12, 84, -11, 40, -10, 69, -9, -91, -8, 63, -6, -31, -5, -99, -3, 100, -1, 7, 1, -97, 2, 40, 4, -128, 5, -93, 6, -121, 7, 52, 8, -100, 8, -90, 8, 77, 8, -113, 7, 115, 6, 14, 5, 84, 3, 94, 1, 79, -1, 51, -3, 53, -5, 110, -7, -26, -9, -111, -10, 115, -11, -93, -12, 27, -12, -73, -13, -98, -13, -80, -13, -16, -13, 94, -12, -13, -12, -49, -11, -21, -10, 71, -8, -13, -7, -46, -5, -36, -3, -13, -1, 9, 2, 6, 4, -54, 5, 80, 7, -126, 8, 91, 9, -69, 9, -72, 9, 110, 9, -4, 8, 122, 8, -24, 7, 100, 7, -10, 6, -111, 6, 69, 6, 1, 6, -28, 5, -45, 5, -63, 5, -91, 5, 117, 5, 71, 5, 17, 5, -59, 4, -116, 4, 101, 4, 55, 4, -7, 3, -96, 3, 54, 3, -75, 2, 19, 2, 73, 1, 108, 0, 120, -1, 116, -2, 118, -3, -124, -4, -83, -5, -12, -6, 118, -6, 73, -6, 92, -6, -87, -6, 33, -5, -52, -5, -87, -4, -113, -3, -122, -2, 115, -1, 73, 0, 15, 1, -65, 1, 116, 2, 38, 3, -67, 3, 62, 4, -80, 4, -6, 4, -6, 4, -56, 4, 93, 4, -91, 3, -97, 2, 67, 1, -84, -1, -13, -3, 31, -4, 72, -6, -106, -8, 37, -9, 11, -10, 101, -11, 52, -11, 127, -11, 77, -10    

nls业务类

public interface NlpService {

    /**
     * 语音翻译
     *
     * @param channel
     * @param byteArray
     */
    void speechTranslation(Channel channel, byte[] byteArray);

    /**
     * 录音翻译
     *
     * @param fileUrl
     */
    R recordingTranslation(String fileUrl);

}

@Slf4j
@Service
public class NlpServiceImpl implements NlpService {

    //private final String token = "6d1216b898ef40e090871b243f4e66a1";

    @Value("${aliyun.nls.appKey}")
    private String appKey;
    @Value("${aliyun.nls.accessKeyId}")
    private String accessKeyId;
    @Value("${aliyun.nls.accessKeySecret}")
    private String accessKeySecret;

    @Autowired
    RedisTemplate<String, String> redisTemplate;

    @Override
    public void speechTranslation(Channel channel, byte[] byteArray) {
        String key = "nlp:token";
        String token;
        Long ttl = redisTemplate.getExpire(key, TimeUnit.SECONDS);
        //token过期
        if (ttl == null && ttl < 0) {
            NlsClientService.applyAccessToken(accessKeyId, accessKeySecret);
            AccessToken accessToken = NlsClientService.getAccessToken();
            token = accessToken.getToken();
            redisTemplate.opsForValue().set(key, token, accessToken.getExpireTime(), TimeUnit.SECONDS);
            NlsClientService.getNlsClient().setToken(token);
        } else {
            token = redisTemplate.opsForValue().get(key);
        }
        SpeechTranslationService service = new SpeechTranslationService(appKey, token, channel);
        service.process(channel, byteArray);
    }

    @Override
    public R recordingTranslation(String fileUrl) {
        RecordingTranslationService service = new RecordingTranslationService(accessKeyId, accessKeySecret);
        // 第一步:提交录音文件识别请求,获取任务ID用于后续的识别结果轮询。
        String taskId = service.submitFileTransRequest(appKey, fileUrl);
        if (taskId == null) {
            log.error("录音文件识别请求失败!");
            return R.fail("录音文件识别请求失败!");
        }
        log.info("录音文件识别请求成功,task_id: " + taskId);

        // 第二步:根据任务ID轮询识别结果。
        String result = service.getFileTransResult(taskId);
        if (result == null) {
            log.error("录音文件识别结果查询失败!");
            return R.fail("录音文件识别结果查询失败!");
        }
        log.info("录音文件识别结果查询成功:" + result);
        Example example = JSON.parseObject(result, Example.class);
        log.info("录音结果映射后:{}", JSON.toJSONString(example));
        return R.data(example );
    }

}

语音识别

/**
 * 实时语音识别
 */
public class SpeechTranslationService {
    private String appKey;
    private String accessToken;
    private Channel channel;
    NlsClient client;

    public static Map<Channel, Object> transcriberMap = new ConcurrentHashMap<>(32);

    public SpeechTranslationService(String appKey, String token) {
        this.appKey = appKey;
        this.accessToken = token;
        client = NlsClientService.getNlsClient();
    }

    public SpeechTranslationService(String appKey, String token, Channel channel) {
        this.appKey = appKey;
        this.accessToken = token;
        this.channel = channel;
        client = NlsClientService.getNlsClient();
    }

    public SpeechTranslationService(String appKey, String token, String url) {
        this.appKey = appKey;
        this.accessToken = token;
        //创建NlsClient实例,应用全局创建一个即可,用户指定服务地址
        client = new NlsClient(url, accessToken);
    }

    public SpeechTranscriberListener getTranscriberListener() {
        SpeechTranscriberListener listener = new SpeechTranscriberListener() {
            //识别出中间结果.服务端识别出一个字或词时会返回此消息.仅当setEnableIntermediateResult(true)时,才会有此类消息返回
            @Override
            public void onTranscriptionResultChange(SpeechTranscriberResponse response) {
                // 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查
                //System.out.println("onTranscriptionResultChange:" + "name: " + response.getName() +
                //        //状态码 20000000 表示正常识别
                //        ", status: " + response.getStatus() +
                //        //句子编号,从1开始递增
                //        ", index: " + response.getTransSentenceIndex() +
                //        //当前的识别结果
                //        ", result: " + response.getTransSentenceText() +
                //        //当前已处理的音频时长,单位是毫秒
                //        ", time: " + response.getTransSentenceTime());
            }

            @Override
            public void onTranscriberStart(SpeechTranscriberResponse response) {
                System.out.println("onTranscriberStart:" + "task_id: " + response.getTaskId() +
                        "name: " + response.getName() +
                        ", status: " + response.getStatus());
            }

            @Override
            public void onSentenceBegin(SpeechTranscriberResponse response) {
                System.out.println("onSentenceBegin:" + "task_id: " + response.getTaskId() +
                        "name: " + response.getName() +
                        ", status: " + response.getStatus());
            }

            //识别出一句话.服务端会智能断句,当识别到一句话结束时会返回此消息
            @Override
            public void onSentenceEnd(SpeechTranscriberResponse response) {

                channel.writeAndFlush(new TextWebSocketFrame(response.getTransSentenceText()));

                System.out.println("onSentenceEnd:" + "name: " + response.getName() +
                        //状态码 20000000 表示正常识别
                        ", status: " + response.getStatus() +
                        //句子编号,从1开始递增
                        ", index: " + response.getTransSentenceIndex() +
                        //当前的识别结果
                        ", result: " + response.getTransSentenceText() +
                        //置信度
                        ", confidence: " + response.getConfidence() +
                        //开始时间
                        ", begin_time: " + response.getSentenceBeginTime() +
                        //当前已处理的音频时长,单位是毫秒
                        ", time: " + response.getTransSentenceTime() +
                        //关键词
                        ", words: " + response.getWords());
            }

            //识别完毕
            @Override
            public void onTranscriptionComplete(SpeechTranscriberResponse response) {
                System.out.println("onTranscriptionComplete:" + "task_id: " + response.getTaskId() +
                        ", name: " + response.getName() +
                        ", status: " + response.getStatus());
            }

            @Override
            public void onFail(SpeechTranscriberResponse response) {
                // 重要提示: task_id很重要,是调用方和服务端通信的唯一ID标识,当遇到问题时,需要提供此task_id以便排查
                System.out.println("onFail:" +
                        "task_id: " + response.getTaskId() +
                        //状态码 20000000 表示识别成功
                        ", status: " + response.getStatus() +
                        //错误信息
                        ", status_text: " + response.getStatusText());
            }
        };

        return listener;
    }

    public void process(Channel channel, byte[] byteArray) {
        SpeechTranscriber transcriber = (SpeechTranscriber) transcriberMap.get(channel);
        try {
            if (Objects.isNull(transcriber)) {
                // 创建实例,建立连接
                transcriber = new SpeechTranscriber(client, getTranscriberListener());
                transcriber.setAppKey(appKey);
                // 输入音频编码方式
                transcriber.setFormat(InputFormatEnum.PCM);
                // 输入音频采样率
                transcriber.setSampleRate(SampleRateEnum.SAMPLE_RATE_16K);
                // 是否返回中间识别结果
                transcriber.setEnableIntermediateResult(false);
                // 是否生成并返回标点符号
                transcriber.setEnablePunctuation(true);
                // 是否将返回结果规整化,比如将一百返回为100
                transcriber.setEnableITN(false);
                //开启词模式
                transcriber.addCustomedParam("enable_words", true);
                //此方法将以上参数设置序列化为json发送给服务端,并等待服务端确认
                transcriber.start();
                transcriberMap.put(channel, transcriber);
            }

            transcriber.send(byteArray, byteArray.length);

        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }

    public void shutdown() {
        client.shutdown();
    }

}

录音识别

/**
 * 录音文件识别
 */
public class RecordingTranslationService {
    // 地域ID,常量,固定值。
    public static final String REGIONID = "cn-shanghai";
    public static final String ENDPOINTNAME = "cn-shanghai";
    public static final String PRODUCT = "nls-filetrans";
    public static final String DOMAIN = "filetrans.cn-shanghai.aliyuncs.com";
    public static final String API_VERSION = "2018-08-17";
    public static final String POST_REQUEST_ACTION = "SubmitTask";
    public static final String GET_REQUEST_ACTION = "GetTaskResult";
    // 请求参数
    public static final String KEY_APP_KEY = "appkey";
    public static final String KEY_FILE_LINK = "file_link";
    public static final String KEY_VERSION = "version";
    public static final String AUTO_SPLIT = "auto_split";
    public static final String KEY_ENABLE_WORDS = "enable_words";
    public static final String ENABLE_DISFLUENCY = "enable_disfluency";
    public static final String FIRST_CHANNEL_ONLY = "first_channel_only";
    public static final String ENABLE_SAMPLE_RATE_ADAPTIVE = "enable_sample_rate_adaptive";
    public static final String ENABLE_SEMANTIC_SENTENCE_DETECTION = "enable_semantic_sentence_detection";
    // 响应参数
    public static final String KEY_TASK = "Task";
    public static final String KEY_TASK_ID = "TaskId";
    public static final String KEY_STATUS_TEXT = "StatusText";
    public static final String KEY_RESULT = "Result";
    // 状态值
    public static final String STATUS_SUCCESS = "SUCCESS";
    private static final String STATUS_RUNNING = "RUNNING";
    private static final String STATUS_QUEUEING = "QUEUEING";
    // 阿里云鉴权client
    IAcsClient client;
    public RecordingTranslationService(String accessKeyId, String accessKeySecret) {
        // 设置endpoint
        try {
            DefaultProfile.addEndpoint(ENDPOINTNAME, REGIONID, PRODUCT, DOMAIN);
        } catch (ClientException e) {
            e.printStackTrace();
        }
        // 创建DefaultAcsClient实例并初始化
        DefaultProfile profile = DefaultProfile.getProfile(REGIONID, accessKeyId, accessKeySecret);
        this.client = new DefaultAcsClient(profile);
    }

    /**
     * 提交录音文件
     * @param appKey
     * @param fileLink
     * @return
     */
    public String submitFileTransRequest(String appKey, String fileLink) {
        /**
         * 1. 创建CommonRequest,设置请求参数。
         */
        CommonRequest postRequest = new CommonRequest();
        // 设置域名
        postRequest.setDomain(DOMAIN);
        // 设置API的版本号,格式为YYYY-MM-DD。
        postRequest.setVersion(API_VERSION);
        // 设置action
        postRequest.setAction(POST_REQUEST_ACTION);
        // 设置产品名称
        postRequest.setProduct(PRODUCT);
        postRequest.setHttpContent(new byte[]{},"", FormatType.JSON);
        /**
         * 2. 设置录音文件识别请求参数,以JSON字符串的格式设置到请求Body中。
         */
        JSONObject taskObject = new JSONObject();
        // 设置appkey
        taskObject.put(KEY_APP_KEY, appKey);
        // 设置音频文件访问链接
        taskObject.put(KEY_FILE_LINK, fileLink);
        // 新接入请使用4.0版本,已接入(默认2.0)如需维持现状,请注释掉该参数设置。
        taskObject.put(KEY_VERSION, "4.0");
        // 设置是否输出词信息,默认为false,开启时需要设置version为4.0及以上。
        taskObject.put(KEY_ENABLE_WORDS, true);
        //开启智能分轨
        taskObject.put(AUTO_SPLIT, true);
        //是否只识别首个声道,搭配启智能分轨使用
        taskObject.put(FIRST_CHANNEL_ONLY, true);
        //是否启⽤语义断句
        taskObject.put(ENABLE_SAMPLE_RATE_ADAPTIVE, true);
        //语义断句
        taskObject.put(ENABLE_SEMANTIC_SENTENCE_DETECTION, false);
        //语气词优化
        taskObject.put(ENABLE_DISFLUENCY, false);
        String task = taskObject.toJSONString();
        System.out.println(task);
        // 设置以上JSON字符串为Body参数。
        postRequest.putBodyParameter(KEY_TASK, task);
        // 设置为POST方式的请求。
        postRequest.setMethod(MethodType.POST);
        /**
         * 3. 提交录音文件识别请求,获取录音文件识别请求任务的ID,以供识别结果查询使用。
         */
        String taskId = null;
        try {
            CommonResponse postResponse = client.getCommonResponse(postRequest);
            System.err.println("提交录音文件识别请求的响应:" + postResponse.getData());
            if (postResponse.getHttpStatus() == 200) {
                JSONObject result = JSONObject.parseObject(postResponse.getData());
                String statusText = result.getString(KEY_STATUS_TEXT);
                if (STATUS_SUCCESS.equals(statusText)) {
                    taskId = result.getString(KEY_TASK_ID);
                }
            }
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return taskId;
    }

    /**
     * 查询录音结果
     * @param taskId
     * @return
     */
    public String getFileTransResult(String taskId) {
        /**
         * 1. 创建CommonRequest,设置任务ID。
         */
        CommonRequest getRequest = new CommonRequest();
        // 设置域名
        getRequest.setDomain(DOMAIN);
        // 设置API版本
        getRequest.setVersion(API_VERSION);
        // 设置action
        getRequest.setAction(GET_REQUEST_ACTION);
        // 设置产品名称
        getRequest.setProduct(PRODUCT);
        // 设置任务ID为查询参数
        getRequest.putQueryParameter(KEY_TASK_ID, taskId);
        // 设置为GET方式的请求
        getRequest.setMethod(MethodType.GET);
        /**
         * 2. 提交录音文件识别结果查询请求
         * 以轮询的方式进行识别结果的查询,直到服务端返回的状态描述为“SUCCESS”或错误描述,则结束轮询。
         */
        String result = null;
        while (true) {
            try {
                CommonResponse getResponse = client.getCommonResponse(getRequest);
                //System.err.println("识别查询结果:" + getResponse.getData());
                if (getResponse.getHttpStatus() != 200) {
                    break;
                }
                JSONObject rootObj = JSONObject.parseObject(getResponse.getData());
                String statusText = rootObj.getString(KEY_STATUS_TEXT);
                if (STATUS_RUNNING.equals(statusText) || STATUS_QUEUEING.equals(statusText)) {
                    // 继续轮询,注意设置轮询时间间隔。
                    Thread.sleep(10000);
                }
                else {
                    // 状态信息为成功,返回识别结果;状态信息为异常,返回空。
                    if (STATUS_SUCCESS.equals(statusText)) {
                        result = rootObj.getString(KEY_RESULT);
                        // 状态信息为成功,但没有识别结果,则可能是由于文件里全是静音、噪音等导致识别为空。
                        if(result == null) {
                            result = "";
                        }
                    }
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}

录音上传

@RestController
@RequestMapping("/api/v1/oss")
@Slf4j
public class OssController {
    @Autowired
    OssConfig ossConfig;
    @Autowired
    NlpService nlpService;

    private static final Long KEEP_ALIVE_TIME = 60L;
    private static final int APS = Runtime.getRuntime().availableProcessors();
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            APS * 2,
            APS * 4,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(256),
            new ThreadFactoryBuilder().setNameFormat("OSS上传-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    @ApiOperation("阿里云oss上传")
    @PostMapping("upload")
    public R uploadFile(
            @ApiParam(value = "文件上传", required = true) @RequestPart("file") MultipartFile file
    ) {
        //原始文件名称  xxx.jpg
        String originalFileName = file.getOriginalFilename().trim();
        log.info("OSS文件上传,上传文件名称:【{}】,文件大小:【{}】", originalFileName, file.getSize());
        if (file.getSize() > 512 * 1024 * 1024) {
            return R.fail("不能大于512M  支持单轨和双轨的WAV、MP3、MP4、M4A、WMA、AAC、OGG、AMR、FLAC格式录音文件识别");
        }
        //获取相关配置
        String bucketName = ossConfig.getBucketName();
        String endpoint = ossConfig.getEndpoint();
        String accessKeyId = ossConfig.getAccessKeyId();
        String accessSecret = ossConfig.getAccessSecret();
        String region = ossConfig.getRegion();
        String roleArn = ossConfig.getRoleArn();
        //创建OSS对象
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessSecret);
        //JDK8 日期格式化
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
        //扩展名
        String extension = StringUtils.isNotBlank(originalFileName) ? originalFileName.substring(originalFileName.lastIndexOf(".")) : "jpg";
        //OSS上的文件名
        String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
        String fileName = yyyyMMdd.format(ldt) + "_" + uuid.substring(0, 10) + extension;
        //拼装路径,oss上存储的路径 2021/05/16/xxx.jpg
        String newFileName = "xxx/" + dtf.format(ldt) + "/" + fileName;
        //推送到oss
        try {
            AtomicBoolean successAtomic = new AtomicBoolean(false);
            CountDownLatch latch = new CountDownLatch(1);
            THREAD_POOL_EXECUTOR.execute(() -> {
                try {
                    ObjectMetadata objectMetadata = new ObjectMetadata();
                    objectMetadata.setContentType("audio/mpeg"); // 设置录音文件的内容类型
                    objectMetadata.setContentLength(file.getSize()); // 设置文件长度
                    ossClient.putObject(bucketName, newFileName, file.getInputStream(), objectMetadata);
                } catch (IOException e) {
                    log.error("oss文件上传失败:{}", e);
                    successAtomic.set(true);
                } finally {
                    latch.countDown();
                }
            });
            try {
                latch.await();
            } catch (InterruptedException e) {
            }
            if (successAtomic.get()) {
                return R.fail("上传文件失败,请稍后重试");
            }
            //拼装返回url
            String fileUrl = "https://" + bucketName + "." + endpoint + "/" + newFileName;
            log.info("OSS文件上传成功:{}", fileUrl);

            URL url = null;
            try {
                STSAssumeRoleSessionCredentialsProvider credentialsProvider = CredentialsProviderFactory
                        .newSTSAssumeRoleSessionCredentialsProvider(
                                region,
                                accessKeyId,
                                accessSecret,
                                roleArn
                        );
                OSS client = new OSSClientBuilder().build(endpoint, credentialsProvider);
                // 设置签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
                Date expiration = DateUtil.plusDays(new Date(), 7);
                // 生成以GET方法访问的签名URL,访客可以直接通过浏览器访问相关内容。
                url = client.generatePresignedUrl(bucketName, newFileName, expiration);
            } catch (ClientException e) {
                log.error("请求oss临时url异常:{}", e.getMessage());
            }

            return nlpService.recordingTranslation(url.toString());
        } finally {
            //oss关闭
            ossClient.shutdown();
        }
    }
}

阿里云oss配置文件

/**
 * 阿里云oss配置文件
 */
@Data
@Configuration
public class OssConfig {
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.oss.accessSecret}")
    private String accessSecret;

    @Value("${aliyun.oss.bucketName}")
    private String bucketName;

    @Value("${aliyun.oss.region}")
    private String region;

    @Value("${aliyun.oss.roleArn}")
    private String roleArn;

}

配置文件

spring.servlet.multipart.max-file-size=512MB
spring.servlet.multipart.max-request-size=512MB

# oss
aliyun.oss.endpoint=oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=xxx
aliyun.oss.accessSecret=xxx
aliyun.oss.bucketName=xxx
aliyun.oss.region=cn-hangzhou
aliyun.oss.roleArn=xxx

# redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
spring.redis.database=12
spring.redis.ssl= false

spring.redis.lettuce.pool.max-active = 200
spring.redis.lettuce.pool.max-idle = 100
spring.redis.lettuce.pool.min-idle = 10
spring.redis.lettuce.pool.max-wait= -1ms
spring.redis.client-type = lettuce

#nls
aliyun.nls.appKey=xxx
aliyun.nls.accessKeyId=xxx
aliyun.nls.accessKeySecret=xxx
aliyun.nls.url=wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1
Netty是一个高性能的网络通信框架,可以用于将Modbus协议与网络进行对接。Modbus是一种通信协议,常用于工业领域的设备间通信。下面是一个简单的示例,展示如何使用Netty对接Modbus协议。 首先,你需要引入Netty和Modbus相关的依赖。可以在项目的构建文件(例如pom.xml)中添加以下依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.63.Final</version> </dependency> <dependency> <groupId>de.gandev</groupId> <artifactId>modbus-client</artifactId> <version>2.1.0</version> </dependency> ``` 接下来,你可以创建一个Netty的客户端来连接Modbus设备。以下是一个简单的示例代码: ```java import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import de.gandev.modjdatabus.ModbusTcpClient; import de.gandev.modjdatabus.ModbusTcpClientHandler; public class ModbusClient { private final String host; private final int port; public ModbusClient(String host, int port) { this.host = host; this.port = port; } public void run() throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加Modbus协议处理器 pipeline.addLast(new ModbusTcpClientHandler()); } }); // 连接Modbus设备 ChannelFuture future = bootstrap.connect(host, port).sync(); Channel channel = future.channel(); // 发送Modbus命令 byte[] command = new byte[]{0x01, 0x03, 0x00, 0x00, 0x00, 0x01, (byte) 0x84, (byte) 0x0A}; channel.writeAndFlush(command); // 等待连接关闭 future.channel().closeFuture().sync(); } finally { // 关闭连接 group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { String host = "192.168.0.1"; int port = 502; ModbusClient client = new ModbusClient(host, port); client.run(); } } ``` 在上述示例中,我们首先创建了一个`ModbusTcpClientHandler`,它是一个Netty的ChannelHandler,用于处理Modbus协议的请求和响应。然后,我们使用Netty的`Bootstrap`来设置客户端的配置和处理器。最后,我们连接Modbus设备并发送Modbus命令。 请注意,以上只是一个简单的示例,实际的Modbus通信可能涉及更多的细节和配置。你可以根据具体的需求进行修改和扩展。希望对你有所帮助!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值