加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

400行Python代码实现文语处理助手(4) - 音频录播

2020/02/10
158
阅读需 25 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

音频录播是 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 诞生之音频录播实现痞子衡便介绍完毕了,掌声在哪里~~~

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
IS61WV102416BLL-10TLI 1 Integrated Silicon Solution Inc Standard SRAM, 1MX16, 10ns, CMOS, PDSO48, 12 X 20 MM, LEAD FREE, TSOP1-48

ECAD模型

下载ECAD模型
$23.94 查看
HFBR-24E2Z 1 Foxconn Receiver, 792nm Min, 865nm Max, SC Connector, Through Hole Mount, ROHS COMPLIANT, PACKAGE-8
$30.25 查看
CY62177EV30LL-55ZXIT 1 Cypress Semiconductor Standard SRAM, 2MX16, 55ns, CMOS, PDSO48, 12 X 18.40 MM, 1 MM HEIGHT, LEAD FREE, MO-142, TSOP1-48
暂无数据 查看

相关推荐

电子产业图谱

硕士毕业于苏州大学电子信息学院,目前就职于恩智浦(NXP)半导体MCU系统部门,担任嵌入式系统应用工程师。痞子衡会定期分享嵌入式相关文章