zoukankan      html  css  js  c++  java
  • 痞子衡嵌入式:语音处理工具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文件并绘制波形的方法

    欢迎订阅

    文章会同时发布到我的 博客园主页CSDN主页微信公众号 平台上。

    微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。

  • 相关阅读:
    TTVNC 2.0 发布了
    软件收费问题,目前没有限制, 全部免费
    为什么服务器端要确认才能连接通过
    关于transfer file功能的使用?
    同一台机器上测试会出现很多黑块
    TTVNC 设计理念
    TTVNC 2.2发布
    redis连接池 jedis2.9.0.jar+commonspool22.4.2.jar
    Calendar add 方法 和set方法
    jar包读取包内properties文件
  • 原文地址:https://www.cnblogs.com/henjay724/p/12242470.html
Copyright © 2011-2022 走看看