语音聊天实现

前言

这几天看了点PyQt相关的知识,感觉可以结合之前得一些内容做出点什么好玩的东西。

之前做过文本转语音的聊天机器人,昨天又恰好做了关于音频处理的。借此机会,整合一下,来做个有界面的语音文本聊天机器人好了。

先来看看最终的效果图。


文本语音聊天机器人效果图

对这些基础内容不是很了解的可以参考我之前的文章。

环境

环境搭建是个坑,之前一直在用的pyttsx语音引擎竟然不支持Python36,只能在Python27版本使用。所以无奈只能选用微软的提供的win32com.client了。

本机环境

本机环境如下

  • Windows10 64位
  • Python36
  • PyCharm pro

所需包

所需包一开始我是手动统计的,但是后来觉得版本这块最好还是精确一下,于是使用了pip的一个freeze命令。

pip freeze > requirements.txt

得到了下面的这些所需的库文件(我删除了一些没用到的)。

PyAudio==0.2.11
PyQt5==5.8.2
pyttsx==1.1
pywin32==221
requests==2.13.0
sip==4.19.2

各大模块

下面开始针对各大模块简要的介绍一下。

百度语音接口

百度语音接口是用来处理本地音频到文本内容转换而使用的。需要用到标准库中的wave库,来处理.wav音频文件。

# coding: utf8

# @Author: 郭 璞
# @File: baiduyuyin.py                                                                 
# @Time: 2017/5/11                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 借助百度语音识别接口实现本地语音解析

import pyaudio
import wave
import requests
import json

class BaiDuYuYin(object):

    def __init__(self):
        # get the token
        self.token = self.gettoken()

    def gettoken(self):
        try:
            apiKey = "Ll0c嘿嘿2ZSGAU"
            secretKey = "44c8a这个不能说34936227d4a19dc2"

            auth_url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + apiKey + "&client_secret=" + secretKey
            response = requests.get(url=auth_url)
            jsondata = response.text
            return json.loads(jsondata)['access_token']
        except Exception as e:
            raise Exception("Cannot get the token, the reason is {}".format(e))

    def parse(self, wavefile='local.wav'):
        """
        返回音频文件对应的文本内容。
        注意返回的是列表类型的数据,待会处理的时候要格外的小心。
        :param wavefile:
        :return:
        """
        try:
            fp = wave.open(wavefile, 'rb')
            # 已经录好音的音频片段内容
            nframes = fp.getnframes()
            filelength = nframes * 2
            audiodata = fp.readframes(nframes)

            # 百度语音接口的产品ID
            cuid = '7519663'
            server_url = 'http://vop.baidu.com/server_api' + '?cuid={}&token={}'.format(cuid, self.token)
            headers = {
                'Content-Type': 'audio/pcm; rete=8000',
                'Content-Length': '{}'.format(filelength),
            }

            response = requests.post(url=server_url, headers=headers, data=audiodata)
            print(response.text)
            data = json.loads(response.text)
            if data['err_msg'] == 'success.':
                return data['result']
            else:
                return '你说的啥啊,听不清听不清!'
        except Exception as e:
            raise Exception("Parsing wave file failed. The reason is {}".format(e))

if __name__ == '__main__':
    yuyinclient = BaiDuYuYin()
    result = yuyinclient.parse(wavefile='local.wav')
    print(result)

图灵机器人接口

然后是图灵机器人接口,这个用于处理文本对话。免费版其实已经够用了。有需要的自己去申请吧。

# coding: utf8

# @Author: 郭 璞
# @File: turing.py
# @Time: 2017/5/11                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 文字对话接口实现
import requests
import json

class TuringRobot(object):

    def __init__(self):
        self.apikey = '2a220b3哟哟哟b74c54'
        self.userid = '产品ID不能说'
        self.url = 'http://www.tuling123.com/openapi/api'

    def talk(self, text):
        payload = {
            'key': self.apikey,
            'userid': self.userid,
            'info': text
        }

        response = requests.post(url=self.url, data=payload)
        return json.loads(response.text)['text']


