待修改
项目简介:运用百度语音进行声音转中文的识别与合成,智能对话使用图灵机器人,录音则,linux端用pythonaudio 模块.树莓派端因为pythonaudio不兼容问题,因此用arecord进行录音.最终代码约150行.代码发布在github上.https://github.com/luyishisi/python_yuyinduihua
这点非常关键,在后期多数问题都是出现在环境不兼容上.
# -*- coding: utf-8 -*- from pyaudio import PyAudio, paInt16 import numpy as np from datetime import datetime import wave import time import urllib, urllib2, pycurl import base64 import json import os import sys reload(sys) sys.setdefaultencoding( "utf-8" )
这部分环境最好搭建,只需要
apt-get install python-wave* 这类的安装命令就可以轻松搞定.本质上安装模块就是在找安装的命令.我一半就是把模块肯定会有的名词后面接上*用于模糊匹配.
如果有模块不懂得装,还是百度一下,难度不大.还有mpg123用来播发
如果你出现这个博文下出现的错误,请果断弃坑.换用命令行录音实现,不要折腾pyaudio了.
##先更新软件包 sudo apt-get update sudo apt-get upgrade ##安装必要的程序 sudo apt-get -y install alsa-utils alsa-tools alsa-tools-gui alsamixergui
主要使用的工具
想通过终端来调整扬声器的音量,只需要输入alsamixer.这个很重要你使用的录音设备的录音音量需要这里设置,而且你可以明显的看到自己的声卡是否有问题.
使用的录音设备我用的是 https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-3667091491.40.mktumv&id=41424706506
录音的命令使用的是arecord
arecord,aplay是命令行的ALSA声卡驱动的录音和播放工具. arecord是命令行ALSA声卡驱动的录音程序.支持多种文件格式和多个声卡. aplay是命令行播放工具,支持多种文件格式.
命令格式:这部分需要研读一下.主要使用dfr三个参数
arecord [flags] [filename] aplay [flags] [filename [filename]] ... 选项: -h, --help帮助. --version打印版本信息. -l, --list-devices列出全部声卡和数字音频设备. -L, --list-pcms列出全部PCM定义. -D, --device=NAME指定PCM设备名称. -q --quiet安静模式. -t, --file-type TYPE文件类型(voc,wav,raw或au). -c, --channels=#设置通道号. -f --format=FORMAT设置格式.格式包括:S8 U8 S16_LE S16_BE U16_LE U16_BE S24_LE S24_BE U24_LE U24_BE S32_LE S32_BE U32_LE U32_BE FLOAT_LE FLOAT_BE FLOAT64_LE FLOAT64_BE IEC958_SUBFRAME_LE IEC958_SUBFRAME_BE MU_LAW A_LAW IMA_ADPCM MPEG GSM -r, --rate=#<Hz>设置频率. -d, --duration=#设置持续时间,单位为秒. -s, --sleep-min=#设置最小休眠时间. -M, --mmap mmap流. -N, --nonblock设置为非块模式. -B, --buffer-time=#缓冲持续时长.单位为微妙. -v, --verbose显示PCM结构和设置. -I, --separate-channels设置为每个通道一个单独文件.
示例:
aplay -c 1 -t raw -r 22050 -f mu_law foobar 播放raw文件foobar.以22050Hz,单声道,8位,mu_law格式. arecord -d 10 -f cd -t wav -D copy foobar.wav 以CD质量录制foobar.wav文件10秒钟.使用PCM的"copy".
这部分难度不大,测试代码如下.
#语音合成 #encoding=utf-8 import wave import urllib, urllib2, pycurl import base64 import json ## get access token by api key & secret key ## 获得token,需要填写你的apikey以及secretkey def get_token(): apiKey = "Ll0c53MSac6GBOtpg22ZSGAU" secretKey = "44c8af396038a24e34936227d4a19dc2" auth_url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + apiKey + "&client_secret=" + secretKey; res = urllib2.urlopen(auth_url) json_data = res.read() return json.loads(json_data)['access_token'] def dump_res(buf): print (buf) ## post audio to server def use_cloud(token): fp = wave.open('2.wav', 'rb') ##已经录好音的语音片段 nf = fp.getnframes() f_len = nf * 2 audio_data = fp.readframes(nf) cuid = "7519663" #你的产品id srv_url = 'http://vop.baidu.com/server_api' + '?cuid=' + cuid + '&token=' + token http_header = [ 'Content-Type: audio/pcm; rate=8000', 'Content-Length: %d' % f_len ] c = pycurl.Curl() c.setopt(pycurl.URL, str(srv_url)) #curl doesn't support unicode #c.setopt(c.RETURNTRANSFER, 1) c.setopt(c.HTTPHEADER, http_header) #must be list, not dict c.setopt(c.POST, 1) c.setopt(c.CONNECTTIMEOUT, 30) c.setopt(c.TIMEOUT, 30) c.setopt(c.WRITEFUNCTION, dump_res) c.setopt(c.POSTFIELDS, audio_data) c.setopt(c.POSTFIELDSIZE, f_len) c.perform() #pycurl.perform() has no return val if __name__ == "__main__": token = get_token() #获得token use_cloud(token) #进行处理,输出在函数内部
官方网址:http://www.tuling123.com/
图灵机器人部分的测试代码
难度不大非常轻松.你得去注册一下,然后使用他们给你的key和api.剩下的就是json的文本提取
# -*- coding: utf-8 -*- import urllib import json def getHtml(url): page = urllib.urlopen(url) html = page.read() return html if __name__ == '__main__': key = '05ba411481c8cfa61b91124ef7389767' api = 'http://www.tuling123.com/openapi/api?key=' + key + '&info=' while True: info = raw_input('我: ') request = api + info response = getHtml(request) dic_json = json.loads(response) print '机器人: '.decode('utf-8') + dic_json['text']
这部分,在正常电脑上,只要环境没有大问题就很轻松,代码放在整体的源代码中,这里做个小说明.
这部分代码不可运行,在整体源代码中可以.不过这部分稍微需要抽取出来,作为理解
建立的pa是pyudio对象,可以获取当前的音高,然后检测当音高超过200就启动,录音.同时有一个5秒的额外限制.
NUM_SAMPLES = 2000 # pyAudio内部缓存的块的大小 SAMPLING_RATE = 8000 # 取样频率 LEVEL = 1500 # 声音保存的阈值 COUNT_NUM = 20 # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音 SAVE_LENGTH = 8 # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样 # 开启声音输入 pa = PyAudio() stream = pa.open(format=paInt16, channels=1, rate=SAMPLING_RATE, input=True, frames_per_buffer=NUM_SAMPLES)\ string_audio_data = stream.read(NUM_SAMPLES) # 将读入的数据转换为数组 audio_data = np.fromstring(string_audio_data, dtype=np.short) # 计算大于LEVEL的取样的个数 large_sample_count = np.sum( audio_data > LEVEL ) temp = np.max(audio_data) if temp > 2000 and t == 0: t = 1#开启录音 print "检测到信号,开始录音,计时五秒" begin = time.time() print temp
这里主要还是记录下整体的一些资料.在树莓派上能够成功运行下面的命令就算ok.别的是一路研究的资料.
sudo arecord -D “plughw:1,0” -d 5 f1.wav
参数释义: -D这个参数的意思就选择设备,外部设备就是plughw:1,0 内部设备就是plughw:0,0,树莓派本身并没有录音模块,故没有内部设备。-d 5
的意思就是录制时间为5秒,如果不加这个参数就是一直录音直到ctrol+C停止, 最后生成的文件名字叫做f1.wav
百度语音要求的是16比特的所以还需要设定-f
具体pcm的说明如下:
这都是PCM的一种表示范围的方法,所以表示方法中最小值等价,最大值等价,中间的数据级别就是对应的进度了,可以都映射到-1~1范围。
PCMU应该是指无符号PCM:可以包括U8,U16_LE,U16_BE,… PCMA应该是指有符号PCM:可以包括S8,S16_LE,S16_BE,…
查看声卡
cat/proc/asound/cards cat/proc/asound/modules
源代码如下:解析在注释上
# -*- coding: utf-8 -*- from pyaudio import PyAudio, paInt16 import numpy as np from datetime import datetime import wave import time import urllib, urllib2, pycurl import base64 import json import os import sys reload(sys) sys.setdefaultencoding( "utf-8" ) #一些全局变量 save_count = 0 save_buffer = [] t = 0 sum = 0 time_flag = 0 flag_num = 0 filename = '' duihua = '1' def getHtml(url): page = urllib.urlopen(url) html = page.read() return html def get_token(): apiKey = "Ll0c53MSac6GBOtpg22ZSGAU" secretKey = "44c8af396038a24e34936227d4a19dc2" auth_url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + apiKey + "&client_secret=" + secretKey; res = urllib2.urlopen(auth_url) json_data = res.read() return json.loads(json_data)['access_token'] def dump_res(buf):#输出百度语音识别的结果 global duihua print "字符串类型" print (buf) a = eval(buf) print type(a) if a['err_msg']=='success.': #print a['result'][0]#终于搞定了,在这里可以输出,返回的语句 duihua = a['result'][0] print duihua def use_cloud(token):#进行合成 fp = wave.open(filename, 'rb') nf = fp.getnframes() f_len = nf * 2 audio_data = fp.readframes(nf) cuid = "7519663" #产品id srv_url = 'http://vop.baidu.com/server_api' + '?cuid=' + cuid + '&token=' + token http_header = [ 'Content-Type: audio/pcm; rate=8000', 'Content-Length: %d' % f_len ] c = pycurl.Curl() c.setopt(pycurl.URL, str(srv_url)) #curl doesn't support unicode #c.setopt(c.RETURNTRANSFER, 1) c.setopt(c.HTTPHEADER, http_header) #must be list, not dict c.setopt(c.POST, 1) c.setopt(c.CONNECTTIMEOUT, 30) c.setopt(c.TIMEOUT, 30) c.setopt(c.WRITEFUNCTION, dump_res) c.setopt(c.POSTFIELDS, audio_data) c.setopt(c.POSTFIELDSIZE, f_len) c.perform() #pycurl.perform() has no return val # 将data中的数据保存到名为filename的WAV文件中 def save_wave_file(filename, data): wf = wave.open(filename, 'wb') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(SAMPLING_RATE) wf.writeframes("".join(data)) wf.close() NUM_SAMPLES = 2000 # pyAudio内部缓存的块的大小 SAMPLING_RATE = 8000 # 取样频率 LEVEL = 1500 # 声音保存的阈值 COUNT_NUM = 20 # NUM_SAMPLES个取样之内出现COUNT_NUM个大于LEVEL的取样则记录声音 SAVE_LENGTH = 8 # 声音记录的最小长度:SAVE_LENGTH * NUM_SAMPLES 个取样 # 开启声音输入pyaudio对象 pa = PyAudio() stream = pa.open(format=paInt16, channels=1, rate=SAMPLING_RATE, input=True, frames_per_buffer=NUM_SAMPLES) token = get_token()#获取token key = '05ba411481c8cfa61b91124ef7389767' #key和api的设定 api = 'http://www.tuling123.com/openapi/api?key=' + key + '&info=' while True: # 读入NUM_SAMPLES个取样 string_audio_data = stream.read(NUM_SAMPLES) # 将读入的数据转换为数组 audio_data = np.fromstring(string_audio_data, dtype=np.short) # 计算大于LEVEL的取样的个数 large_sample_count = np.sum( audio_data > LEVEL ) temp = np.max(audio_data) if temp > 2000 and t == 0: t = 1#开启录音 print "检测到信号,开始录音,计时五秒" begin = time.time() print temp if t: print np.max(audio_data) if np.max(audio_data)<1000: sum += 1 print sum end = time.time() if end-begin>5: time_flag = 1 print "五秒到了,准备结束" # 如果个数大于COUNT_NUM,则至少保存SAVE_LENGTH个块 if large_sample_count > COUNT_NUM: save_count = SAVE_LENGTH else: save_count -= 1 if save_count < 0: save_count = 0 if save_count > 0: # 将要保存的数据存放到save_buffer中 save_buffer.append(string_audio_data ) else: # 将save_buffer中的数据写入WAV文件,WAV文件的文件名是保存的时刻 #if time_flag: if len(save_buffer) > 0 or time_flag: #filename = datetime.now().strftime("%Y-%m-%d_%H_%M_%S") + ".wav"#原本是用时间做名字 filename = str(flag_num)+".wav" flag_num += 1 save_wave_file(filename, save_buffer) save_buffer = [] t = 0 sum =0 time_flag = 0 print filename, "保存成功正在进行语音识别" use_cloud(token) print duihua info = duihua duihua = "" request = api + info response = getHtml(request) dic_json = json.loads(response) #print '机器人: '.decode('utf-8') + dic_json['text']#这里麻烦的是字符编码 #huida = ' '.decode('utf-8') + dic_json['text'] a = dic_json['text'] print type(a) unicodestring = a # 将Unicode转化为普通Python字符串:"encode" utf8string = unicodestring.encode("utf-8") print type(utf8string) print str(a) url = "http://tsn.baidu.com/text2audio?tex="+dic_json['text']+"&lan=zh&per=0&pit=1&spd=7&cuid=7519663&ctp=1&tok=24.a5f341cf81c523356c2307b35603eee6.2592000.1464423912.282335-7519663" os.system('mpg123 "%s"'%(url))#用mpg123来播放
这里算是解析一下主要坑的地方.除了环境因素,就是中文编码,还有对象解析了.源代码中从百度语音识别出来返回的是一个字典对象,而字典对象中有部分是直接一个字符串,有的则是数组,首先得读出字符串来确定是否是succees.然后再读取text数组.中的中文.
另外一个bug是中文编码.要这么处理
import sys reload(sys) sys.setdefaultencoding( "utf-8" ) #还有 #print '机器人: '.decode('utf-8') + dic_json['text'] #huida = ' '.decode('utf-8') + dic_json['text'] a = dic_json['text'] print type(a) unicodestring = a # 将Unicode转化为普通Python字符串:"encode" utf8string = unicodestring.encode("utf-8")
然后移植到树莓派上出现的主要问题是有aercode命令出现文件目录找不到.那么说明是你声卡选择错了,录音声音太小了也是,使用alsamixer选择清楚.
还有录音识别效率问题,问题主要集中在百度有他的要求,所以得设定16bit.然后再听一遍录制的声音,看看音量会不会太大,,有没有很粗糙的声音.最好能分开测试
pyaudio错误得我不要不要的,,所以还是绕开,使用aercode进行录音命令,然后python进行掉用..代码也短很多,但是失去了实时处理音波的能力.
# -*- coding: utf-8 -*- from pyaudio import PyAudio, paInt16 import numpy as np from datetime import datetime import wave import time import urllib, urllib2, pycurl import base64 import json import os import sys reload(sys) sys.setdefaultencoding( "utf-8" ) save_count = 0 save_buffer = [] t = 0 sum = 0 time_flag = 0 flag_num = 0 filename = '2.wav' duihua = '1' def getHtml(url): page = urllib.urlopen(url) html = page.read() return html def get_token(): apiKey = "Ll0c53MSac6GBOtpg22ZSGAU" secretKey = "44c8af396038a24e34936227d4a19dc2" auth_url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=client_credentials&client_id=" + apiKey + "&client_secret=" + secretKey; res = urllib2.urlopen(auth_url) json_data = res.read() return json.loads(json_data)['access_token'] def dump_res(buf): global duihua print "字符串类型" print (buf) a = eval(buf) print type(a) if a['err_msg']=='success.': #print a['result'][0]#终于搞定了,在这里可以输出,返回的语句 duihua = a['result'][0] print duihua def use_cloud(token): fp = wave.open(filename, 'rb') nf = fp.getnframes() f_len = nf * 2 audio_data = fp.readframes(nf) cuid = "7519663" #产品id srv_url = 'http://vop.baidu.com/server_api' + '?cuid=' + cuid + '&token=' + token http_header = [ 'Content-Type: audio/pcm; rate=8000', 'Content-Length: %d' % f_len ] c = pycurl.Curl() c.setopt(pycurl.URL, str(srv_url)) #curl doesn't support unicode #c.setopt(c.RETURNTRANSFER, 1) c.setopt(c.HTTPHEADER, http_header) #must be list, not dict c.setopt(c.POST, 1) c.setopt(c.CONNECTTIMEOUT, 30) c.setopt(c.TIMEOUT, 30) c.setopt(c.WRITEFUNCTION, dump_res) c.setopt(c.POSTFIELDS, audio_data) c.setopt(c.POSTFIELDSIZE, f_len) c.perform() #pycurl.perform() has no return val # 将data中的数据保存到名为filename的WAV文件中 def save_wave_file(filename, data): wf = wave.open(filename, 'wb') wf.setnchannels(1) wf.setsampwidth(2) wf.setframerate(SAMPLING_RATE) wf.writeframes("".join(data)) wf.close() token = get_token() key = '05ba411481c8cfa61b91124ef7389767' api = 'http://www.tuling123.com/openapi/api?key=' + key + '&info=' while(True): os.system('arecord -D "plughw:1,0" -f S16_LE -d 5 -r 8000 /home/luyi/yuyinduihua/2.wav') use_cloud(token) print duihua info = duihua duihua = "" request = api + info response = getHtml(request) dic_json = json.loads(response) a = dic_json['text'] print type(a) unicodestring = a # 将Unicode转化为普通Python字符串:"encode" utf8string = unicodestring.encode("utf-8") print type(utf8string) print str(a) url = "http://tsn.baidu.com/text2audio?tex="+dic_json['text']+"&lan=zh&per=0&pit=1&spd=7&cuid=7519663&ctp=1&tok=24.a5f341cf81c523356c2307b35603eee6.2592000.1464423912.282335-7519663" os.system('mpg123 "%s"'%(url))