你們好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給你們介紹的是語音處理工具pzh-py-speech誕生之音頻錄播實現。html
音頻錄播是pzh-py-speech的主要功能,pzh-py-speech藉助的是Python自帶wave庫以及第三方PyAudio庫來實現的音頻播放和錄製功能,今天痞子衡爲你們介紹音頻錄播在pzh-py-speech中是如何實現的。python
wave是python標準庫,其能夠實現wav音頻文件的讀寫,而且能解析wav音頻的參數。pzh-py-speech藉助wave庫來讀寫wav文件,播放音頻時藉助wave庫來讀取wav文件並獲取音頻參數(通道,採樣寬度,採樣率),錄製音頻時藉助wave庫來設置音頻參數並保存成wav文件。下面列舉了pzh-py-speech所用到的所有API:app
wave.open() # wav音頻讀API Wave_read.getnchannels() # 獲取音頻通道數 Wave_read.getsampwidth() # 獲取音頻採樣寬度 Wave_read.getframerate() # 獲取音頻採樣率 Wave_read.getnframes() # 獲取音頻總幀數 Wave_read.readframes(n) # 讀取音頻幀數據 Wave_read.tell() # 獲取已讀取的音頻幀數 Wave_read.close() # wav音頻寫API Wave_write.setnchannels(n) # 設置音頻通道數 Wave_write.setsampwidth(n) # 設置音頻採樣寬度 Wave_write.setframerate(n) # 設置音頻採樣率 Wave_write.writeframes(data) # 寫入音頻幀數據 Wave_write.close()
PyAudio是開源跨平臺音頻庫PortAudio的python封裝,PyAudio庫的維護者是Hubert Pham,該庫從2006年開始推出,一直持續更新至今,pzh-py-speech使用的是PyAudio 0.2.11。
pzh-py-speech藉助PyAudio庫來實現音頻數據流控制(包括從PC麥克風獲取音頻流,將音頻流輸出給PC揚聲器),若是說wave庫實現的是對wav文件的單純操做,那麼PyAudio庫則實現的是音頻相關硬件設備的交互。
PyAudio項目的官方主頁以下:函數
- PortAudio官方主頁: http://www.portaudio.com/
- PyAudio官方主頁: http://people.csail.mit.edu/hubert/pyaudio/
- PyAudio安裝方法: https://pypi.org/project/PyAudio/
PyAudio對音頻流的控制有兩種,一種是阻塞式的,另外一種是非阻塞式的(callback),前者通常用於肯定的音頻控制(好比單純播放一個本地音頻文件,而且中途不會有暫停/繼續等操做),後者通常用於靈活的音頻控制(好比錄製一段音頻,可是要等待一個事件響應纔會結束)。pzh-py-speech用的是後者。下面是兩種方式的音頻播放使用示例:工具
import pyaudio import wave CHUNK = 1024 wf = wave.open(「test.wav」, 'rb') p = pyaudio.PyAudio() ########################################################## # 此爲阻塞式,循環讀取1024個byte音頻數據去播放,直到test.wav文件數據被所有讀出 stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True) data = wf.readframes(CHUNK) while data != '': stream.write(data) data = wf.readframes(CHUNK) ########################################################## # 此爲非阻塞式的(callback),系統會自動讀取test.wav文件裏的音頻幀,直到播放完畢 def callback(in_data, frame_count, time_info, status): data = wf.readframes(frame_count) return (data, pyaudio.paContinue) stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), channels=wf.getnchannels(), rate=wf.getframerate(), output=True, stream_callback=callback) stream.start_stream() while stream.is_active(): time.sleep(0.1) ########################################################## stream.stop_stream() stream.close() p.terminate()
播放功能自己實現不算複雜,但pzh-py-speech裏實現的是播放按鈕的五種狀態Start -> Play -> Pause -> Resume -> End控制,即播放中途實現了暫停和恢復,所以代碼要稍微複雜一些。此處的重點是playAudioCallback()函數裏的else分支,若是在暫停狀態下,必須仍是要給PyAudio返回一段空數據:spa
import wave import pyaudio AUDIO_PLAY_STATE_START = 0 AUDIO_PLAY_STATE_PLAY = 1 AUDIO_PLAY_STATE_PAUSE = 2 AUDIO_PLAY_STATE_RESUME = 3 AUDIO_PLAY_STATE_END = 4 class mainWin(win.speech_win): def __init__(self, parent): # ... # Start -> Play -> Pause -> Resume -> End self.playState = AUDIO_PLAY_STATE_START def viewAudio( self, event ): self.wavPath = self.m_genericDirCtrl_audioDir.GetFilePath() if self.playState != AUDIO_PLAY_STATE_START: self.playState = AUDIO_PLAY_STATE_END self.m_button_play.SetLabel('Play Start') def playAudioCallback(self, in_data, frame_count, time_info, status): if self.playState == AUDIO_PLAY_STATE_PLAY or self.playState == AUDIO_PLAY_STATE_RESUME: data = self.wavFile.readframes(frame_count) if self.wavFile.getnframes() == self.wavFile.tell(): status = pyaudio.paComplete self.playState = AUDIO_PLAY_STATE_END self.m_button_play.SetLabel('Play Start') else: status = pyaudio.paContinue return (data, status) else: # Note!!!: data = numpy.zeros(frame_count*self.wavFile.getnchannels()).tostring() return (data, pyaudio.paContinue) def playAudio( self, event ): if os.path.isfile(self.wavPath): if self.playState == AUDIO_PLAY_STATE_END: self.playState = AUDIO_PLAY_STATE_START self.wavStream.stop_stream() self.wavStream.close() self.wavPyaudio.terminate() self.wavFile.close() if self.playState == AUDIO_PLAY_STATE_START: self.playState = AUDIO_PLAY_STATE_PLAY self.m_button_play.SetLabel('Play Pause') self.wavFile = wave.open(self.wavPath, "rb") self.wavPyaudio = pyaudio.PyAudio() self.wavStream = self.wavPyaudio.open(format=self.wavPyaudio.get_format_from_width(self.wavFile.getsampwidth()), channels=self.wavFile.getnchannels(), rate=self.wavFile.getframerate(), output=True, stream_callback=self.playAudioCallback) self.wavStream.start_stream() elif self.playState == AUDIO_PLAY_STATE_PLAY or self.playState == AUDIO_PLAY_STATE_RESUME: self.playState = AUDIO_PLAY_STATE_PAUSE self.m_button_play.SetLabel('Play Resume') elif self.playState == AUDIO_PLAY_STATE_PAUSE: self.playState = AUDIO_PLAY_STATE_RESUME self.m_button_play.SetLabel('Play Pause') else: pass
相比播放功能,錄製功能就簡單了些,由於錄製按鈕狀態就兩種Start -> End,暫不支持中斷後繼續錄製。這裏的重點主要是音頻三大參數(採樣寬度,採樣率,通道數)設置的支持:code
import wave import pyaudio class mainWin(win.speech_win): def recordAudioCallback(self, in_data, frame_count, time_info, status): if not self.isRecording: status = pyaudio.paComplete else: self.wavFrames.append(in_data) status = pyaudio.paContinue return (in_data, status) def recordAudio( self, event ): if not self.isRecording: self.isRecording = True self.m_button_record.SetLabel('Record Stop') # Get the wave parameter from user settings fileName = self.m_textCtrl_recFileName.GetLineText(0) if fileName == '': fileName = 'rec_untitled1.wav' self.wavPath = os.path.join(os.path.dirname(os.path.abspath(os.path.dirname(__file__))), 'conv', 'rec', fileName) self.wavSampRate = int(self.m_choice_sampRate.GetString(self.m_choice_sampRate.GetSelection())) channels = self.m_choice_channels.GetString(self.m_choice_channels.GetSelection()) if channels == 'Mono': self.wavChannels = 1 else: #if channels == 'Stereo': self.wavChannels = 2 bitDepth = int(self.m_choice_bitDepth.GetString(self.m_choice_bitDepth.GetSelection())) if bitDepth == 8: self.wavBitFormat = pyaudio.paInt8 elif bitDepth == 24: self.wavBitFormat = pyaudio.paInt24 elif bitDepth == 32: self.wavBitFormat = pyaudio.paFloat32 else: self.wavBitFormat = pyaudio.paInt16 # Record audio according to wave parameters self.wavFrames = [] self.wavPyaudio = pyaudio.PyAudio() self.wavStream = self.wavPyaudio.open(format=self.wavBitFormat, channels=self.wavChannels, rate=self.wavSampRate, input=True, frames_per_buffer=AUDIO_CHUNK_SIZE, stream_callback=self.recordAudioCallback) self.wavStream.start_stream() else: self.isRecording = False self.m_button_record.SetLabel('Record Start') self.wavStream.stop_stream() self.wavStream.close() self.wavPyaudio.terminate() # Save the wave data into file wavFile = wave.open(self.wavPath, 'wb') wavFile.setnchannels(self.wavChannels) wavFile.setsampwidth(self.wavPyaudio.get_sample_size(self.wavBitFormat)) wavFile.setframerate(self.wavSampRate) wavFile.writeframes(b''.join(self.wavFrames)) wavFile.close()
至此,語音處理工具pzh-py-speech誕生之音頻錄播實現痞子衡便介紹完畢了,掌聲在哪裏~~~orm