#-*- coding: utf-8 -*- import os import wave from time import sleep import numpy as np import pyaudio import matplotlib.pyplot as plt SUCCESS = 0 FAIL = 1 audio = pyaudio.PyAudio() audio2 = "" stream2 = "" FORMAT = pyaudio.paInt16 stream = audio.open(format=FORMAT, channels=1, rate=16000, input=True, frames_per_buffer=256 ) """ 語音和噪聲的區別可以體現在他們的能量上,語音段的能量比噪聲段的能量大,如果環境噪聲和系統輸入的噪聲比較小, 只要計算輸入信號的短時能量就能夠把語音段和噪聲背景區分開,除此之外,用基於能量的算法來檢測濁音通常效果也是比較理想的, 因爲濁音的能量值比清音大得多,可以判斷濁音和清音之間過渡的時刻[3],但對清音來說,效果不是很好,因此還需要藉助短時過零率來表徵。 短時能量可以近似爲互補的情況,短時能量大的地方過零率小,短時能量小的地方過零率較大。 基於短時能量和過零率的檢測方法 儘管基於短時能量和過零率的檢測方法各有其優缺點,但是若將這兩種基本方法相結合起來使用也可以實現對語音信號可靠的端點檢測。 無聲段的短時能量爲零,清音段的短時能量又比濁音段的短時能量大, 而在過零率方面,理想的情況是無聲段的過零率爲零,濁音段的過零率比清音段的過零率要大的多, 假設有一段語音, 如果某部分短時能量和過零率都爲零或者爲很小的值,就可以認爲這部分爲無聲段, 如果該部分語音短時能量很大但是過零率很小,則認爲該部分語音爲濁音段, 如果該部分短時能量很小但是過零率很大,則認爲該部分語音爲清音段。 正如前面提到,語音信號具有短時性,因此在對語音信號進行分析時, ,需要將語音信號以30ms爲一段分爲若干幀來進行分析,則兩幀起始點之間的間隔爲10ms。 短時能量,無聲<濁音<清音 過零率,無聲<清音<濁音 """ """ 事蹟測試 說話時的過零率 在0-3之間 輕呼吸時 重呼吸呼吸時的過零率在2-7之間 說話時的短時能量 在940-12000之間 輕呼吸時 重呼吸15000-23000之間 """ # 需要添加錄音互斥功能能,某些功能開啓的時候錄音暫時關閉 def ZCR(curFrame): # 過零率 tmp1 = curFrame[:-1] tmp2 = curFrame[1:] sings = (tmp1 * tmp2 <= 0) diffs = (tmp1 - tmp2) > 0.02 zcr = np.sum(sings * diffs) return zcr def STE(curFrame): # 短時能量 amp = np.sum(np.abs(curFrame)) return amp class Vad(object): def __init__(self): # 初始短時能量高門限 self.amp1 = 940 # 初始短時能量低門限 self.amp2 = 120 # 初始短時過零率高門限 self.zcr1 = 30 # 初始短時過零率低門限 self.zcr2 = 2 # 允許最大靜音長度 self.maxsilence = 45 #允許換氣的最長時間 # 語音的最短長度 self.minlen = 40 # 過濾小音量 # 偏移值 self.offsets = 40 self.offsete = 40 # 能量最大值 self.max_en = 20000 # 初始狀態爲靜音 self.status = 0 self.count = 0 self.silence = 0 self.frame_len = 256 self.frame_inc = 128 self.cur_status = 0 self.frames = [] # 數據開始偏移 self.frames_start = [] self.frames_start_num = 0 # 數據結束偏移 self.frames_end = [] self.frames_end_num = 0 # 緩存數據 self.cache_frames = [] self.cache = "" # 最大緩存長度 self.cache_frames_num = 0 self.end_flag = False self.wait_flag = False self.on = True self.callback = None self.callback_res = [] self.callback_kwargs = {} self.frames = [] self.x = [] self.y = [] def check_ontime(self,num,cache_frame): # self.cache的值爲空 self.cache_frames的數據長度爲744 global audio2,stream2 wave_data = np.frombuffer(cache_frame, dtype=np.int16) # 這裏的值竟然是256 wave_data = wave_data * 1.0 / self.max_en # max_en 爲20000 data = wave_data[np.arange(0, self.frame_len)] # 取前frame_len個值 這個值爲256 # speech_data = self.cache_frames.pop(0) #刪除第一個元素,並把第一個元素給speech_data ,長度爲256 # 獲得音頻過零率 zcr = ZCR(data) # 獲得音頻的短時能量, 平方放大 amp = STE(data)**2 # 返回當前音頻數據狀態 res = self.speech_status(amp, zcr) self.cur_status = res if res ==2: #開始截取音頻 if not audio2: audio2 = pyaudio.PyAudio() stream2 = audio2.open(format=FORMAT, channels=1, rate=16000, input=True, frames_per_buffer=256 ) stream_data = stream2.read(256) wave_data = np.frombuffer(stream_data, dtype=np.int16) # print(num, wave_data, len(stream_data)) print(num, "正在說話ing...") self.frames.append(stream_data) if res ==3 and len(self.frames)>25: # print(len(self.frames)) wf = wave.open(str(num)+".wav", 'wb') wf.setnchannels(1) wf.setsampwidth(audio2.get_sample_size(FORMAT)) wf.setframerate(16000) wf.writeframes(b"".join(self.frames)) self.frames = [] stream2.stop_stream() stream2.close() audio2.terminate() audio2 = "" stream2 = "" wf.close() # if num <=100: # self.x.append(num) # self.y.append(res) # elif num > 100: # self.x.append(num) # self.y.append(res) # self.x.pop(0) # self.y.pop(0) # # plt.cla() # # plt.scatter(num, res) # plt.plot(self.x, self.y, 'r-', lw=1) # plt.pause(0.01) # plt.show() # print("idx={}\t過零率={}\t短時能量={}\tres={}".format(num,zcr,amp,res)) # 短時能量,無聲 < 濁音 < 清音 # 過零率,無聲 < 清音 < 濁音 """ 實際測試 說話時的過零率 在0-3之間 呼吸時的過零率在0-5之間 說話時的短時能量 在940-12000之間 15000-23000之間 """ def speech_status(self, amp, zcr): status = 0 # 0= 靜音, 1= 可能開始, 2=確定進入語音段 3語音結束 if self.cur_status in [0, 1]: #如果在靜音狀態或可能的語音狀態,則執行下面操作 # 確定進入語音段 if amp > self.amp1 or zcr > self.zcr1: #超過最大 短時能量門限了 status = 2 self.silence = 0 self.count += 1 # 可能處於語音段 能量處於濁音段,過零率在清音或濁音段 elif amp > self.amp2 or zcr > self.zcr2: status = 2 self.count += 1 # 靜音狀態 else: status = 0 self.count = 0 self.count = 0 # 2 = 語音段 elif self.cur_status == 2: # 保持在語音段 能量處於濁音段,過零率在清音或濁音段 if amp > self.amp2 or zcr > self.zcr2: self.count += 1 status = 2 # 語音將結束 else: # 靜音還不夠長,尚未結束 self.silence += 1 if self.silence < self.maxsilence: self.count += 1 status = 2 # 語音長度太短認爲是噪聲 elif self.count < self.minlen: status = 0 self.silence = 0 self.count = 0 # 語音結束 else: status = 3 self.silence = 0 self.count = 0 return status class FileParser(Vad): def __init__(self): self.block_size = 256 Vad.__init__(self) if __name__ == "__main__": # plt.ion() stream_test = FileParser() num = 0 while True: byte_obj = stream.read(256) stream_test.check_ontime(0,byte_obj) num = num+1