• 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>
        
        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.76.Finalversion>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.8.18version>
        dependency>
    
    
    
    
    
    
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.72version>
        dependency>
    
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
    
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>com.alibaba.nlsgroupId>
            <artifactId>nls-sdk-transcriberartifactId>
            <version>2.2.1version>
        dependency>
    
        
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-boot-starterartifactId>
            <version>3.0.0version>
        dependency>
    
        
        <dependency>
            <groupId>com.aliyun.ossgroupId>
            <artifactId>aliyun-sdk-ossartifactId>
            <version>3.15.1version>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.12.0version>
        dependency>
    
        
        <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>31.0.1-jreversion>
        dependency>
    
    
    
    
    
    
    
    
    
    
    
    
    
    
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettucegroupId>
                    <artifactId>lettuce-coreartifactId>
                exclusion>
            exclusions>
        dependency>
        
        <dependency>
            <groupId>io.lettucegroupId>
            <artifactId>lettuce-coreartifactId>
            <version>6.1.6.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-pool2artifactId>
        dependency>
    
    dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    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);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    构建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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    语音识别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);
                byteBuf.release();
    
                //调用阿里云语音翻译
                nlpService.speechTranslation(ctx.channel(), byteArray);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    音频流格式,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    
    
    • 1

    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 );
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    语音识别

    /**
     * 实时语音识别
     */
    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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144

    录音识别

    /**
     * 录音文件识别
     */
    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171

    录音上传

    @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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105

    阿里云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;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    配置文件

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
  • 相关阅读:
    股票价格预测
    Hqst百兆网络变压器H81601S高频参数 IR插入损耗 RL回波损耗 CTK串扰 CMRR共模抑制 实测值
    第50节:cesium 绘制指定类型区域(含源码+视频)
    基于Java的设计模式-观察者模式
    《大模型时代-ChatGPT开启通用人工智能浪潮》精华摘抄
    模型相关术语:Model vs DTO vs Entity vs Value Object vs Pojo vs Bean
    用正则表达式简单解析JSON字符串
    语法基础(判断语句)
    REACH认证办理流程,与高度关注物质SVHC认证的关系
    【ESP32 + Edge Impulse平台】模拟多传感器数据融合实验测试
  • 原文地址:https://blog.csdn.net/qq_42665745/article/details/134076575