利用免费的百度语音及合成服务,图灵机器人聊天服务,在加上一点简单的硬件模块,我们就可以用树莓派搭建自己的语音机器人
树莓派3B带有3.5MM音频输出口,所以可以直接接到自己的音箱上,但是没有音频输入接口,所以所以可以买一个可同时支持输入和输出的USB声卡,把麦克风和音箱接上。
3.5MM输出功能可能需要进入配置页面打开,执行sudo raspi-config
命令进入系统配置,在Advanced Options -> Audio中打开3.5MM。
怎么用python程序控制音频输入和输出?可以直接利用PyAudio这个库,教程地址:http://people.csail.mit.edu/hubert/pyaudio/#examples ,教程中给出了很多example,可以看到使用起来是很简单的,它使用流对象来处理输入输出数据,并且支持非阻塞调用
可以用下面的代码来测试麦克风和音箱工作是否正常,这段程序就是接收麦克风的信号同时输出到音箱,如果工作正常就是一个话筒的效果
import pyaudio
import os
import sys
import time
#os.close(sys.stderr.fileno())
WIDTH=2
CHANNELS=2
RATE=44100
p=pyaudio.PyAudio()
def callback(in_data,frame_count,time_info,status):
return (in_data,pyaudio.paContinue)
stream=p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
stream_callback=callback)
stream.start_stream()
while stream.is_active():
time.sleep(0.1)
stream.stop_stream()
stream.close()
p.terminate()
这段程序使用非阻塞方法,指定回调函数为callback,我们可以从输入参数in_data中获取从麦克风输入的数据,在return的时候传入的数据则会输出到音箱。
百度提供了语音识别和语音合成的API接口,我们只要注册并创建应用就可以调用了。
在官网上创建一个应用,记住分配的AppID, API Key和Secret Key,调用API的时候需要用这些信息获得鉴权。
因为是REST API接口,只要可以发起Http请求就可以调用服务,并且官方提供了封装好的类库(执行pip3 install baidu-aip
下载库),使用起来更加简单。
首先是百度语音的示例程序,上传一段音频录音文件,成功的话返回识别的文字:
from aip import AipSpeech
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
def get_file_content(filePath):
with open(filePath, 'rb') as fp:
return fp.read()
response=client.asr(get_file_content('D:\\MyBlogs\\VoiceRobot\\VoiceRobot\\recordVoice2_pcm.pcm'), 'pcm', 16000, {
'dev_pid': 1536,
})
print(response)
print(response['result'])
语音识别服务支持wav和pcm格式的音频文件,但是最好直接上传pcm,因为它的服务最终都是转换成pcm文件来处理的,如何将wav文件转换成pcm格式,可以参考官方文档中“语音识别工具”中的内容利用ffmpeg来转换;采样率参数需要固定为16000;音频文件声道为单声道
然后是语音合成的示例,上传一段文字,返回音频文件:
from aip import AipSpeech
import wave
import os
import numpy as np
APP_ID = ''
API_KEY = ''
SECRET_KEY = ''
client = AipSpeech(APP_ID, API_KEY, SECRET_KEY)
result = client.synthesis('你好呀', 'zh', 1, {
'vol': 5,'per': 4, 'aue':6
})
# 识别正确返回语音二进制 错误则返回dict
if not isinstance(result, dict):
with open('auido.wav', 'wb') as f:
f.write(result)
else:
print(result)
vol参数表示音量;per参数表示说话风格;aue指定数据格式,最好指定为6,表示获取wav格式,这样在树莓派上可以直接播放
图灵机器人平台同样提供REST API的调用,不过相比百度限制会多一些,每天免费使用的次数是有上限的,一些额外的特性也需要额外付费。图灵没有提供封装好的类库,所以我们需要自己构造http调用来获取服务。下面是示例程序:
import requests
import json
url='http://openapi.tuling123.com/openapi/api/v2' #服务地址
params={
'reqType':0,
'perception':{
'inputText':{
'text':'' #询问的文本
}
},
'userInfo':{
'apiKey':'', #使用你自己申请的
'userId':''
}
}
params['perception']['inputText']['text']='你好呀'
response=requests.post(url,json=params)
#print(response.text)
answer=json.loads(response.text)
print(answer['results'][0]['values']['text'])
LED显示只是为了增加一点互动,不是必要的组件。具体的资料参考:https://github.com/rm-hull/luma.led_matrix 里面有很具体的MAX7219 LED点阵屏驱动库的使用教程。
测试过所有模块后,把它们整合到一起,在一个主程序中实现设计好的流程。我把相关的模块都封装成类,然后在main.py中调用。main.py的代码如下:
# -*- coding: UTF-8 -*-
import pyaudio
import os
import sys
import time
import wave
import numpy as np
from enum import Enum
#from FormatConvertModule import FormatConvert #格式转换模块
from StopWatchModule.StopWatch import StopWatch #计时器模块
from BaiduAipModule.BaiduAip import BaiduAipSpeech #百度语音服务模块
from TuringRobotModule.TuringRobot import TuringRobot #图灵机器人模块
from configparser import ConfigParser
from MatrixLedModule.Matrix_Led import Matrix_Led #Led模块
#某些配置写在配置文件里
cp=ConfigParser()
cp.read('/home/pi/VoiceRobot/Configs/MainApp.cfg')
print(cp.sections())
section=cp.sections()[0]
WIDTH=int(cp.get(section,'WIDTH'))
CHANNELS=int(cp.get(section,'CHANNELS'))
RATE=int(cp.get(section,'RATE'))
CHUNK=int(cp.get(section,'CHUNK'))
RECORD_MAX_SECONDS=int(cp.get(section,'RECORD_MAX_SECONDS'))
RESPONSE_WAVE_PATH=cp.get(section,'RESPONSE_WAVE_PATH')
RECORD_PCM_PATH=cp.get(section,'RECORD_PCM_PATH')
VOICE_STD_THRESHOLD=int(cp.get(section,'VOICE_STD_THRESHOLD'))
class AudioState(Enum):
'''标记音频操作当前的状态'''
LISTENING=1 #监听状态
RECORDING=2 #录音状态
PLAYING=3 #播放状态
def getStdOfVoiceFrame(in_data):
'''获取一帧音频信号的标准差'''
return np.std(np.frombuffer(in_data,dtype=np.short))
frames=[]
wf=None
stopWatch_Record=None #用于存储录音时长
baiduAip=BaiduAipSpeech() #百度语音识别与合成
turingRobot=TuringRobot() #图灵机器人
p=pyaudio.PyAudio()
audioState=AudioState.LISTENING
matrixLed=Matrix_Led(2,90,0) #LED相关
matrixLed.setScrollChar(27)
def handleVoice(frames):
'''处理接收到的声音信息'''
global baiduAip
np.array(frames).tofile(RECORD_PCM_PATH) #先保存为pcm格式; wav文件相比pcm文件只是多了开头的描述字节,所以可以直接保存为pcm文件,不需要保存为wav后再格式转换
question=baiduAip.speechRecognition(RECORD_PCM_PATH) #获取文字
if question is None:
question=''
print('Ask a question : %s' % question)
answer=turingRobot.getResponse(question) #获取回应
print('Answer : %s' % answer)
baiduAip.speechSynthesis(answer,RESPONSE_WAVE_PATH) #获取应答
def callback(in_data,frame_count,time_info,status):
global audioState
global frames
global stopWatch_Record
global wf
#如果当前帧的std大于阈值且处于监听状态,开始录音
if audioState==AudioState.LISTENING and getStdOfVoiceFrame(in_data)>=VOICE_STD_THRESHOLD:
matrixLed.scrollingChar() #Led相关
stopWatch_Record=StopWatch() #开始计时
print('Recording...')
frames.append(in_data)
audioState=AudioState.RECORDING #当前处于RECORDING状态
return (bytes(len(in_data)),pyaudio.paContinue) #如果不是播放状态,应该输入空的数据流
#如果当前处于RECORDING状态
if audioState==AudioState.RECORDING:
matrixLed.scrollingChar() #Led相关
t0=stopWatch_Record.getSeconds()
frames.append(in_data)
#如果录音时长已经超过限定,停止录音,保存音频文件
if t0>RECORD_MAX_SECONDS:
print('Done!')
handleVoice(frames)
wf=wave.open(RESPONSE_WAVE_PATH,'rb')
audioState=AudioState.PLAYING #PLAYING状态
matrixLed.setBrightChar(1) #Led相关
frames=[]
return (bytes(len(in_data)),pyaudio.paContinue)
if audioState==AudioState.PLAYING:
matrixLed.brightChar() #Led相关
data=wf.readframes(frame_count)
if len(data)/WIDTH >= frame_count:
return (data,pyaudio.paContinue)
else:
print('stop playing')
wf.close()
audioState=AudioState.LISTENING
matrixLed.setScrollChar(27) #Led相关
return (data,pyaudio.paComplete)
matrixLed.scrollingChar() #Led相关
return (bytes(len(in_data)),pyaudio.paContinue)
stream=p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
stream_callback=callback) #指定了input和output都为true,表示这是输入输出流,可以同时写入data和读取data
stream.start_stream()
print('Listening...')
try:
while True:
while stream.is_active():
pass
stream.stop_stream()
stream.close()
stream=p.open(format=p.get_format_from_width(WIDTH),
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
stream_callback=callback)
stream.start_stream()
print('Listening...')
except Exception as e:
stream.stop_stream()
stream.close()
p.terminate()
raise e
完整的代码放在了github上:https://github.com/RyanWang20180512/PiVoiceRobot