sound_tool.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #-*- coding:utf-8 -*-
  2. import os,sys
  3. reload(sys)
  4. sys.setdefaultencoding('utf-8')
  5. import wave
  6. from time import sleep
  7. import time
  8. import numpy
  9. import numpy as np
  10. import pyaudio
  11. # import pylab
  12. # import matplotlib.pyplot as pl
  13. # import matplotlib
  14. import math,random
  15. from ssat_sdk.sound.audio_recorder import ARecorder
  16. # from ssat_sdk.sound.audio_device import AudioDevice
  17. from ssat_sdk.sound.audio_recorder import READ_BUF_TIME
  18. from ssat_sdk.sound.audio_analysis import AAnalysis
  19. from ssat_sdk.sat_environment import getSATTmpDIR,getVoicecards,writeSoundList
  20. import threading,thread
  21. import Queue
  22. from scipy import fftpack
  23. from scipy import signal
  24. from scipy.io import wavfile
  25. from ssat_sdk.device_manage.sound_manager import *
  26. from threading import Lock
  27. FORMAT = pyaudio.paInt16
  28. RATE = 44100
  29. RECORD_SECONDS = 5
  30. data = []
  31. FFT_LEN = 128
  32. frames = []
  33. counter = 1
  34. isMonitor = True
  35. NoSoundLT = 10#记录连续8次<SoundLevel状态,1秒钟大概抓40次数据。10次代表0.2秒,即表示无声。
  36. NoSoundTLimit = 0.1 #没有声音时长判断标准
  37. SoundTLimit = 0.06 #有声时长判断标准
  38. ExpList = []
  39. t = None
  40. q = Queue.Queue()
  41. # processing block
  42. # window = signal.hamming(CHUNK)
  43. TAG = "SoundTool"
  44. MuTex = Lock()
  45. class AudioIdentify():
  46. # 设备名称,默认拾音器=0, 亚为USB=1;
  47. Sound_Has = 0
  48. Sound_No = 1
  49. Sound_Break = 2
  50. def __init__(self):
  51. print "Init AudioIdentify"
  52. self.aAnalysis = AAnalysis()
  53. self.isSound = False #整个监听过程,是否有声音
  54. self.hasBlock = False #是否有断续现象
  55. self.deviceType = 0
  56. # self.audioChecker = Sound()
  57. self.audioChecker = None
  58. self.audioFile = ""
  59. try:
  60. list = getVoicecards()
  61. print u'音频设备列表:',list
  62. self.default = list["default"]
  63. for item in list["devices"]:
  64. if self.default == item["name"]:
  65. self.deviceType = int(item["type"])
  66. break
  67. if self.deviceType == 1: # usb
  68. print u'使用usb声音检测设备'
  69. self.audioChecker = SoundManager()
  70. if self.audioChecker.IsOpen != 0:
  71. print u'初始化声音设备失败'
  72. else:
  73. self.audioChecker = ARecorder()
  74. print u'使用拾音器/MIC设备'
  75. except Exception, e:
  76. self.audioChecker = ARecorder()
  77. print u"获取VoiceCards失败", e
  78. '''
  79. 设置有声阈值.录制声音3秒钟,然后获取声音强度,写入resource_run.cfg文件:
  80. [Sound]
  81. sound_list = [2000, 2000]
  82. '''
  83. def setSoundLevel(self):
  84. self.monitorSound(3)
  85. soundPower = self.getSoundPower()
  86. soundLevel = [int(soundPower),int(soundPower)]
  87. writeSoundList(soundLevel)
  88. # 是否有声音,muteVoltage静音电压值
  89. # 废弃函数
  90. def hasSound(self, duration, muteVoltage = 1.0):
  91. if self.deviceType == 0:
  92. return self.isSound
  93. else:
  94. if self.audioChecker == None:
  95. print u'USB设备未初始化'
  96. return False
  97. # 获取静音百分比,1.0表示完全静音;
  98. percentMute = self.audioChecker.IsMute(duration, muteVoltage)
  99. # 静音百分比小于1.0时认为有声音;
  100. self.isSound = True if percentMute < 1.0 else False
  101. return self.isSound
  102. '''
  103. 返回当前设备状态。设备就绪返回True,失败返回False。
  104. '''
  105. def getStatus(self):
  106. if self.deviceType == 0:
  107. return self.audioChecker.getStatus()
  108. if self.audioChecker == None:
  109. print u'USB设备未初始化'
  110. return False
  111. return True if self.audioChecker.IsOpen == 0 else False
  112. '''
  113. 判断当前声音是否中断,中断返回True,没有中断返回False
  114. collectionTime: 采集时长,单位毫秒
  115. muteVoltage: 静音电压值,单位伏
  116. interruptTime: 停顿时长,单位毫秒
  117. '''
  118. def isInterrupt(self, collectionTime = 5000, muteVoltage = 1.0, interruptTime = 200):
  119. # return self.audioChecker.isInterrupt()
  120. if self.audioChecker == None:
  121. print u'USB设备未初始化'
  122. return False
  123. return self.audioChecker.IsInterrupt( collectionTime, muteVoltage, interruptTime)
  124. '''
  125. 判断当前是否监听到声音,成功返回True,失败返回False。
  126. collectionTime: 采集时长,单位毫秒
  127. muteVoltage: 静音电压值,单位伏
  128. [return] Bool
  129. '''
  130. def isOK(self):
  131. return self.isSound
  132. '''
  133. 采集声音(主线程)
  134. [int] int :采集多少时长的声明,以秒为单位
  135. [return] void
  136. '''
  137. def startCHK(self, seconds,muteVoltage = 1.0):
  138. if self.deviceType == 0:
  139. self.monitorSound(seconds)
  140. elif self.deviceType == 1:
  141. if self.audioChecker == None:
  142. print u'USB设备未初始化'
  143. return False
  144. # 获取静音百分比,1.0表示完全静音;
  145. percentMute = self.audioChecker.IsMute(seconds, muteVoltage)
  146. # 静音百分比小于1.0时认为有声音;
  147. self.isSound = True if percentMute < 1.0 else False
  148. # return self.isSound
  149. '''
  150. 返回monitorSound过程中的声音平均强度,如果LR为True则返回左右声道的声音强度,Freq为频率范围,单位Hz。
  151. 切割音频,进行最大强度累计,然后算平均值。
  152. :param minFreq:频率范围中的最小频率值。 作废
  153. :param maxFreq:频率范围中的最大频率值。 作废
  154. :param LR:左右声道检测选项。True:左右声道分开检测;False:仅检测0声道,可能是左声道,有可能仅有的一个声道。
  155. :param audioFile: 传入新的音频文件,不用monitorSound的音频文件。
  156. :return: LR=True时,返回左右声道声音强度2个值,LR=False时,返回一个声道强度值。
  157. '''
  158. def getSoundPower(self, minFreq=50, maxFreq=20000, LR = False, audioFile="", avg=False):
  159. if audioFile.__len__() > 2:
  160. self.aAnalysis.analyWav(audioFile)
  161. return self.aAnalysis.getTotalAVGPower(LR=LR)
  162. '''
  163. 返回monitorSound过程中的最大音量,如果LR为True,则返回左右声道的声音最大强度。
  164. :param LR:左右声道检测选项。True:左右声道分开检测;False:仅检测0声道,可能是左声道,有可能仅有的一个声道。
  165. :param audioFile: 传入新的音频文件,不用monitorSound的音频文件。
  166. :return: LR=True时,返回左右声道声音强度2个值,LR=False时,返回一个声道强度值。
  167. '''
  168. def getMaxSoundPower(self, LR=False, audioFile=""):
  169. if audioFile.__len__() > 2:
  170. self.aAnalysis.analyWav(audioFile)
  171. return self.aAnalysis.getTotalMaxPower(LR=LR)
  172. '''
  173. 获取左右声道有无声的状态。
  174. :return result,valueList。
  175. result:-1代表声音检测异常;1表示单声道;2 表示双声道
  176. valueList:-1代表没有声音,0代表仅左声道,1代表仅右声道,2代表左右声道有声
  177. '''
  178. def getLRVariation(self):
  179. return self.aAnalysis.getLRVariation()
  180. '''
  181. 监听声音状态,阻塞式监听
  182. :param seconds:监听声音异常时间
  183. :param expList数组:声音异常记录数组。数组第一个值{"time":time.asctime( time.localtime(time.time()))},记录开始时间。
  184. 后续,碰到一次异常,记录一次。
  185. expList第一个字典数据,有audio 键,取录音文件路径。status键:0 代表无异常;1代表没有声音发出;2代表声音有间断。
  186. :param waveFile:可以指定录制的音频文件路径
  187. :return expList数组。 和带入的expList一样
  188. '''
  189. def monitorSound(self, seconds,expList=None, waveFile = None, buf_time=READ_BUF_TIME):
  190. global ExpList
  191. MuTex.acquire()
  192. print "monitorSound start:",seconds,expList
  193. del ExpList
  194. if expList is not None:
  195. ExpList = expList
  196. else:
  197. ExpList = []
  198. if self.deviceType == 0:
  199. firstItem = {"time":time.asctime(time.localtime(time.time()))}
  200. # analysisLock = threading.Condition()
  201. self.aAnalysis.startFramesAnalysis(self.audioChecker.getFrameQue(),
  202. self.audioChecker.CHANNELS,
  203. self.audioChecker.WIDTH,
  204. buf_time)
  205. self.audioChecker.monitor(seconds,saveWave=True, waveFile=waveFile, buf_time=buf_time)
  206. self.audioFile = self.audioChecker.waveFile
  207. time.sleep(1) #预留1秒钟,用于音频分析。
  208. self.aAnalysis.endFramesAnalysis()
  209. firstItem["audio"] = self.audioChecker.getWavFile()
  210. firstItem["status"] = self.Sound_No
  211. retExpList = self.changeMonSoundResult(firstItem)
  212. # print u'monitorSound end',retExpList
  213. MuTex.release() # 解锁
  214. ExpList.append(firstItem)
  215. print "monitorSound end:", seconds, retExpList
  216. return retExpList
  217. elif self.deviceType == 1:
  218. # 采集时长,单位毫秒;
  219. # collectionTime = 5000
  220. # 静音电压,单位伏;
  221. muteVoltage = 1.0
  222. # 停顿时长,单位毫秒;
  223. interruptTime = 200
  224. ExpList.append({"time":time.asctime( time.localtime(time.time())),"audio":""})
  225. isInterrupt = self.audioChecker.IsInterrupt( seconds, muteVoltage, interruptTime)
  226. if isInterrupt == True:
  227. ExpList.append({"time":time.asctime( time.localtime(time.time()))})
  228. MuTex.release() # 解锁
  229. return ExpList
  230. '''
  231. 启动一个线程,调用monitorSound函数。
  232. 详细说明,查看monitorSound函数。
  233. '''
  234. def monitorSoundTH(self,seconds,expList=None, waveFile = None, buf_time=READ_BUF_TIME):
  235. thread.start_new_thread(self.monitorSound, (seconds, expList, waveFile, buf_time,))
  236. def changeMonSoundResult(self,expFirstItem):
  237. self.isSound = self.aAnalysis.hasSound
  238. self.hasBlock = self.aAnalysis.hasBlock
  239. soundPower = self.getSoundPower()
  240. print "changeMonSoundResult:", self.aAnalysis.hasSound, self.aAnalysis.hasBlock, self.aAnalysis.hasPlosive,soundPower
  241. if soundPower - self.aAnalysis.TH_POWER >= 0:
  242. self.aAnalysis.hasSound = True
  243. if self.aAnalysis.hasSound is True and self.aAnalysis.hasBlock is True:
  244. expFirstItem["status"] = self.Sound_Break
  245. elif self.aAnalysis.hasSound is True and self.aAnalysis.hasBlock is False:
  246. expFirstItem["status"] = self.Sound_Has
  247. else:
  248. expFirstItem["status"] = self.Sound_No
  249. retExpList = []
  250. retExpList.append(expFirstItem)
  251. # del ExpList #线程调用,回传参数,需要用到
  252. return retExpList
  253. # print "changeMonSoundResult:",ExpList
  254. '''
  255. 开启一个后台线程,录制音频文件,并返回音频文件路径(保存于临时文件夹)。
  256. :param seconds:录制声音时间
  257. :param fileName:希望命名的文件名
  258. :param CHANNELS:录制单声道时为1,录制双声道时为2
  259. 成功时返回音频文件路径,失败则返回空结果
  260. '''
  261. def recordAudioFileReturnPath(self, seconds, fileName,CHANNELS = 2):
  262. try:
  263. filePath = os.path.join(getSATTmpDIR(), fileName)
  264. audioPath = self.monitorSound(seconds,waveFile=filePath)[0]["audio"]
  265. return audioPath
  266. except Exception,e:
  267. # LoggingUtil.printLog(u"获取录制的音频文件失败!")
  268. print u"获取录制的音频文件失败!",e
  269. return "wav file record fail!!!"
  270. '''
  271. 读取立体声文件并分离左右声道音源,
  272. 并返回左右声道音源的路径
  273. '''
  274. def splitChannel(self, filePath = ""):
  275. # 读取WAV声音文件
  276. if filePath == "":
  277. filePath = self.audioChecker.getWavFile()
  278. sampleRate, musicData = wavfile.read(filePath)
  279. # 提取左右声道数据
  280. left = []
  281. right = []
  282. musicData.shape = -1,2
  283. musicData = musicData.T
  284. left = musicData[0]
  285. right = musicData[1]
  286. # 写入结果文件
  287. section_path = filePath.split(".")[0]
  288. leftPath = section_path + "_left.wav"
  289. rightPath = section_path + "_right.wav"
  290. wavfile.write(leftPath, sampleRate, np.array(left))
  291. wavfile.write(rightPath, sampleRate, np.array(right))
  292. return leftPath, rightPath
  293. '''
  294. 对比两个声音模式的参数,判断是否为同一种声音模式。
  295. 传入的参数,mode1和mode2数组数据要对齐,按低频率到高频率幅度值排列。
  296. 建议采集幅度:200HZ、500HZ、1KHz、1.5KHz、2KHz、5KHz、10KHz
  297. :param mode1,数组[200hz声音强度、500hz声音强度...]
  298. :param mode2,数组[200hz声音强度、500hz声音强度...]
  299. :return True/False, True代表一样,False代表不一样
  300. '''
  301. def cmpSoundMode(self, mode1, mode2, offset=2000):
  302. freqD = 0
  303. for index in range(mode1.__len__()):
  304. if abs(mode1[index] - mode2[index]) > offset:
  305. return False
  306. elif abs(mode1[index] - mode2[index]) > 2*offset/3:
  307. freqD += 2
  308. elif abs(mode1[index] - mode2[index]) > offset/3:
  309. freqD += 1
  310. if freqD/mode1.__len__() > 1:
  311. return False
  312. else:
  313. return True
  314. '''
  315. 用于检测幅度不变单频音的稳定播放,支持左右两个声道不同单频音
  316. 在monitorSound后,根据记录下的音频文件,判断最大幅度的频率是否和目标频率吻合。
  317. 或者输入一个音频文件,分析音频文件,判断最大幅度的频率是否和目标频率吻合。
  318. 精确度:1秒为单位,进行频率检测。
  319. :param stdFreq:目标频率,单位Hz
  320. :param audioFile:录制的音频文件
  321. :param offset:表示频率偏差允许范围
  322. :return 声道1数值,声道2数值。1:频率完全吻合,0:频率部分吻合,有频率变化情况(可能是噪音);-1:频率完全不吻合
  323. '''
  324. def checkSingleFreq(self, stdFreq,audioFile="",offset=10):
  325. #audioFile: D:\1.wav
  326. ret1 = -1
  327. ret2 = -1
  328. if audioFile is not None and audioFile.find(".wav") > -1:
  329. self.aAnalysis.analyWav(audioFile, buf_time=1)
  330. else:
  331. self.aAnalysis.analyWav(self.audioFile, buf_time=1)
  332. # 记录每秒的最大幅度频率
  333. freqList = self.aAnalysis.getFFTFreq(LR=True)
  334. print "checkSingleFreq,freqList:",freqList.__len__(),freqList
  335. if freqList is None:
  336. return ret1, ret2
  337. # 根据记录的频率列表,与stdFreq比对,返回结果。
  338. if freqList.__len__() < 1:
  339. return ret1.ret2
  340. freqList1,freqList2 = [],[]
  341. if freqList.__len__() >= 1:
  342. freqList1 = freqList[0]
  343. if freqList.__len__() >= 2:
  344. freqList2 = freqList[0]
  345. #计算声道1
  346. meetNum = 0
  347. for freq in freqList1:
  348. if abs(freq - stdFreq) < offset:
  349. meetNum += 1
  350. if meetNum/freqList1.__len__() == 1:
  351. ret1 = 1
  352. elif meetNum/freqList1.__len__() > 0:
  353. ret1 = 0
  354. elif meetNum/freqList1.__len__() == 0:
  355. ret1 = -1
  356. #计算声道2
  357. meetNum = 0
  358. for freq in freqList2:
  359. if abs(freq - stdFreq) < offset:
  360. meetNum += 1
  361. if meetNum/freqList2.__len__() == 1:
  362. ret2 = 1
  363. elif meetNum/freqList2.__len__() > 0:
  364. ret2 = 0
  365. elif meetNum/freqList2.__len__() == 0:
  366. ret2 = -1
  367. return ret1,ret2
  368. '''
  369. 设置录音设备:
  370. input_front_mic = Realtek High Definition
  371. input_back_mic = HD Webcam C270
  372. :param devName,传入自定义设备名字,例如:input_front_mic
  373. '''
  374. def setInputSoundDev(self, devName):
  375. self.audioChecker.setDeviceName(devName)
  376. if __name__ == "__main__":
  377. audioDevice = AudioDevice()
  378. audioDevice.setDevice(2)
  379. aIden = AudioIdentify()
  380. # aIden.setInputSoundDev("input_front_mic")
  381. # aIden.setInputSoundDev("input_back_mic")
  382. aIden.monitorSound(5)
  383. # print "cmpSoundMode:", aIden.cmpSoundMode([3000,6000,34000,40000],[3400,5000,30000,40000])
  384. # aIden.monitorSound(5)
  385. time.sleep(1)
  386. # sound1 = "D:/1.wav"
  387. # sound2 = "D:/2.wav"
  388. # list1=[]
  389. # expList1 = aIden.monitorSoundTH(10)
  390. # time.sleep(15)
  391. # aIden.recordAudioFileReturnPath(5, "wavetest_%s.wav" % str(1))
  392. # print "expList1:",expList1
  393. # print "soundPower:", aIden.getSoundPower()
  394. # print "fftsoundPower:", aIden.aAnalysis.getFFTPower()
  395. # for i in range(10):
  396. # filePath = aIden.recordAudioFileReturnPath(5,"wavetest_%s.wav"%str(i))
  397. # print "getSoundPower:", aIden.getSoundPower(LR=True)
  398. # print "filePath:", filePath
  399. # time.sleep(3)
  400. # print "getSoundPower:",aIden.getSoundPower(LR=True)
  401. # print "getTotalAVGPower:",aIden.aAnalysis.getTotalAVGPower(LR=True)
  402. # aIden.aAnalysis.analyWav("D:/5.wav")
  403. # print "getLRVariation:",aIden.getLRVariation()
  404. # print "getFFTPower:",aIden.aAnalysis.getFFTPower(LR=True)
  405. # print "aIden.isOK():", aIden.isOK(),list1
  406. # shutil.move(expList1[0]['audio'], sound1)
  407. # for i in range(10):
  408. # expList2 = aIden.monitorSound(1)
  409. # print "expList2:",i, expList2
  410. # shutil.move(expList2[0]['audio'], sound2)
  411. # waveFile = "sound/wav_balance_v15.wav"
  412. # print "getSoundPower:", aIden.getSoundPower(LR=True,audioFile=waveFile)