audioHandler.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import pyaudio
  2. import wave
  3. import os
  4. from collections import deque
  5. import numpy as np
  6. import struct
  7. class Listener:
  8. def __init__(self, dataTime = 1/20, agcTime = 10, input = False):
  9. self._dataTime = dataTime
  10. self._agcTime = agcTime
  11. self._input = input
  12. self.p = pyaudio.PyAudio()
  13. self.left = self.right = self.fft = []
  14. self._agcMaxima = [0]
  15. self._agcIndex = 0
  16. self._agcLen = 0
  17. self._beatCb = None
  18. self._sampleRate = 0
  19. self._hasNewData = False
  20. self.buffersize = None
  21. self._doFFT = False
  22. self._doAgcFFT = False
  23. def start(self, dev = None):
  24. self._device = dev or self.getDefaultOutputDeviceInfo()
  25. if self._device == None:
  26. print("no device found")
  27. return
  28. print("device name: {} channels: {} defaultSampleRate: {}".format(self._device["name"], self._device["channels"], self._device["defaultSampleRate"]))
  29. if self._sampleRate != self._device["defaultSampleRate"]:
  30. self._sampleRate = self._device["defaultSampleRate"]
  31. self.buffersize = int(self._sampleRate * self._dataTime)
  32. self.fft = self.right = self.left = np.ndarray((self.buffersize))
  33. self._agcLen = int(1 / self._dataTime * self._agcTime)
  34. self._agcMaxima = np.ndarray((self._agcLen))
  35. self._agcMaxima.fill(2**15 * 0.1)
  36. self._agcIndex = 0
  37. self._lastBeatTime = 0
  38. self.meanAmp = 2**15 * 0.1
  39. try:
  40. self._stream = self.openStream(self._device)
  41. except OSError:
  42. self._stream = None
  43. if not self._stream:
  44. print("stream open failed")
  45. return
  46. self._stream.start_stream()
  47. return self._stream.is_active()
  48. def stop(self):
  49. if not self._stream:# or not self._stream.is_active():
  50. return False
  51. self._stream.stop_stream()
  52. return True
  53. def setBeatCb(self, cb):
  54. self._beatCb = cb
  55. def getDefaultOutputDeviceInfo(self):
  56. #Set default to first in list or ask Windows
  57. try:
  58. self.p.terminate()
  59. self.p.__init__()
  60. if self._input:
  61. info = self.p.get_default_input_device_info()
  62. else:
  63. info = self.p.get_default_output_device_info()
  64. except IOError:
  65. info = None
  66. #Handle no devices available
  67. if info == None:
  68. print ("No device available.")
  69. return None
  70. if (self.p.get_host_api_info_by_index(info["hostApi"])["name"]).find("WASAPI") == -1:
  71. for i in range(0, self.p.get_device_count()):
  72. x = self.p.get_device_info_by_index(i)
  73. is_wasapi = (self.p.get_host_api_info_by_index(x["hostApi"])["name"]).find("WASAPI") != -1
  74. if x["name"].find(info["name"]) >= 0 and is_wasapi:
  75. info = x
  76. break
  77. #Handle no devices available
  78. if info == None:
  79. print ("Device doesn't support WASAPI")
  80. return None
  81. info["channels"] = info["maxInputChannels"] if (info["maxOutputChannels"] < info["maxInputChannels"]) else info["maxOutputChannels"]
  82. return info
  83. def openStream(self, dev):
  84. is_input = dev["maxInputChannels"] > 0
  85. is_wasapi = (self.p.get_host_api_info_by_index(dev["hostApi"])["name"]).find("WASAPI") != -1
  86. #print("is input: {} is wasapi: {}".format(is_input, is_wasapi))
  87. if not is_input and not is_wasapi:
  88. print ("Selection is output and does not support loopback mode.")
  89. return None
  90. if is_wasapi:
  91. stream = self.p.open(
  92. format = pyaudio.paInt16,
  93. channels = (dev["channels"] if dev["channels"] < 2 else 2),
  94. rate = int(self._sampleRate),
  95. input = True,
  96. frames_per_buffer = self.buffersize,
  97. input_device_index = dev["index"],
  98. stream_callback=self.streamCallback,
  99. as_loopback = False if is_input else is_wasapi)
  100. else:
  101. stream = self.p.open(
  102. format = pyaudio.paInt16,
  103. channels = (dev["channels"] if dev["channels"] < 2 else 2),
  104. rate = int(self._sampleRate),
  105. input = True,
  106. frames_per_buffer = self.buffersize,
  107. input_device_index = dev["index"],
  108. stream_callback=self.streamCallback)
  109. return stream
  110. def closeStream(self):
  111. if not self._stream:
  112. return False
  113. self._stream.close()
  114. return True
  115. def streamCallback(self, buf, frame_count, time_info, flag):
  116. self._buf = buf
  117. arr = np.array(struct.unpack("%dh" % (len(buf)/2), buf))
  118. mx = arr.max()
  119. self._agcIndex += 1
  120. if self._agcIndex >= self._agcLen:
  121. self._agcIndex = 0
  122. self._agcMaxima[self._agcIndex] = mx
  123. self.meanAmp = np.max(np.absolute(self._agcMaxima))
  124. if self.meanAmp > 2**15 * 0.02:
  125. amp = 1 / self.meanAmp
  126. else:
  127. amp = 1 / (2**15 * 0.02)
  128. if self._device["channels"] >= 2:
  129. self.left, self.right = arr[::2] * amp, arr[1::2] * amp
  130. if self._doFFT:
  131. self.fft = self.fftCalc((self.left+self.right)/2)
  132. else:
  133. self.left = self.right = arr * amp
  134. if self._doFFT:
  135. self.fft = self.fftCalc(self.left)
  136. self._hasNewData = True
  137. if self._doAgcFFT:
  138. self.agcFFT = np.fft.rfft(self._agcMaxima, self.beatnFFT) / self.beatnFFT
  139. if self._beatCb and mx * (time_info["current_time"] - self._lastBeatTime) > 0.5:
  140. self._lastBeatTime = time_info["current_time"]
  141. self._beatCb(self.fft)
  142. return (None, pyaudio.paContinue)
  143. def hasNewData(self):
  144. if not self._hasNewData:
  145. return False
  146. self._hasNewData = False
  147. return True
  148. def getSampleRate(self):
  149. return int(self._sampleRate)
  150. def getAgc(self):
  151. return self.meanAmp / 2**15
  152. def getVolume(self):
  153. if self._agcMaxima.sum() == 0:
  154. return 0
  155. return self._agcMaxima[self._agcIndex] / self.meanAmp
  156. def isActive(self):
  157. if not self._stream:
  158. return False
  159. return self._stream.is_active()
  160. def fftSetLimits(self, nFFT, fMin, fMax):
  161. self._doFFT = True
  162. self.nFFT = nFFT
  163. self.fftMin = int(fMin / self._sampleRate * nFFT)
  164. self.fftMax = int(fMax / self._sampleRate * nFFT)
  165. print("nFFT: {} \tfftMin: {} \tfftMax: {}".format(self.nFFT, self.fftMin, self.fftMax))
  166. def agcFftSetLimits(self, fMin, fMax):
  167. self._doAgcFFT = True
  168. self.beatnFFT = self._agcLen
  169. self.beatFftMin = int(fMin * self._dataTime * self.beatnFFT)
  170. self.beatFftMax = int(fMax * self._dataTime * self.beatnFFT)
  171. print("beat nFFT: {} \tfftMin: {} \tfftMax: {}".format(self.beatnFFT, self.beatFftMin, self.beatFftMax))
  172. def fftCalc(self, data):
  173. return abs(np.fft.rfft(data, self.nFFT)[self.fftMin:self.fftMax]) / self.nFFT
  174. def fftGroup(self, fft, limits):
  175. groups = []
  176. for freqs in zip(limits, limits[1:]):
  177. a = int(freqs[0] / self._sampleRate * self.nFFT)
  178. b = int(freqs[1] / self._sampleRate * self.nFFT)
  179. #groups.append(sum(fft[a:b]) / (b-a) if (b-a) > 0 else 0)
  180. if b != a:
  181. groups.append(max(fft[a:b]))
  182. else:
  183. groups.append(fft[a])
  184. return groups