if __name__ == '__main__':
    turing = TuringRobot()
    answer = turing.talk('你好吗,我是小黄鸡!')
    print(answer)

音频处理

昨天对于音频处理这块做了一点点的研究,今天还是那个套路。默认录音五秒,保存为同一级目录下的local.wav文件。

# coding: utf8

# @Author: 郭 璞
# @File: recorder.py                                                                 
# @Time: 2017/5/11                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 记录本地录音,默认保存为local.wav, 留作解析引擎备用。
import pyaudio
import wave

class Recorder(object):

    def __init__(self):
        self.CHUNK = 1024
        self.FORMAT = pyaudio.paInt16
        self.CHANNELS = 2
        self.RATE = 44100
        self.RECORD_SECONDS = 5
        self.WAVE_OUTPUT_FILENAME = 'local.wav'

        self.engine = pyaudio.PyAudio()

    def record(self):
        try:
            # 提示语句可以使用一下语音方式,这里先打印算了。
            print("Begin Recoding ...")

            stream = self.engine.open(format=self.FORMAT,
                                      channels=self.CHANNELS,
                                      rate=self.RATE,
                                      input=True,
                                      frames_per_buffer=self.CHUNK)
            # 记录到的音频总数据帧
            frames = []
            for i in range(0, int(self.RATE / self.CHUNK * self.RECORD_SECONDS)):
                data = stream.read(self.CHUNK)
                frames.append(data)

            # 音频记录完毕
            print('Recording Over!')
            # 释放资源,接触阻塞监听。
            stream.stop_stream()
            stream.close()
            self.engine.terminate()

            # 并将音频数据保存到本地音频文件中
            wf = wave.open(self.WAVE_OUTPUT_FILENAME, 'wb')
            wf.setnchannels(self.CHANNELS)
            wf.setsampwidth(self.engine.get_sample_size(self.FORMAT))
            wf.setframerate(self.RATE)
            wf.writeframes(b''.join(frames))
            wf.close()
        except Exception as e:
            raise Exception("Recording failed. The reason is {}".format(e))


if __name__ == '__main__':
    recorder = Recorder()
    recorder.record()

本地朗读模块

本地语音朗读相当于是一个加分项,之前一直在用的pyttsx这下尴尬了,无奈只能试用第二个方式,不过使用pyttsx的代码我还是留出来吧。万一哪天它支持了Python36,就有更多可选项的丰富功能了。

# coding: utf8

# @Author: 郭 璞
# @File: localvoicer.py                                                                 
# @Time: 2017/5/11                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 本地语音朗读实现。
import win32com.client

class Reader(object):
    """
    尴尬的是pyttsx不支持Python36,要不然还可以有更多可选项。
    """

    def __init__(self):
        import pyttsx
        self.engine = pyttsx.init()
        # optional property
        self.rate = self.engine.getProperty('rate')
        self.voices = self.engine.getProperty('voices')
        self.volume = self.engine.getProperty('volume')

    def read(self, text="", rate=200, voices="", volume=""):
        self.engine.say(text)
        self.engine.runAndWait()


class Speaker(object):
    def __init__(self):
        self.engine = win32com.client.Dispatch("SAPI.SpVoice")

    def speak(self, text):
        self.engine.Speak(text)



if __name__ == '__main__':
    # reader = Reader()
    # reader.read(text='Hello World!')
    speaker = Speaker()
    speaker.speak("hello world! 你好世界")


GUI 模块

做完了前面的部分,就差界面了。测试完毕之后发现,各大模块均能正常工作,虽然音频解析那块特别地依赖于网速,校园网这网速我也是醉了。

下面简单的写个界面来“打包美化”一下吧。

# coding: utf8

# @Author: 郭 璞
# @File: audioui.py                                                                 
# @Time: 2017/5/11                                   
# @Contact: 1064319632@qq.com
# @blog: http://blog.csdn.net/marksinoberg
# @Description: 外部界面
from PyQt5 import QtCore, QtGui, QtWidgets
from audiorobot.dispatcher import Dispatcher
from audiorobot.baiduyuyin import BaiDuYuYin
from audiorobot.turing import TuringRobot
from audiorobot.localvoicer import Speaker

