大家好, 我是痞子衡, 是正经搞技术的痞子. 今天痞子衡给大家介绍的是语音处理工具 pzh-py-speech 诞生之音频录播实现.
音频录播是 pzh-py-speech 的主要功能, pzh-py-speech 借助的是 Python 自带 wave 库以及第三方 PyAudio 库来实现的音频播放和录制功能, 今天痞子衡为大家介绍音频录播在 pzh-py-speech 中是如何实现的.
一, wave 简介
wave 是 python 标准库, 其可以实现 wav 音频文件的读写, 并且能解析 wav 音频的参数. pzh-py-speech 借助 wave 库来读写 wav 文件, 播放音频时借助 wave 库来读取 wav 文件并获取音频参数(通道, 采样宽度, 采样率), 录制音频时借助 wave 库来设置音频参数并保存成 wav 文件. 下面列举了 pzh-py-speech 所用到的全部 API:
wave 用法: https://docs.python.org/2/library/wave.html
- 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 简介
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 音频录播实现
3.1 播放实现
播放功能本身实现不算复杂, 但 pzh-py-speech 里实现的是播放按钮的五种状态 Start -> Play -> Pause -> Resume -> End 控制, 即播放中途实现了暂停和恢复, 因此代码要稍微复杂一些. 此处的重点是 playAudioCallback()函数里的 else 分支, 如果在暂停状态下, 必须还是要给 PyAudio 返回一段空数据:
- 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
3.2 录制实现
相比播放功能, 录制功能就简单了些, 因为录制按钮状态就两种 Start -> End, 暂不支持中断后继续录制. 这里的重点主要是音频三大参数 (采样宽度, 采样率, 通道数) 设置的支持:
- 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 诞生之音频录播实现痞子衡便介绍完毕了, 掌声在哪里~~~
参考文档
Python 解析 Wav 文件并绘制波形的方法
来源: https://www.cnblogs.com/henjay724/p/12242470.html