PaddleSpeech 是基于飞桨 PaddlePaddle 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型,一些典型的应用示例如下:语音识别、语音翻译 (英译中)、语音合成、标点恢复等。

我只用到了语音识别(语音转文字)、语音合成(文字转语音)。

安装

我只在CentOS上用了(虚拟机CentOS Linux release 7.9.2009和云服务器CentOS Linux release 8.5.2111),因截止到写这篇文章(2022年11月18日),官方README中说

我们强烈建议用户在 Linux 环境下,3.7 以上版本的 python 上安装 PaddleSpeech。

linux

指定源是因为安装文档中建议的

  • 提示: 我们建议在安装 paddlepaddle 的时候使用百度源 https://mirror.baidu.com/pypi/simple ,而在安装 paddlespeech 的时候使用清华源 https://pypi.tuna.tsinghua.edu.cn/simple 。

不过README中没说要指定源。 

百度飞桨PaddleSpeech的简单使用
GPU版,CUDA版本取决于你的显卡型号

显卡驱动的安装可以看我另一篇文章。

使用

如果你的机器CPU或内存不够,可能运行不起来代码,终端中能看到进程会被自动结束掉。

测试语音转文字时,我用手机的录音机录了wav音频,用PaddleSpeech转文字时提示

The sample rate of the input file is not 16000.The program will resample the wav file to 16000.If the result does not meet your expectations,Please input the 16k 16 bit 1 channel wav file. 

它要求音频文件的采样率是16000Hz,如果输入的文件不符合要求,根据提示按y后,程序会自动将音频文件调整成它能识别的样子,然后给出识别结果。此时我用的官方的示例代码,只不过音频文件是我自己录的。

from paddlespeech.cli.asr.infer import ASRExecutor
asr = ASRExecutor()
result = asr(audio_file="luyin.wav")
print(result)

我需要把这个功能写成接口,接口中程序运行时,若输入的音频文件不符合要求,用户是无法用键盘进行交互的,导致输入的音频无法被转成文字。这就需要提前将音频文件转成16k 16 bit 1 channel wav,然后将转换后的音频文件传给PaddleSpeech。我不知道源码中有没有提供可供调用的转换函数(因为服务器上只有vim,找代码看代码不方便),我直接用ffmpeg转换了(python执行shell命令),ffmpeg的安装可以参考这两个链接:CentOS安装使用ffmpeg - 开普勒醒醒吧 - 博客园 (cnblogs.com)、centos 安装ffmpeg_qq_duhai的博客-CSDN博客。

也可以直接在这里下载静态编译好的,不用自己解决依赖问题。

ffmpeg -y -i input.wav  -ac 1 -ar 16000  -b:a 16k  output.wav # from https://blog.csdn.net/Ezerbel/article/details/124393431

这个命令输出的文件的格式,和PaddleSpeech给的示例zh.wav的格式一样,可以用PotPlayer查看。

接口形式的语音转文字、文字转语音的完整代码

import os
import random
import time
import json
import base64
import shutil
from paddlespeech.cli.asr.infer import ASRExecutor
from paddlespeech.cli.tts.infer import TTSExecutor
from flask import Flask, request
app=Flask(__name__)
asr = ASRExecutor()  # 初始化成全局变量,防止多次初始化导致显存不够 from https://github.com/PaddlePaddle/PaddleSpeech/issues/2881和https://github.com/PaddlePaddle/PaddleSpeech/issues/2908
tts = TTSExecutor()
# 公共函数,所有接口都能用
def random_string(length=32): # 生成32位随机字符串,为了生成随机文件名    
    string='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    return ''.join(random.choice(string) for i in range(length))
# 公共函数,所有接口都能用
def base64_to_audio(audio_base64, folder_name=None):  # 服务器上用folder_name参数,用于在audio_file_path中拼接路径,如f'/home/www/card/{folder_name}/',不同的folder_name对应不同的识别任务(如身份证识别、营业执照识别),本地测试不用
    audio_base64 = audio_base64.split(',')[-1]
    audio = base64.b64decode(audio_base64)
    audio_file_name = random_string() + '_' + (str(time.time()).split('.')[0])  # 不带扩展名,因为不知道收到的音频文件的原始扩展名,手机录的不一定是什么格式
    audio_file_path = f'/home/python/speech/{folder_name}/' + audio_file_name
    with open(audio_file_path, 'wb') as f:
        f.write(audio)
    return audio_file_path