class ClientUI(QtWidgets.QWidget):

    def __init__(self):
        super(ClientUI, self).__init__()
        self.dispatcher = Dispatcher()
        self.baiduyuyin = BaiDuYuYin()
        self.turingrobot = TuringRobot()
        self.speaker = Speaker()
        self.initui()


    def initui(self):
        self.setWindowTitle("图灵·聊天室")
        self.setGeometry(20, 20, 400, 500)

        # 顶部布局
        toplayout = QtWidgets.QHBoxLayout()
        self.textarea = QtWidgets.QTextBrowser()
        toplayout.addWidget(self.textarea)
        # 中间布局
        centerlayut = QtWidgets.QHBoxLayout()
        self.editline = QtWidgets.QLineEdit()
        self.voicebutton = QtWidgets.QPushButton("发语音")
        self.textbutton = QtWidgets.QPushButton("发文字")
        centerlayut.addWidget(self.editline)
        centerlayut.addWidget(self.voicebutton)
        centerlayut.addWidget(self.textbutton)

        mainlayout = QtWidgets.QVBoxLayout()
        mainlayout.addLayout(toplayout)
        mainlayout.addLayout(centerlayut)

        self.setLayout(mainlayout)
        # 关于事件处理,交给handler来处理即可
        self.eventhandler()

    def eventhandler(self):
        self.voicebutton.clicked.connect(self.pushvoice)
        self.textbutton.clicked.connect(self.pushtext)

    def pushvoice(self):
        print('voice')
        # 先保存到本地,再调用语音接口上传
        self.dispatcher.record()
        response = self.baiduyuyin.parse()
        print('百度语音接口解析到的数据为:{}'.format(response))
        self.speaker.speak(text=response)
        # 更新一下窗体文本域的内容
        text = self.textarea.toPlainText()+"\n"+"<<< "+"上传音频中..."
        self.textarea.setText(text)
        text =  text +"\n>>> " +response
        self.textarea.setText(text)


    def pushtext(self):
        inputtext = self.editline.text()
        print(inputtext)
        trans = self.turingrobot.talk(text=inputtext)
        self.speaker.speak(text=trans)
        # 更新文本域内容
        text = self.textarea.toPlainText() + "\n<<<"+inputtext
        self.textarea.setText(text)
        text = self.textarea.toPlainText() + "\n>>> " + trans
        self.textarea.setText(text)
        self.editline.clear()


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ui = ClientUI()
    ui.show()
    sys.exit(app.exec_())

演示

好了,大功告成。运行代码的时候仅仅需要下面的这个命令就可以了。

Python audioui.py

文本

先来看看对于文本的处理,这其实跟之前的聊天机器人没啥区别。仅仅是带了语音朗读罢了。


文本测试

语音

然后是语音测试,我本人在图书馆。所以为了不打扰别人,录音的时候只能假装咳嗽来掩盖测试录音的事实,所以效果不是很好。但是如果是标准的普通话,测试的结果还是差强人意的。

音频处理


总结

最后还是来总结一下。

本次也算是整合的比较多的内容了。模块内测试都是用的

if __name__ == "__main__":
    # testing code
    pass

看起来还算不错,单元测试倒是没什么必要,毕竟代码量还很少。集成测试也算是马马虎虎,功能这块倒是还能满足需求,但是以后如果代码量大了的话,还是要好好测测的,以防万一。

已知的缺点就是界面这块。录音的时候要是能加一个statusBar实时提醒录音进度就好了,而且录音的时候是阻塞线程的,不是很优雅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 时间过得很快,我的第一份iOS工作做的就是IM应用(选用的是XMPP),如今也忘得差不多了.利用空闲时间来重写一遍...
    飘金阅读 2,953评论 2 2
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • CSS3的基本特性 一切的基础:选择器 基本选择器:div (选中所有div标签).test (class="te...
    cenkai88阅读 355评论 0 0
  • 虽然现在我们做开发走到使用ARC 自动引用计数,但是更好地去了解内存管理还是非常有必要的。  手机的内存是有限的,...
    HQ今日磨墨阅读 359评论 0 1