Django整合edge_tts实现文本转语音

  最近一直在研究TTS文本转语音的实现,虽然当下的这个技术已经很成熟了,但尝试遍了各大语音平台,基本都是要收费。也参考过很多帖子,其中提到的免费也只是临时的试用一段时间,并不能完全免费的使用。

  这里通过微软提供的edge_tts来自由的实现想要的tts效果。

  由于我是用在Android客户端中进行语音播放,Android中虽然自带了TTS的相关包TextToSpeech,但是音色选择实在是一言难尽,网上能搜索到的相关内容都是围绕setPitch这个函数来达到改变音色的目的。根据实际测试后,发现这只是改变了声音的尖锐程度,并不算完全的改变音色。

  微软提供了一个edge_tts的工具可以直接在python中实现文本至语音的转换,但刚开始使用发现它还是有很大的限制的。这个工具可以在命令行中使用,也可以在python中调用。

  相关包:

edge-tts==6.1.10

  接下来看它常规的调用方式:

async def my_function():
    tts = edge_tts.Communicate(text, voice)
    await tts.save(output)
    
if __name__ == '__main__':
    asyncio.run(my_function())

  通过上面的代码片段可以看出,tts转换成语音后,获取结果数据相关的函数只有save这一个,必须写入文件。我也是卡在这一步琢磨了好久,最后终于发现,它提供的stream函数所返回的音频流,我们可以在python中定义一个“虚拟文件”以避免实际的磁盘读写。相当于是“骗过了”微软edge_tts库的API。最终实现的代码如下:

async def tts_task(text, voice="zh-CN-YunxiNeural"):

    base64_data = ""
    memory_file = io.BytesIO()
    try:
        communicate = edge_tts.Communicate(text, voice)
        async for chunk in communicate.stream():
            if chunk["type"] == "audio":
                memory_file.write(chunk["data"])
        memory_file.seek(0)
        read_data = memory_file.read()
        base64_data = base64.b64encode(read_data).decode("utf-8")
    finally:
        memory_file.close()
    return base64_data

  在上面的代码块中,将最后的音频流结果转换成了base64数据。

  在Django WebSocket调用效果如下:

    @database_sync_to_async
    def chat_flow(self, json_data):
        print("接收数据:", json.dumps(json_data, ensure_ascii=False))
        user_msg = json_data["content"]
        audio_data = ""
        content = ""
        
        try:
            # 此处省略对content进行处理,生成实际要转成语音的文本内容
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            audio_data = loop.run_until_complete(pyTTSUtils.tts_task(content))
            loop.close()

        except requests.exceptions.RequestException as e:
            logger.debug(e)
        except openai.APIConnectionError as e:
            logger.debug(e)

        return audio_data, content

 async def websocket_receive(self, message):
     text = message["text"]
     json_data = json.loads(text)
     if self.assistant is None:
         audio_data, content = await self.chat_flow(json_data)
         await self.send(json.dumps({"audio_data": audio_data, "content": content}))
     

  上面的代码块中我是在AsyncWebsocketConsumer中实现,通过websocket的方式将最终通过文本生成的语音数据转成base64数据返回给了客户端。

下面附带上Android客户端接收数据的处理:

okHttpClient.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                Log.d(TAG, "onOpen: websocket已连接");
                empWebSocket = webSocket;
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                Log.d(TAG, "onTextMessage: ");
                Log.d(TAG, text);
                String audioData = parseJson(text, "audio_data");
                String content = parseJson(text, "content");
                if (!"".equals(audioData)) {
                    try {
                        mediaPlayer = new MediaPlayer();
                        mediaPlayer.setDataSource("data:audio/mpeg;base64," + audioData);
                        mediaPlayer.prepareAsync();
                        mediaPlayer.setVolume(100f, 100f);
                        mediaPlayer.setLooping(false);
                        mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                            @Override
                            public void onPrepared(MediaPlayer mp) {
                                sendActivityMessage("message",content);

                                visualizer = new Visualizer(mediaPlayer.getAudioSessionId());
                                visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
                                visualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
                                    @Override
                                    public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
                                        int numBars = 4;
                                        int barWidth = 32;
                                        int[] amplitudes = new int[numBars];
                                        for (int i = 0; i < numBars; i++){
                                            int sum = 0;
                                            int startIndex = i * barWidth;
                                            int endIndex = (i + 1) * barWidth;
                                            for (int j = startIndex; j < endIndex; j++) {
                                                sum += Math.abs(waveform[j]);
                                            }
                                            amplitudes[i] = sum / barWidth;
                                        }
//                                        Log.d(TAG, "播放音频波形: " + Arrays.toString(amplitudes));
                                        sendActivityMessage("wave",Arrays.toString(amplitudes));
                                    }

                                    @Override
                                    public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {

                                    }
                                },Visualizer.getMaxCaptureRate() / 5,true,false);
            
                                visualizer.setEnabled(true);

                                mediaPlayer.start();
                            }
                        });

                        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                            @Override
                            public void onCompletion(MediaPlayer mp) {
                                visualizer.setEnabled(false);
                                mediaPlayer.stop();
                                mediaPlayer.release();
                            }
                        });
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
                Log.d(TAG, "onBytesMessage: ");
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                Log.d(TAG, "onClosing: 正在关闭连接...");
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                Log.d(TAG, "onClosed: websocket连接已关闭");
                sendActivityMessage("status","close");
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                Log.d(TAG, "onFailure: " + t.getMessage());
                sendActivityMessage("status","close");
            }
        });

  这里Android客户端接收到base64音频数据后,使用MediaPlayer进行播放,并且还做了音频波形的转换处理,用于在界面上显示,这里也顺带记录下。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jaris_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值