# 将收到的音频文件转为16k 16 bit 1 channel的wav文件,16k表示16000Hz的采样率,16bit不知道是什么
# 若给paddlespeech传的文件不对,会提示The sample rate of the input file is not 16000.The program will resample the wav file to 16000.If the result does not meet your expectations,Please input the 16k 16 bit 1 channel wav file.所以要提前转换。
def resample_rate(audio_path_input):
    audio_path_output = audio_path_input + '_output' + '.wav'  # 传入的audio_path_input不带扩展名,所以后面直接拼接字符串
    command = f'ffmpeg -y -i {audio_path_input}  -ac 1 -ar 16000  -b:a 16k  {audio_path_output}'  # 这个命令输出的wav文件,格式上和PaddleSpeech在README中给的示例zh.wav(https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav,内容是'我认为跑步最重要的就是给我带来了身体健康')一样。from https://blog.csdn.net/Ezerbel/article/details/124393431
    command_result = os.system(command)  # from https://blog.csdn.net/liulanba/article/details/115466783
    assert command_result == 0
    if os.path.exists(audio_path_output):
        return audio_path_output
    elif not os.path.exists(audio_path_output):  # ffmpeg输出的文件不存在,可能是ffmpeg命令没执行完,等1秒(因在虚拟机测试转一个8.46M的MP3需0.48秒),1秒后若还没有输出文件,说明报错了
        time.sleep(1)
        if os.path.exists(audio_path_output):
            return audio_path_output
        else:
            return None
# 语音转文字
# 只接受POST方法访问
@app.route("/speechtotext",methods=["POST"])
def speech_to_text():
    audio_file_base64 = request.get_json().get('audio_file_base64')  # 要转为文字的语音文件的base64编码,开头含不含'data:audio/wav;base64,'都行
    audio_file_path = base64_to_audio(audio_file_base64, folder_name='speech_to_text/audio_file')  # 存放收到的原始音频文件
    audio_path_output = resample_rate(audio_path_input=audio_file_path)
    if audio_path_output:
        # asr = ASRExecutor()
        result = asr(audio_file=audio_path_output)  # 会在当前代码所在文件夹中产生exp/log文件夹,里面是paddlespeech的日志文件,每一次调用都会生成一个日志文件。记录这点时的版本号是paddlepaddle==2.3.2,paddlespeech==1.2.0。 from https://github.com/PaddlePaddle/PaddleSpeech/issues/1211
        os.remove(audio_file_path)  # 识别成功时删除收到的原始音频文件和转换后的音频文件
        os.remove(audio_path_output)
        # try:
        #     shutil.rmtree('')  # 删除文件夹,若文件夹不存在会报错。若需删除日志文件夹,用这个。from https://blog.csdn.net/a1579990149wqh/article/details/124953746
        # except Exception as e:
        #     pass
        return json.dumps({'code':200, 'msg':'识别成功', 'data':result}, ensure_ascii=False)
    else:
        return json.dumps({'code':400, 'msg':'识别失败'}, ensure_ascii=False)
# 文字转语音
# 只接受POST方法访问
@app.route("/texttospeech",methods=["POST"])
def text_to_speech():
    text_str = request.get_json().get('text')  # 要转为语音的文字
    # tts = TTSExecutor()
    audio_file_name = random_string() + '_' + (str(time.time()).split('.')[0]) + '.wav'
    audio_file_path = '/home/python/speech/text_to_speech/audio_file' + audio_file_name
    tts(text=text_str, output=audio_file_path)  # 输出24k采样率wav格式音频。同speech_to_text()中一样,会在当前代码所在文件夹中产生exp/log文件夹,里面是paddlespeech的日志文件,每一次调用都会生成一个日志文件。
    if os.path.exists(audio_file_path):
        with open(audio_file_path, 'rb') as f:
            base64_str = base64.b64encode(f.read()).decode('utf-8')  # 开头不含'data:audio/wav;base64,'
        os.remove(audio_file_path)  # 识别成功时删除转换后的音频文件
        # try:
        #     shutil.rmtree('')  # 删除文件夹,若文件夹不存在会报错。若需删除日志文件夹,用这个。from https://blog.csdn.net/a1579990149wqh/article/details/124953746
        # except Exception as e:
        #     pass
        return json.dumps({'code':200, 'msg':'识别成功', 'data':base64_str}, ensure_ascii=False)
    elif not os.path.exists(audio_file_path):
        return json.dumps({'code':400, 'msg':'识别失败'}, ensure_ascii=False)
if __name__=='__main__':
    app.run(host='127.0.0.1', port=9723)

最后

如果你想调整语速,可以看请问自己 finetune 的 tts 模型能够改变语速吗? · Issue #2383 · PaddlePaddle/PaddleSpeech · GitHub

如果你用的是GPU版,查看是否调用了GPU,请问语音合成可以使用GPU进行推理吗,如果可以应该怎么操作呢? · Issue #2467 · PaddlePaddle/PaddleSpeech · GitHub,也可以用nvidia-smi命令查看GPU占用情况

如果在使用过程中遇到显存未释放,导致显存不够,可以看音频转文字过程中显存不断增加,最终 out of memory · Issue #2881 · PaddlePaddle/PaddleSpeech · GitHub

 [TTS]使用gpu合成后显存未释放 · Issue #2908 · PaddlePaddle/PaddleSpeech · GitHub

发表回复