痞子衡嵌入式:语音处理工具pzh-speech诞生记(4)- 音频录播实现(PyAudio)



  大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是语音处理工具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.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项目的官方主页如下:

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

参考文档

  1. Python解析Wav文件并绘制波形的方法

原文地址:https://www.cnblogs.com/henjay724/p/12242470.html

时间: 2024-10-20 17:01:18

痞子衡嵌入式:语音处理工具pzh-speech诞生记(4)- 音频录播实现(PyAudio)的相关文章

痞子衡嵌入式:史上最强i.MX RT学习资源汇总(持续更新中...)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MX RT学习资源. 类别 资源 简介 官方汇总 i.MXRT产品主页 恩智浦官方i.MXRT产品主页,最权威的资料都在这里,参考手册/数据手册,官方EVK板设计文件,各种应用笔记,各种参考设计方案.培训视频.软件SDK开发包,官方IDE/CFG工具,第三方软件支持等应有尽有,如果这上面文档你都能全部仔细看一遍,软件都能下载用起来,不用怀疑,你就是资深专家了. 其中痞子衡特别推荐你把所有应用笔记都看一遍,这些笔记凝结了所有恩智浦

《痞子衡嵌入式半月刊》 第 2 期

痞子衡嵌入式半月刊: 第 2 期 这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期. 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿. 上期回顾 :<痞子衡嵌入式半月刊: 第 1 期> 唠两句 如果你第一时间阅读本期,此时应正是立春与雨水交节之时(2020年02月19日 12:56:53).雨水节气标示着降雨开始.雨量渐增,俗话说"

痞子衡嵌入式:测一测i.MXRT1170 Raw NAND启动时间(从POR到进App的Reset_Handler)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RT1170 Raw NAND启动时间. 关于i.MXRT1170这颗划时代的MCU,痞子衡去年10月在其刚发布的时候,专门写过一篇文章介绍过其特点(详见 <终于可以放开聊一聊i.MXRT1170这颗划时代MCU了>),眼看着其上市日期越来越近了,恩智浦软硬件技术支持团队也正在紧锣密鼓地开发SDK以及参考设计.因为官方首次在i.MXRT1170 EVK板上(Rev.B)放了一片旺宏的Raw NAND芯片,而i.MX

痞子衡嵌入式:语音处理工具pzh-speech诞生记(2)- 界面构建(wxFormBuilder3.8.0)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是语音处理工具pzh-py-speech诞生之界面构建. 之前痞子衡设计过一个串口调试助手pzh-py-com,也专门写过一篇关于其界面构建的文章 <串口调试工具pzh-com诞生记(2)- 界面构建>,今天咱们来构建pzh-py-speech的界面过程与pzh-py-com构建步骤类似,也是分四步:界面设计简图.界面设计wxPython组件图.在wxFormBuilder里创作.使用生成的Python代码.为了突出重点,痞子衡只讲

痞子衡嵌入式:超级好用的可视化PyQt GUI构建工具(Qt Designer)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是PyQt GUI构建工具Qt Designer. 痞子衡开博客至今已有好几年,一直以嵌入式开发相关主题的文章为主线,偶尔穿插一些其他技术或工具的介绍,前段时间因为要做一个跟恩智浦MCU启动相关的上位机工具 NXP-MCUBootUtility,网上搜索对比了几个Python下的GUI框架,最终选择了wxPython这个成熟稳定的GUI库,从而接触到wxFormBuilder这个配套wxPython使用的GUI构建工具.苦于网上关于该

痞子衡嵌入式:飞思卡尔i.MX RT系列微控制器启动篇(6)- Bootable image格式与加载(elftosb/.bd/.bin)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Bootable image格式与加载过程. 在i.MXRT启动系列第三篇文章 飞思卡尔i.MX RT系列微控制器启动篇(3)- Serial Downloader模式(sdphost, mfgtool) 里痞子衡在介绍使用sdphost引导启动Flashloader时使用过一个名叫ivt_flashloader.bin的image文件,其实这个image文件就是Bootable image的一种,

痞子衡嵌入式:飞思卡尔i.MX RT系列微控制器启动篇(2)- Boot配置(BOOT_CFG Pin/eFUSE)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Boot配置. 在上一篇文章 飞思卡尔i.MX RT系列微控制器启动篇(1)- Boot简介 里痞子衡为大家介绍了Boot基本原理以及i.MXRT Boot方式简介.今天痞子衡就来重点聊一聊i.MXRT Boot方式具体由哪些配置决定的. 无论是什么芯片里的BootROM,其最核心的功能无非两个:一.从存放Application的存储器中加载执行:二.通过支持的通信接口接收来自Host的Applic

痞子衡嵌入式:飞思卡尔i.MX RT系列微控制器启动篇(3)- Serial Downloader模式(sdphost/mfgtool)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Serial Downloader模式. 在上一篇文章 飞思卡尔i.MX RT系列微控制器启动篇(2)- Boot配置(BOOT Pin, eFUSE) 里痞子衡为大家介绍了i.MXRT Boot的行为配置,其中第一节里讲了Boot有三种行为模式:Serial Downloader.Boot From Fuses.Internal Boot,后两种是核心的加载启动行为模式,而Serial Downl

痞子衡嵌入式:飞思卡尔i.MX RT系列微控制器启动篇(4)- Flashloader初体验(blhost)

大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的Flashloader. 在上一篇文章 飞思卡尔i.MX RT系列微控制器启动篇(3)- Serial Downloader模式(sdphost, mfgtool) 里痞子衡为大家介绍了i.MXRT Boot的Serial Downloader模式,这种模式主要是用来引导启动Flashloader,那么Flashloader到底具有哪些功能?这是本篇文章痞子衡要为大家解惑的主题. 痞子衡在前面提过F