在線免費(fèi)貨源網(wǎng)站入口站長(zhǎng)之家下載
語音識(shí)別技術(shù)的快速發(fā)展為實(shí)現(xiàn)更多智能化應(yīng)用提供了無限可能。本文旨在介紹一個(gè)基于Python實(shí)現(xiàn)的簡(jiǎn)易音頻錄制與語音識(shí)別應(yīng)用。文章簡(jiǎn)要介紹相關(guān)技術(shù)的應(yīng)用,重點(diǎn)放在音頻錄制方面,而語音識(shí)別則關(guān)注于調(diào)用相關(guān)的語音識(shí)別庫。本文將首先概述一些音頻基礎(chǔ)概念,然后詳細(xì)講解如何利用PyAudio庫和SpeechRecognition庫實(shí)現(xiàn)音頻錄制功能。最后,構(gòu)建一個(gè)簡(jiǎn)單的語音識(shí)別示例應(yīng)用,該應(yīng)用程序可以實(shí)時(shí)監(jiān)聽音頻的開始和結(jié)束,并將錄制的音頻數(shù)據(jù)傳輸至Whisper語音識(shí)別庫進(jìn)行語音識(shí)別,最終將識(shí)別結(jié)果輸出到基于PyQt5搭建的簡(jiǎn)易頁面中。
本文所有代碼見:Python-Study-Notes
文章目錄
- 0 音頻基礎(chǔ)概念
- 1 PyAudio
- 1.1 PyAudio介紹與安裝
- 1.2 音頻錄制與播放
- 1.2.1 音頻播放
- 1.2.2 音頻錄制
- 1.2.3 全雙工音頻錄制與播放
- 1.3 回調(diào)函數(shù)的使用
- 1.4 設(shè)備管理
- 2 SpeechRecognition
- 2.1 SpeechRecognition介紹與安裝
- 2.2 示例代碼
- 3 語音識(shí)別示例應(yīng)用
- 4 參考
0 音頻基礎(chǔ)概念
隨著深度學(xué)習(xí)技術(shù)的迅猛發(fā)展,端到端語音識(shí)別已廣泛應(yīng)用。然而,音頻相關(guān)的最基礎(chǔ)概念如采樣頻率、采樣位數(shù),我們?nèi)孕栌幸欢私?。聲音是由物體振動(dòng)引起的機(jī)械波,而音頻是聲音的電子表示。PCM (Pulse Code Modulation)編碼將一種常見將模擬音頻信號(hào)轉(zhuǎn)換為數(shù)字形式的方法。在此過程,音頻采樣是指在一段時(shí)間內(nèi)通過固定間隔采集聲音的振幅值以將連續(xù)的聲音模擬信號(hào)轉(zhuǎn)換為離散的數(shù)字?jǐn)?shù)據(jù)。采樣頻率表示每秒鐘采集的樣本數(shù),而采樣位數(shù)則表示每個(gè)樣本的量化級(jí)別即聲音的精細(xì)度和動(dòng)態(tài)范圍。關(guān)于音頻詳細(xì)概念介紹見:數(shù)字音頻基礎(chǔ)----------從PCM說起。
采樣頻率
音頻信號(hào)通常是連續(xù)的模擬波形,為了存儲(chǔ)它們,需要將其離散化。這通過采樣來實(shí)現(xiàn),即在固定時(shí)間間隔內(nèi)測(cè)量聲音信號(hào)的幅度。采樣的過程就是抽取模擬信號(hào)各點(diǎn)的頻率值。采樣率越高即1秒內(nèi)抽取的數(shù)據(jù)點(diǎn)越多,音頻音質(zhì)就越好,但同時(shí)也增加了存儲(chǔ)和處理成本。奈奎斯特-香農(nóng)(Nyquist–Shannon)采樣定理強(qiáng)調(diào)采樣頻率必須高于信號(hào)最大頻率的兩倍,以確保從采樣值中完全恢復(fù)原始模擬信號(hào)。在音頻信號(hào)采樣領(lǐng)域,常使用兩個(gè)主要的采樣頻率:16kHz和44.1kHz。
舉例來說,16kHz表示每秒采樣16000次,而人類言語聲音頻率范圍在200Hz到8kHz,16kHz的采樣頻率已足夠捕捉人類語音頻率特征,同時(shí)減輕了音頻數(shù)據(jù)存儲(chǔ)和處理的負(fù)擔(dān)。因此常用語音采樣頻率為16kHz。人耳可感知20Hz到20kHz的聲音,為了呈現(xiàn)高質(zhì)量音頻,通常選擇44.1kHz采樣頻率以覆蓋人耳可聽聲音的上限。
采樣位數(shù)和聲道
采樣后的信號(hào)是連續(xù)的模擬值,為了將其轉(zhuǎn)換為數(shù)字形式,需要對(duì)信號(hào)進(jìn)行量化。量化是將連續(xù)的模擬值映射到離散的數(shù)字值,通常使用固定采樣位數(shù)(例如16位或24位)來表示樣本的幅度范圍。例如,使用16位(16bit),也就是雙字節(jié)的二進(jìn)制信號(hào)表示音頻采樣,而16位的取值范圍為-32768到32767,共有65536個(gè)可能的取值。因此,最終模擬的音頻信號(hào)在幅度上被分成了65536個(gè)數(shù)值等級(jí)。較高的采樣位數(shù)能夠表示更大的聲音幅度范圍,并保留更多的細(xì)節(jié)信息。常用的位深度包括8位、16位和24位,其中8位是最低要求,16位可以滿足一般應(yīng)用的需求,而24位則適用于專業(yè)音頻工作。
聲道是指音頻信號(hào)在播放系統(tǒng)或錄音系統(tǒng)中的傳輸通道。一個(gè)聲道通常對(duì)應(yīng)于一個(gè)單獨(dú)的音頻信號(hào)源或信號(hào)流,并負(fù)責(zé)傳輸該信號(hào)到揚(yáng)聲器或錄音設(shè)備。在立體聲系統(tǒng)中,通常有兩個(gè)聲道,分別是左聲道和右聲道,用來分別處理來自音頻源的左右聲音信號(hào),以實(shí)現(xiàn)空間立體聲效果。聲道的概念也可擴(kuò)展到多聲道系統(tǒng),如5.1聲道、7.1聲道等,它們可以支持更多的音頻源和更豐富的音效體驗(yàn),比如環(huán)繞音效。
常用音頻編碼格式
PCM編碼所獲得的音頻數(shù)據(jù)是最為原始的,為了進(jìn)行存儲(chǔ)和網(wǎng)絡(luò)傳輸需要對(duì)其進(jìn)行二次編碼。這些二次音頻編碼格式都是在PCM編碼基礎(chǔ)上再次編碼和壓縮的,按照壓縮方式又分為無損壓縮和有損壓縮。無損壓縮是指相對(duì)于PCM編碼完整地保留音頻數(shù)據(jù)的音質(zhì)。然而,無損壓縮的音頻文件通常比有損壓縮的音頻文件稍大。有損壓縮在編碼過程中,為了減小文件大小,犧牲了部分音頻數(shù)據(jù)的信息和音質(zhì)。
無損壓縮常見的音頻編碼格式有:WAV/WAEV(Waveform Audio File Format),FLAC(Free Lossless Audio Codec),AIFF(Audio Interchange File Format)等。有損壓縮常見的音頻編碼格式有:MP3(MPEG Audio Layer III),AAC(Advanced Audio Coding),WMA(Windows Media Audio)等。
在獲得編碼后的音頻數(shù)據(jù)后,需要使用合適的文件格式來保存編碼數(shù)據(jù)。一種音頻編碼可能對(duì)應(yīng)一種文件格式,也可能對(duì)應(yīng)多種文件格式,一般情況下是一種。例如WAV編碼數(shù)據(jù)對(duì)應(yīng)于.wav文件格式,MP3編碼數(shù)據(jù)對(duì)應(yīng)于.mp3文件格式。PCM編碼數(shù)據(jù)對(duì)應(yīng)于.raw或者.pcm文件格式,AAC編碼數(shù)據(jù)對(duì)應(yīng)于.acc或者.mp4文件格式等。
音頻與視頻概念對(duì)比
概念 | 音頻 | 視頻 |
---|---|---|
維度 | 通過聲波傳遞的聲音信息,是一維的 | 通過圖像序列傳遞的運(yùn)動(dòng)圖像信息,是二維的 |
核心特征 | 包括音調(diào)、音量、節(jié)奏等,由頻率和振幅表現(xiàn) | 包括畫面內(nèi)容、顏色等,由像素和色彩表現(xiàn) |
信號(hào)頻率 | 以采樣率表示,人類對(duì)聲音的感知更為敏銳,因而音頻采樣率遠(yuǎn)遠(yuǎn)大于視頻幀率 | 以幀率表示,視頻是通過多張靜止圖像以一定的速度播放來模擬流暢的動(dòng)畫 |
采樣精度 | 用于表示聲音的幅度值,常見16bit | 用于表示圖像的顏色和亮度值,常見為8bit(256色) |
處理技術(shù) | 均衡、壓縮、降噪等 | 剪輯、特效、編解碼等 |
通道 | 以聲道表示,如單聲道和雙聲道 | 以顏色通道表示,如GRAY、RGB、RGBA |
存儲(chǔ) | 便于傳輸和存儲(chǔ),占用的空間較小 | 需要更大的存儲(chǔ)空間和帶寬來傳輸和保存 |
下面的代碼展示了讀取音頻內(nèi)容為123456789的wav文件并繪制出音頻數(shù)據(jù)的波形圖。
# 導(dǎo)入用于繪圖的matplotlib庫
from matplotlib import pyplot as plt
# 導(dǎo)入用于讀取音頻文件的soundfile庫
# pip install soundfile
import soundfile as sf# 從demo.wav文件中讀取音頻數(shù)據(jù)和采樣率,data為numpy數(shù)組
data, samplerate = sf.read('asr_example_hotword.wav', dtype='float32')# 保存音頻
sf.write("output.wav", data=data, samplerate=samplerate)# 打印音頻數(shù)據(jù)的形狀和打印采樣率
# data為一個(gè)numpy數(shù)組,samplerate為一個(gè)整數(shù)
print('data shape: {}'.format(data.shape))
print('sample rate: {}'.format(samplerate))# 繪制音頻波形
plt.figure()
plt.plot(data)
plt.show()
代碼運(yùn)行結(jié)果如下,其中表示音頻數(shù)據(jù)的樣本點(diǎn)索引,即音頻的時(shí)間軸。每個(gè)樣本點(diǎn)都對(duì)應(yīng)音頻數(shù)據(jù)的每一幀,從左到右依次遞增。縱軸表示音頻信號(hào)在每個(gè)時(shí)間點(diǎn)的歸一化后的音頻強(qiáng)度。所讀取的數(shù)據(jù)以float32表示格式,數(shù)值采樣值范圍為-32678~32678,soundfile庫會(huì)除以32678(2^16/2),以歸一化到[-1, 1]區(qū)間內(nèi)。
1 PyAudio
1.1 PyAudio介紹與安裝
PyAudio是一個(gè)用于處理音頻輸入和輸出的Python庫,其主要變量和接口的實(shí)現(xiàn)依賴于C語言版本的PortAudio。PyAudio提供從麥克風(fēng)或其他輸入設(shè)備錄制音頻、保存音頻文件、實(shí)時(shí)處理音頻數(shù)據(jù)以及播放音頻文件或?qū)崟r(shí)音頻流等功能。此外,PyAudio也允許通過設(shè)置采樣率、位深度、聲道數(shù)等參數(shù)以及支持回調(diào)函數(shù)和事件驅(qū)動(dòng)機(jī)制來滿足不同應(yīng)用需求。PyAudio官方網(wǎng)站見:PyAudio。PyAudio的安裝需要Python3.7及以上環(huán)境。
Windows下PyAudio安裝命令如下:
python -m pip install pyaudio
Linux下PyAudio按照命令如下:
sudo apt-get install python3-pyaudio
python -m pip install pyaudio
本文所用PyAudio版本為0.2.13。
1.2 音頻錄制與播放
1.2.1 音頻播放
以下代碼展示了基于PyAudio播放本地音頻文件。
# wave為python處理音頻標(biāo)準(zhǔn)庫
import wave
import pyaudio# 定義每次從音頻文件中讀取的音頻采樣數(shù)據(jù)點(diǎn)的數(shù)量
CHUNK = 1024filepath = "demo.wav"
# 以音頻二進(jìn)制流形式打開音頻文件
with wave.open(filepath, 'rb') as wf:# 實(shí)例化PyAudio并初始化PortAudio系統(tǒng)資源p = pyaudio.PyAudio()# 打開音頻流# format: 指定音頻流的采樣格式。其中wf.getsampwidth()用于獲取音頻文件的采樣位數(shù)(sample width)。# 采樣位數(shù)指的是每個(gè)采樣點(diǎn)占用的字節(jié)數(shù)。通常情況下,采樣位數(shù)可以是1字節(jié)(8位)、2字節(jié)(16位)等。# channels:指定音頻流的聲道數(shù)。聲道數(shù)可以是單聲道(1)或立體聲(2)# rate:指定音頻流的采樣率。采樣率表示每秒鐘音頻采樣的次數(shù),常見的采樣率有44100Hz或16000Hz# output:是否播放音頻stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),channels=wf.getnchannels(),rate=wf.getframerate(),output=True)# 從音頻文件播放樣本數(shù)據(jù)while True:# data為二進(jìn)制數(shù)據(jù)data = wf.readframes(CHUNK)# len(data)表示讀取數(shù)據(jù)的長(zhǎng)度# 在此len(data)應(yīng)該等于采樣點(diǎn)占用的字節(jié)數(shù)wf.getsampwidth()乘以CHUNKif len(data):stream.write(data)else:break# 或者使用python3.8引入的海象運(yùn)算符# while len(data := wf.readframes(CHUNK)):# stream.write(data)# 關(guān)閉音頻流stream.close()# 釋放PortAudio系統(tǒng)資源p.terminate()
1.2.2 音頻錄制
以下代碼展示了基于PyAudio調(diào)用麥克風(fēng)錄音,并將錄音結(jié)果保存為本地文件。
import wave
import pyaudio# 設(shè)置音頻流的數(shù)據(jù)塊大小
CHUNK = 1024
# 設(shè)置音頻流的格式為16位整型,也就是2字節(jié)
FORMAT = pyaudio.paInt16
# 設(shè)置音頻流的通道數(shù)為1
CHANNELS = 1
# 設(shè)置音頻流的采樣率為16KHz
RATE = 16000
# 設(shè)置錄制時(shí)長(zhǎng)為5秒
RECORD_SECONDS = 5outfilepath = 'output.wav'
with wave.open(outfilepath, 'wb') as wf:p = pyaudio.PyAudio()# 設(shè)置wave文件的通道數(shù)wf.setnchannels(CHANNELS)# 設(shè)置wave文件的采樣位數(shù) wf.setsampwidth(p.get_sample_size(FORMAT))# 設(shè)置wave文件的采樣率wf.setframerate(RATE)# 打開音頻流,input表示錄音stream = p.open(format=FORMAT, channels=CHANNELS, rate=RATE, input=True)print('Recording...')# 循環(huán)寫入音頻數(shù)據(jù)for _ in range(0, RATE // CHUNK * RECORD_SECONDS):wf.writeframes(stream.read(CHUNK))print('Done') stream.close()p.terminate()
1.2.3 全雙工音頻錄制與播放
全雙工系統(tǒng)(full-duplex)可以同時(shí)進(jìn)行雙向數(shù)據(jù)傳輸,而半雙工系統(tǒng)(half-duplex)只能在同一時(shí)間內(nèi)進(jìn)行單向數(shù)據(jù)傳輸。在半雙工系統(tǒng)中,一臺(tái)設(shè)備傳輸數(shù)據(jù)時(shí),另一臺(tái)設(shè)備必須等待傳輸完成后才能進(jìn)行數(shù)據(jù)處理。以下代碼展示了全雙工(full-duplex)音頻錄制與播放,即同時(shí)進(jìn)行音頻錄制和播放,而不需要等待一個(gè)操作完成后再進(jìn)行另一個(gè)操作。
import pyaudioRECORD_SECONDS = 5
CHUNK = 1024
RATE = 16000p = pyaudio.PyAudio()
# frames_per_buffer設(shè)置音頻每個(gè)緩沖區(qū)的大小
stream = p.open(format=p.get_format_from_width(2),channels=1,rate=RATE,input=True,output=True,frames_per_buffer=CHUNK)print('recording')
for i in range(0, int(RATE / CHUNK * RECORD_SECONDS)):# read讀取音頻然后writer播放音頻stream.write(stream.read(CHUNK))
print('done')stream.close()
p.terminate()
1.3 回調(diào)函數(shù)的使用
在前面的代碼中,PyAudio執(zhí)行音頻播放或錄制是以阻塞主線程的方式進(jìn)行的,這意味著代碼無法同時(shí)處理其他任務(wù)。為了解決這一問題,PyAudio提供了回調(diào)函數(shù),使得程序在進(jìn)行音頻輸入和輸出時(shí),能夠以非阻塞的方式進(jìn)行操作,即處理音頻流的同時(shí)處理其他任務(wù)。PyAudio回調(diào)函數(shù)是在單獨(dú)的線程中執(zhí)行的,當(dāng)音頻流數(shù)據(jù)可用時(shí),回調(diào)函數(shù)會(huì)被自動(dòng)調(diào)用并以立即對(duì)音頻數(shù)據(jù)進(jìn)行處理。PyAudio回調(diào)函數(shù)具有固定的參數(shù)接口,函數(shù)介紹如下:
def callback(in_data, # 錄制的音頻數(shù)據(jù)的字節(jié)流,如果沒有錄音則為Noneframe_count, # 每個(gè)緩沖區(qū)中的幀數(shù),本次讀取的數(shù)據(jù)量time_info, # 有關(guān)音頻流時(shí)間信息的字典status_flags) # 音頻流狀態(tài)的標(biāo)志位
以下代碼展示了以回調(diào)函數(shù)的形式播放音頻。
import wave
import time
import pyaudiofilepath = "demo.wav"
with wave.open(filepath, 'rb') as wf:# 當(dāng)音頻流數(shù)據(jù)可用時(shí),回調(diào)函數(shù)會(huì)被自動(dòng)調(diào)用def callback(in_data, frame_count, time_info, status):# 讀取了指定數(shù)量的音頻幀數(shù)據(jù)data = wf.readframes(frame_count)# pyaudio.paContinue為常量,表示繼續(xù)進(jìn)行音頻流的處理# 根據(jù)需要更改為pyaudio.paAbort或pyaudio.paComplete等常量來控制處理流程的中斷和結(jié)束return (data, pyaudio.paContinue)p = pyaudio.PyAudio()# stream_callback設(shè)置回調(diào)函數(shù)stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),channels=wf.getnchannels(),rate=wf.getframerate(),output=True,stream_callback=callback)# 判斷音頻流是否處于活動(dòng)狀態(tài)while stream.is_active():time.sleep(0.1)stream.close()p.terminate()
以下代碼展示了如何運(yùn)用回調(diào)函數(shù)實(shí)現(xiàn)音頻錄制與播放的全雙工模式。在超時(shí)情況下,通過調(diào)用stream.close()來關(guān)閉音頻流并釋放相關(guān)資源。一旦音頻流被關(guān)閉,將無法再傳輸音頻數(shù)據(jù)。若想實(shí)現(xiàn)錄音過程中暫停一段時(shí)間后再繼續(xù)錄音,可使用stream.stop_stream()來暫停音頻流的數(shù)據(jù)傳輸,即暫時(shí)停止音頻的讀取和寫入,但仍保持流對(duì)象處于打開狀態(tài)。隨后,可通過調(diào)用stream.start_stream()來重新啟動(dòng)音頻流的數(shù)據(jù)傳輸。
import time
import pyaudio# 錄音時(shí)長(zhǎng)
DURATION = 5 def callback(in_data, frame_count, time_info, status):# in_data為麥克風(fēng)輸入的音頻流return (in_data, pyaudio.paContinue)p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(2),channels=1,rate=16000,input=True,output=True,stream_callback=callback)start = time.time()
# 當(dāng)音頻流處于活動(dòng)狀態(tài)且錄音時(shí)間未達(dá)到設(shè)定時(shí)長(zhǎng)時(shí)
while stream.is_active() and (time.time() - start) < DURATION:time.sleep(0.1)# 超過時(shí)長(zhǎng)關(guān)閉音頻流
stream.close()
p.terminate()
1.4 設(shè)備管理
PyAudio提供了host Api和device Api來獲取音頻設(shè)備,但host Api和device Api代表了不同的層級(jí)和功能。具體如下:
- host Api:是對(duì)底層音頻系統(tǒng)的抽象,表示系統(tǒng)上可用的音頻接口,提供了與底層音頻設(shè)備交互的功能。每個(gè)host Api都有自己的特點(diǎn)和支持的功能集,如使用的數(shù)據(jù)格式、采樣率等。常見的host Api包括ALSA、PulseAudio、CoreAudio等。
- device Api:是指具體的音頻輸入或輸出設(shè)備,如麥克風(fēng)、揚(yáng)聲器或耳機(jī)等。每個(gè)音頻設(shè)備都屬于一個(gè)特定的音頻host Api,并具有不同的參數(shù)配置,例如采樣率、緩沖區(qū)大小等。
本文主要對(duì)更為常用的device Api進(jìn)行介紹,PyAudio中關(guān)于device Api的函數(shù)有如下:
-
get_device_info_by_index(index)
:
通過整數(shù)型索引獲取指定設(shè)備的詳細(xì)信息。該函數(shù)返回一個(gè)包含設(shè)備信息的字典,包括設(shè)備名稱、輸入/輸出通道數(shù)、支持的采樣率范圍等。 -
get_default_input_device_info()
:
獲取默認(rèn)輸入設(shè)備的詳細(xì)信息。該函數(shù)返回一個(gè)包含設(shè)備信息的字典。 -
get_default_output_device_info()
:
獲取默認(rèn)輸出設(shè)備的詳細(xì)信息。該函數(shù)返回一個(gè)包含設(shè)備信息的字典。 -
get_device_count()
:
獲取計(jì)算機(jī)上可用音頻設(shè)備的數(shù)量,這些設(shè)備可以是麥克風(fēng)、揚(yáng)聲器、音頻接口等。
其中默認(rèn)設(shè)備為當(dāng)前操作系統(tǒng)的音頻默認(rèn)設(shè)備,可以通過操作系統(tǒng)音頻控制頁面更改默認(rèn)音頻輸入輸出設(shè)備。下面代碼展示了這些函數(shù)的使用:
import pyaudio# 獲取指定設(shè)備的詳細(xì)信息
def get_device_info_by_index(index):p = pyaudio.PyAudio()device_info = p.get_device_info_by_index(index)p.terminate()return device_info# 獲取默認(rèn)輸入設(shè)備的詳細(xì)信息
def get_default_input_device_info():p = pyaudio.PyAudio()default_input_info = p.get_default_input_device_info()p.terminate()return default_input_info# 獲取默認(rèn)輸出設(shè)備的詳細(xì)信息
def get_default_output_device_info():p = pyaudio.PyAudio()default_output_info = p.get_default_output_device_info()p.terminate()return default_output_info# 獲取計(jì)算機(jī)上可用音頻設(shè)備的數(shù)量
def get_device_count():p = pyaudio.PyAudio()device_count = p.get_device_count()p.terminate()return device_count# 示例用法
index = 0
print("可用音頻設(shè)備數(shù)量:", get_device_count())
print("設(shè)備{}的信息:{}".format(index, get_device_info_by_index(index)))
print("默認(rèn)錄音設(shè)備的信息:", get_default_input_device_info())
print("默認(rèn)播放設(shè)備的信息:", get_default_output_device_info())
對(duì)于以上代碼,如返回的默認(rèn)播放設(shè)備信息字典如下:
默認(rèn)播放設(shè)備的信息:
{'index': 3,'structVersion': 2,'name': '揚(yáng)聲器 (Realtek High Definition Au','hostApi': 0,'maxInputChannels': 0,'maxOutputChannels': 2,'defaultLowInputLatency': 0.09,'defaultLowOutputLatency': 0.09,'defaultHighInputLatency': 0.18,'defaultHighOutputLatency': 0.18,'defaultSampleRate': 44100.0}
該設(shè)備也是系統(tǒng)當(dāng)前默認(rèn)的音頻設(shè)備,其中各個(gè)參數(shù)的含義如下:
'index': 3
:設(shè)備的索引號(hào),用于在設(shè)備列表中唯一標(biāo)識(shí)該設(shè)備。'structVersion': 2
:設(shè)備信息結(jié)構(gòu)的版本號(hào),用于指示該設(shè)備信息的數(shù)據(jù)結(jié)構(gòu)版本。'name': '揚(yáng)聲器 (Realtek High Definition Au'
:設(shè)備的名稱,表示該設(shè)備是一個(gè) Realtek High Definition 型號(hào)的揚(yáng)聲器。'hostApi': 0
:設(shè)備聲卡驅(qū)動(dòng)模式,來自于PortAudio,如果想詳細(xì)了解見:pyaudio聲卡信息中hostApi。'maxInputChannels': 0
:設(shè)備支持的最大輸入通道數(shù),這里為0表示該設(shè)備沒有輸入功能,不支持錄音。'maxOutputChannels': 2
:設(shè)備支持的最大輸出通道數(shù),這里為2表示該設(shè)備支持2個(gè)輸出通道,即可以播放立體聲音頻。'defaultLowInputLatency': 0.09
:默認(rèn)低輸入延遲,以秒為單位,表示從音頻輸入信號(hào)進(jìn)入設(shè)備所需的最小時(shí)間。'defaultLowOutputLatency': 0.09
:默認(rèn)低輸出延遲,以秒為單位,表示從設(shè)備輸出信號(hào)到達(dá)音頻輸出所需的最小時(shí)間。'defaultHighInputLatency': 0.18
:默認(rèn)高輸入延遲,以秒為單位,表示從音頻輸入信號(hào)進(jìn)入設(shè)備所需的最大時(shí)間。'defaultHighOutputLatency': 0.18
:默認(rèn)高輸出延遲,以秒為單位,表示從設(shè)備輸出信號(hào)到達(dá)音頻輸出所需的最大時(shí)間。'defaultSampleRate': 44100.0
:默認(rèn)采樣率,表示設(shè)備支持的默認(rèn)音頻采樣率為44100赫茲(Hz)。這是音頻設(shè)備在單位時(shí)間內(nèi)采樣的樣本數(shù),影響聲音的質(zhì)量和頻率范圍。
如果想指定設(shè)備進(jìn)行音頻錄制或錄制,則在open函數(shù)中指定設(shè)備的索引,代碼如下:
import pyaudiop = pyaudio.PyAudio()# 獲取可用的設(shè)備數(shù)量
device_count = p.get_device_count()# 遍歷設(shè)備,打印設(shè)備信息和索引
for i in range(device_count):device_info = p.get_device_info_by_index(i)print(f"Device {i}: {device_info['name']}")# 選擇所需的錄音設(shè)備的索引
input_device_index = 1
# 選擇所需的播放設(shè)備的索引
output_device_index = 2 # 打開音頻流,并指定設(shè)備
stream = p.open(format=p.get_format_from_width(2),channels=1,rate=16000,input=True,output=True,input_device_index = input_device_index,output_device_index = output_device_index)# 操作輸出設(shè)備和錄音設(shè)備
# ...
2 SpeechRecognition
2.1 SpeechRecognition介紹與安裝
SpeechRecognition是一個(gè)用于語音識(shí)別的Python庫,支持多個(gè)語音識(shí)別引擎以將音頻轉(zhuǎn)換為文本。SpeechRecognition開源倉庫地址為:speech_recognition?;赑yAudio庫,SpeechRecognition封裝了更加方面和全面的音頻錄制函數(shù)。本文主要介紹利用SpeechRecognition錄制音頻。使用SpeechRecognition進(jìn)行音頻錄制,需要Python3.8及以上環(huán)境,以及最低PyAudio 0.2.11版本。在安裝PyAudio后,SpeechRecognition安裝命令如下:
pip install SpeechRecognition
SpeechRecognition主要的類有:
AudioData
AudioData類是用于表示語音數(shù)據(jù),主要參數(shù)和函數(shù)如下:
參數(shù)
frame_data
:音頻字節(jié)流數(shù)據(jù)sample_rate
:音頻采樣率sample_width
: 音頻的采樣位數(shù)
函數(shù)
get_segment
:返回指定時(shí)間段內(nèi)的音頻數(shù)據(jù)的AudioData對(duì)象get_raw_data
:返回音頻數(shù)據(jù)的原始字節(jié)流get_wav_data
:返回音頻數(shù)據(jù)的wav格式字節(jié)流get_aiff_data
:返回音頻數(shù)據(jù)的aiff格式字節(jié)流get_flac_data
:返回音頻數(shù)據(jù)的flac格式字節(jié)流
Microphone
Microphone類是封裝PyAudio,用于驅(qū)動(dòng)麥克風(fēng)設(shè)備功能的類,因此構(gòu)造參數(shù)與PyAudio主要參數(shù)和函數(shù)如下:
參數(shù)
device_index
:麥克風(fēng)設(shè)備的索引號(hào),不指定將采用PyAudio的默認(rèn)音頻輸入設(shè)置format
:采樣格式為16位整數(shù),不指定將采用PyAudio的默認(rèn)音頻輸入設(shè)置SAMPLE_WIDTH
:音頻的采樣位數(shù) ,不指定將采用PyAudio的默認(rèn)音頻輸入設(shè)置SAMPLE_RATE
:采樣率,不指定將采用PyAudio的默認(rèn)音頻輸入設(shè)置CHUNK
:每個(gè)緩沖區(qū)中存儲(chǔ)的幀數(shù),默認(rèn)為1024audio
: PyAudio對(duì)象stream
:調(diào)用PyAudio的open函數(shù)打開的音頻流
函數(shù)
get_pyaudio
:用來獲取PyAudio的版本號(hào),并調(diào)用PyAudio庫list_microphone_names
:返回當(dāng)前系統(tǒng)中所有可用的麥克風(fēng)設(shè)備的名稱列表list_working_microphones
:返回當(dāng)前系統(tǒng)中所有正在工作的麥克風(fēng)設(shè)備的名稱列表。麥克風(fēng)設(shè)備是否運(yùn)行的評(píng)定方式為:對(duì)于某設(shè)備,嘗試錄制一段短暫的音頻,然后檢查是否成功錄制到了具有一定音頻能量的音頻數(shù)據(jù)。
Recognizer類
Recognizer類是用于語音識(shí)別的主要類,它提供了一系列參數(shù)和函數(shù)來處理音頻輸入,主要參數(shù)和函數(shù)如下:
參數(shù)
energy_threshold = 300
: 用于錄制最低音頻能量,基于音頻均方根RMS計(jì)算能量dynamic_energy_threshold = True
: 是否使用動(dòng)態(tài)能量閾值dynamic_energy_adjustment_damping = 0.15
: 能量閾值調(diào)整的阻尼系數(shù)dynamic_energy_ratio = 1.5
: 動(dòng)態(tài)能量比率pause_threshold = 0.8
: 在一段完整短語被認(rèn)為結(jié)束之前,非語音音頻的持續(xù)時(shí)間(以秒為單位)operation_timeout = None
: 內(nèi)部操作(例如API請(qǐng)求)開始后超時(shí)的時(shí)間(以秒為單位),如果不設(shè)置超時(shí)時(shí)間,則為None
phrase_threshold = 0.3
: 認(rèn)為一段語音至少需要的持續(xù)時(shí)間(以秒為單位),低于該值的語音將被忽略(用于過濾噪聲)non_speaking_duration = 0.5
: 非語音音頻的持續(xù)時(shí)間(以秒為單位)
函數(shù)
record
:從一個(gè)音頻源中讀取數(shù)據(jù)adjust_for_ambient_noise
:用于在錄制音頻之前自動(dòng)根據(jù)麥克風(fēng)的環(huán)境噪聲水平調(diào)整energy_threshold參數(shù)listen
:音頻錄制,結(jié)果返回AudioData類listen_in_background
:用于在后臺(tái)錄制音頻并調(diào)用回調(diào)函數(shù)
Recognizer類的listen函數(shù)每次錄音分為三個(gè)階段:
-
錄音起始
這一階段意味著開始錄音但是沒有聲音輸入。如果當(dāng)前獲得的聲音片段能量值低于energy_threshold
,則認(rèn)為沒有聲音輸入。一旦當(dāng)前獲得的聲音片段能量值高于energy_threshold
,則進(jìn)入下一階段。該階段將最多保存non_speaking_duration
長(zhǎng)度的音頻片段。如果dynamic_energy_threshold
為True,則會(huì)根據(jù)環(huán)境動(dòng)態(tài)調(diào)整energy_threshold
。
listen函數(shù)提供輸入?yún)?shù)timeout
以控制該階段時(shí)長(zhǎng),如果錄音處于該階段timeout
秒則停止錄音返回錯(cuò)誤提示,timeout
默認(rèn)為None。 -
錄音中
這一階段意味著已有聲音輸入。如果聲音片段能量值低于energy_threshold
連續(xù)超過pause_threshold
秒,則結(jié)束錄音。在這一階段energy_threshold一直是固定值,并不會(huì)進(jìn)行動(dòng)態(tài)調(diào)整。
listen函數(shù)提供輸入?yún)?shù)phrase_time_limit
以控制該階段最大時(shí)長(zhǎng),如果錄音處于該階段phrase_time_limit
秒則結(jié)束錄音。 -
錄音結(jié)束
在這一階段中,如果錄音中階段獲得的聲音片段時(shí)間不超過phrase_threshold
秒,則不返回錄音結(jié)果且進(jìn)入下一次錄音起始階段。如果超過phrase_threshold
秒,則將音頻片段轉(zhuǎn)為音頻流,以AudioData對(duì)象返回。
2.2 示例代碼
音頻錄制
import speech_recognition as sr# 創(chuàng)建一個(gè)Recognizer對(duì)象,用于語音識(shí)別
r = sr.Recognizer()# 設(shè)置相關(guān)閾值
r.non_speaking_duration = 0.3
r.pause_threshold = 0.5# 創(chuàng)建一個(gè)Microphone對(duì)象,設(shè)置采樣率為16000
# 構(gòu)造函數(shù)所需參數(shù)device_index=None, sample_rate=None, chunk_size=1024
msr = sr.Microphone(sample_rate=16000)# 打開麥克風(fēng)
with msr as source:# 如果想連續(xù)錄音,該段代碼使用for循環(huán)# 進(jìn)行環(huán)境噪音適應(yīng),duration為適應(yīng)時(shí)間,不能小于0.5# 如果無噪聲適應(yīng)要求,該段代碼可以注釋r.adjust_for_ambient_noise(source, duration=0.5)print("開始錄音")# 使用Recognizer監(jiān)聽麥克風(fēng)錄音# phrase_time_limit=None表示不設(shè)置時(shí)間限制audio = r.listen(source, phrase_time_limit=None)print("錄音結(jié)束")# 將錄音數(shù)據(jù)寫入.wav格式文件with open("microphone-results.wav", "wb") as f:# audio.get_wav_data()獲得wav格式的音頻二進(jìn)制數(shù)據(jù)f.write(audio.get_wav_data())# 將錄音數(shù)據(jù)寫入.raw格式文件with open("microphone-results.raw", "wb") as f:f.write(audio.get_raw_data())# 將錄音數(shù)據(jù)寫入.aiff格式文件with open("microphone-results.aiff", "wb") as f:f.write(audio.get_aiff_data())# 將錄音數(shù)據(jù)寫入.flac格式文件with open("microphone-results.flac", "wb") as f:f.write(audio.get_flac_data())
音頻文件讀取
# 導(dǎo)入speech_recognition庫,別名為sr
import speech_recognition as sr# 創(chuàng)建一個(gè)Recognizer對(duì)象r,用于語音識(shí)別
r = sr.Recognizer()# 設(shè)置音頻文件路徑
filepath = "demo.wav"# 使用AudioFile打開音頻文件作為音頻源
with sr.AudioFile(filepath) as source:# 使用record方法記錄從音頻源中提取的2秒音頻,從第1秒開始audio = r.record(source, offset=1, duration=2)# 創(chuàng)建一個(gè)文件用于保存提取的音頻數(shù)據(jù)with open("microphone-results.wav", "wb") as f:# 將提取的音頻數(shù)據(jù)寫入文件f.write(audio.get_wav_data())
回調(diào)函數(shù)的使用
import time
import speech_recognition as sr# 這是從后臺(tái)線程調(diào)用的回調(diào)函數(shù)
def callback(recognizer, audio):# recognizer是Recognizer對(duì)象的實(shí)例。audio是從麥克風(fēng)捕獲到的音頻數(shù)據(jù)print(type(audio))r = sr.Recognizer()
m = sr.Microphone()
with m as source:# 我們只需要在開始監(jiān)聽之前校準(zhǔn)一次r.adjust_for_ambient_noise(source)# 在后臺(tái)開始監(jiān)聽
stop_listening = r.listen_in_background(m, callback)# 進(jìn)行一些無關(guān)的計(jì)算,持續(xù)5秒鐘
for _ in range(50):# 即使主線程正在做其他事情,我們?nèi)匀辉诒O(jiān)聽time.sleep(0.1)# 調(diào)用此函數(shù)請(qǐng)求停止后臺(tái)監(jiān)聽
stop_listening(wait_for_stop=False)
麥克風(fēng)設(shè)備查看
import speech_recognition as sr# 獲取麥克風(fēng)設(shè)備名稱列表
def list_microphone_names():mic_list = sr.Microphone.list_microphone_names()for index, mic_name in enumerate(mic_list):print("Microphone {}: {}".format(index, mic_name))print("\n")# 獲取可用的工作麥克風(fēng)列表
def list_working_microphones():mic_list = sr.Microphone.list_working_microphones()for index, mic_name in mic_list.items():print("Microphone {}: {}".format(index, mic_name))print("\n")# 獲得pyaudio對(duì)象
def get_pyaudio():audio = sr.Microphone.get_pyaudio().PyAudio()# 獲取默認(rèn)音頻輸入設(shè)備信息print(audio.get_default_input_device_info())print("\n")return audioprint("所有麥克風(fēng)列表")
list_microphone_names()
print("可運(yùn)行麥克風(fēng)列表")
list_working_microphones()
print("默認(rèn)音頻輸入設(shè)備信息")
get_pyaudio()
3 語音識(shí)別示例應(yīng)用
本示例給出一個(gè)基于SpeechRecognition庫和Whisper語音識(shí)別庫的非流式語音識(shí)別示例應(yīng)用。一般來說語音識(shí)別分為流式語音識(shí)別和非流式語音識(shí)別:
- 流式語音識(shí)別是指在語音輸入過程中實(shí)時(shí)進(jìn)行語音識(shí)別,即邊接收語音數(shù)據(jù)邊輸出識(shí)別結(jié)果,實(shí)現(xiàn)實(shí)時(shí)性較高的語音識(shí)別。在流式語音識(shí)別中,語音被分割成一小段一小段的流,可以通過連續(xù)發(fā)送這些流來實(shí)時(shí)地獲取識(shí)別結(jié)果。隨著語音輸入的增加,流式語音識(shí)別也可以優(yōu)化輸出部分結(jié)果。流式語音識(shí)別準(zhǔn)確率相對(duì)較低,但是實(shí)時(shí)性強(qiáng),適用于需要快速響應(yīng)的場(chǎng)景,例如實(shí)時(shí)語音助手、電話客服、會(huì)議記錄等。技術(shù)上,流式語音識(shí)別需要實(shí)時(shí)處理音頻流,要求算法具有低延遲和高吞吐量的特點(diǎn),通常使用各種優(yōu)化策略來提高實(shí)時(shí)性能。
- 非流式語音識(shí)別是指等待語音輸入結(jié)束后將完整的語音輸入一次性進(jìn)行分析和識(shí)別。非流式語音識(shí)別精度高,適用于一些不需要實(shí)時(shí)響應(yīng)的場(chǎng)景或一次性識(shí)別整段語音的場(chǎng)景,如指令識(shí)別、語音轉(zhuǎn)寫、語音搜索、語音翻譯等。技術(shù)上,非流式語音識(shí)別注重語音的整體準(zhǔn)確性和語義理解,通常采用復(fù)雜的模型和算法來提高識(shí)別準(zhǔn)確率。
Whisper是OpenAI開源的通用多語言語音識(shí)別模型庫。Whisper使用了一個(gè)序列到序列的Transformer模型,支持多國語言語音識(shí)別,其英語的識(shí)別水平與人類接近。關(guān)于Whisper的安裝和使用可參考Whisper開源倉庫或參考文章:Whisper語音轉(zhuǎn)文字手把手教程。所提供的語音識(shí)別示例實(shí)現(xiàn)了簡(jiǎn)單的語音起始和結(jié)束檢測(cè),并進(jìn)行相應(yīng)的語音識(shí)別和結(jié)果展示,程序代碼結(jié)構(gòu)如下:
.
├── asr.py 語音識(shí)別類
├── record.py 錄音類
└── run.py 界面類
安裝SpeechRecognition庫和Whisper庫后運(yùn)行run.py文件即可打開示例應(yīng)用。
界面類
界面類提供了一個(gè)基于PyQt5編寫的簡(jiǎn)單應(yīng)用界面,如下所示。當(dāng)界面初始化時(shí),會(huì)同時(shí)初始化錄音類和語音識(shí)別類。點(diǎn)擊開始錄音按鈕后,程序?qū)?shí)現(xiàn)自動(dòng)循環(huán)監(jiān)聽說話音頻的開始和結(jié)束。每次說話結(jié)束后,程序會(huì)自動(dòng)進(jìn)行語音識(shí)別,并將識(shí)別結(jié)果顯示在界面中。點(diǎn)擊停止按鈕則會(huì)等待錄音結(jié)束并停止語音監(jiān)聽。
# run.py
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QSize, Qt
import sys
from record import AudioHandleclass Window(QMainWindow):"""界面類"""def __init__(self):super().__init__()# --- 設(shè)置標(biāo)題self.setWindowTitle('語音識(shí)別demo')# --- 設(shè)置窗口尺寸# 獲取系統(tǒng)桌面尺寸desktop = app.desktop()# 設(shè)置界面初始尺寸self.width = int(desktop.screenGeometry().width() * 0.3)self.height = int(0.5 * self.width)self.resize(self.width, self.height)# 設(shè)置窗口最小值self.minWidth = 300self.setMinimumSize(QSize(self.minWidth, int(0.5 * self.minWidth)))# --- 創(chuàng)建組件self.showBox = QTextEdit()self.showBox.setReadOnly(True)self.startBtn = QPushButton("開始錄音")self.stopBtn = QPushButton("停止錄音")self.stopBtn.setEnabled(False)# --- 組件初始化self.initUI()# --- 初始化音頻類self.ahl = AudioHandle()# 連接用于傳遞信息的信號(hào)self.ahl.infoSignal.connect(self.showInfo)self.showInfo("<font color='blue'>{}</font>".format("程序已初始化"))def initUI(self) -> None:"""界面初始化"""# 設(shè)置整體布局mainLayout = QVBoxLayout()mainLayout.addWidget(self.showBox)# 設(shè)置底部水平布局blayout = QHBoxLayout()blayout.addWidget(self.startBtn)blayout.addWidget(self.stopBtn)mainLayout.addLayout(blayout)mainWidget = QWidget()mainWidget.setLayout(mainLayout)self.setCentralWidget(mainWidget)# 設(shè)置事件self.startBtn.clicked.connect(self.record)self.stopBtn.clicked.connect(self.record)def record(self) -> None:"""錄音控制"""sender = self.sender()if sender.text() == "開始錄音":self.stopBtn.setEnabled(True)self.startBtn.setEnabled(False)# 開啟錄音線程self.ahl.start()elif sender.text() == "停止錄音":self.stopBtn.setEnabled(False)# waitDialog用于等待錄音停止waitDialog = QProgressDialog("正在停止錄音...", None, 0, 0)waitDialog.setWindowTitle("請(qǐng)等待")waitDialog.setWindowModality(Qt.ApplicationModal)waitDialog.setCancelButton(None)waitDialog.setRange(0, 0)# 設(shè)置 Marquee 模式waitDialog.setWindowFlag(Qt.WindowContextHelpButtonHint, False)waitDialog.setWindowFlag(Qt.WindowCloseButtonHint, False)waitDialog.setWindowFlag(Qt.WindowMaximizeButtonHint, False)waitDialog.setWindowFlag(Qt.WindowMinimizeButtonHint, False)waitDialog.setWindowFlag(Qt.WindowTitleHint, False)# 關(guān)閉對(duì)話框邊框waitDialog.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)# 連接關(guān)閉信號(hào),即ahl線程結(jié)束則waitDialog關(guān)閉self.ahl.finished.connect(waitDialog.accept)# 結(jié)束錄音線程self.ahl.stop()if self.ahl.isRunning():# 顯示對(duì)話框waitDialog.exec_()# 關(guān)閉對(duì)話框self.ahl.finished.disconnect(waitDialog.accept)waitDialog.close()self.startBtn.setEnabled(True)def showInfo(self, text: str) -> None:"""信息展示函數(shù):param text: 輸入文字,可支持html"""self.showBox.append(text)if not self.ahl.running:self.stopBtn.click()def closeEvent(self, event: QtGui.QCloseEvent):"""重寫退出事件:param event: 事件對(duì)象"""# 點(diǎn)擊停止按鈕if self.ahl.running:self.stopBtn.click()del self.ahlevent.accept()if __name__ == '__main__':app = QApplication(sys.argv)ex = Window()# 獲取默認(rèn)圖標(biāo)default_icon = app.style().standardIcon(QStyle.SP_MediaVolume)# 設(shè)置窗口圖標(biāo)為默認(rèn)圖標(biāo)ex.setWindowIcon(default_icon)ex.show()sys.exit(app.exec_())
錄音類
錄音類可以用于監(jiān)聽麥克風(fēng)輸入的音頻并調(diào)用語音識(shí)別類進(jìn)行識(shí)別。通過設(shè)置采樣率、適應(yīng)環(huán)境時(shí)長(zhǎng)、錄音最長(zhǎng)時(shí)長(zhǎng)等參數(shù),實(shí)現(xiàn)自動(dòng)判斷說話開始和結(jié)束的功能。同時(shí),通過PyQt5的信號(hào)機(jī)制,在界面上展示不同類型的信息,包括警告信息和識(shí)別結(jié)果。
# record.py
import speech_recognition as sr
from PyQt5.QtCore import QThread, pyqtSignal
import time, os
import numpy as np
from asr import ASRclass AudioHandle(QThread):"""錄音控制類"""# 用于展示信息的pyqt信號(hào)infoSignal = pyqtSignal(str)def __init__(self, sampleRate: int = 16000, adjustTime: int = 1, phraseLimitTime: int = 5,saveAudio: bool = False, hotWord: str = ""):""":param sampleRate: 采樣率:param adjustTime: 適應(yīng)環(huán)境時(shí)長(zhǎng)/s:param phraseLimitTime: 錄音最長(zhǎng)時(shí)長(zhǎng)/s:param saveAudio: 是否保存音頻:param hotWord: 熱詞數(shù)據(jù)"""super(AudioHandle, self).__init__()self.sampleRate = sampleRateself.duration = adjustTimeself.phraseTime = phraseLimitTime# 用于設(shè)置運(yùn)行狀態(tài)self.running = Falseself.rec = sr.Recognizer()# 麥克風(fēng)對(duì)象self.mic = sr.Microphone(sample_rate=self.sampleRate)# 語音識(shí)別模型對(duì)象# hotWord為需要優(yōu)先識(shí)別的熱詞# 輸入"秦劍 無憾"表示優(yōu)先匹配該字符串中的字符self.asr = ASR(prompt=hotWord)self.saveAudio = saveAudioself.savePath = "output"def run(self) -> None:self.listen()def stop(self) -> None:self.running = Falsedef setInfo(self, text: str, type: str = "info") -> None:"""展示信息:param text: 文本:param type: 文本類型"""nowTime = time.strftime("%H:%M:%S", time.localtime())if type == "info":self.infoSignal.emit("<font color='blue'>{} {}</font>".format(nowTime, text))elif type == "text":self.infoSignal.emit("<font color='green'>{} {}</font>".format(nowTime, text))else:self.infoSignal.emit("<font color='red'>{} {}</font>".format(nowTime, text))def listen(self) -> None:"""語音監(jiān)聽函數(shù)"""try:with self.mic as source:self.setInfo("錄音開始")self.running = Truewhile self.running:# 設(shè)備監(jiān)控audioIndex = self.mic.audio.get_default_input_device_info()['index']workAudio = self.mic.list_working_microphones()if len(workAudio) == 0 or audioIndex not in workAudio:self.setInfo("未檢測(cè)到有效音頻輸入設(shè)備!!!", type='warning')breakself.rec.adjust_for_ambient_noise(source, duration=self.duration)self.setInfo("正在錄音")# self.running為否無法立即退出該函數(shù),如果想立即退出則需要重寫該函數(shù)audio = self.rec.listen(source, phrase_time_limit=self.phraseTime)# 將音頻二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為numpy類型audionp = self.bytes2np(audio.frame_data)if self.saveAudio:self.saveWav(audio)# 判斷音頻rms值是否超過經(jīng)驗(yàn)閾值,如果沒超過表明為環(huán)境噪聲if np.sqrt(np.mean(audionp ** 2)) < 0.02:continueself.setInfo("音頻正在識(shí)別")# 識(shí)別語音result = self.asr.predict(audionp)self.setInfo(f"識(shí)別結(jié)果為:{result}", "text")except Exception as e:self.setInfo(e, "warning")finally:self.setInfo("錄音停止")self.running = Falsedef bytes2np(self, inp: bytes, sampleWidth: int = 2) -> np.ndarray:"""將音頻二進(jìn)制數(shù)據(jù)轉(zhuǎn)換為numpy類型:param inp: 輸入音頻二進(jìn)制流:param sampleWidth: 音頻采樣寬度:return: 音頻numpy數(shù)組"""# 使用np.frombuffer函數(shù)將字節(jié)序列轉(zhuǎn)換為numpy數(shù)組tmp = np.frombuffer(inp, dtype=np.int16 if sampleWidth == 2 else np.int8)# 確保tmp為numpy數(shù)組tmp = np.asarray(tmp)# 獲取tmp數(shù)組元素的數(shù)據(jù)類型信息i = np.iinfo(tmp.dtype)# 計(jì)算tmp元素的絕對(duì)最大值absmax = 2 ** (i.bits - 1)# 計(jì)算tmp元素的偏移量offset = i.min + absmax# 將tmp數(shù)組元素轉(zhuǎn)換為浮點(diǎn)型,并進(jìn)行歸一化array = np.frombuffer((tmp.astype(np.float32) - offset) / absmax, dtype=np.float32)# 返回轉(zhuǎn)換后的numpy數(shù)組return arraydef saveWav(self, audio: sr.AudioData) -> None:"""保存語音結(jié)果:param audio: AudioData音頻對(duì)象"""nowTime = time.strftime("%H_%M_%S", time.localtime())os.makedirs(self.savePath, exist_ok=True)with open("{}/{}.wav".format(self.savePath, nowTime), 'wb') as f:f.write(audio.get_wav_data())
語音識(shí)別類
語音識(shí)別類利用Whisper進(jìn)行語音識(shí)別。在使用Whisper進(jìn)行語音識(shí)別時(shí),可以通過設(shè)置initial_prompt參數(shù)來指定初始提示。initial_prompt參數(shù)是一個(gè)字符串,用于在模型生成文本之前提供一些初始的上下文信息。將這些信息傳遞給Whisper模型可以幫助它更好地理解任務(wù)的背景和上下文。通過設(shè)置適當(dāng)?shù)膇nitial_prompt,可以引導(dǎo)模型產(chǎn)生與特定主題相關(guān)的響應(yīng)或者在對(duì)話中提供一些先驗(yàn)知識(shí)。例如,熱點(diǎn)詞匯識(shí)別,結(jié)果為簡(jiǎn)體字還是繁體字。initial_prompt并不是必需的參數(shù),如果沒有適當(dāng)?shù)某跏继崾?#xff0c;可以選擇不使用它,讓模型完全自由生成響應(yīng)。但是要注意的是如果輸入的語音為環(huán)境噪聲或者使用的是小型Whisper模型,initial_prompt的設(shè)置可能會(huì)導(dǎo)致語音識(shí)別輸出結(jié)果為initial_prompt。
Whisper提供了5種型號(hào)的模型,其中4種支持純英文版本,以平衡速度和準(zhǔn)確性。Whisper模型越大精度越高,速度越慢,本文默認(rèn)使用small型號(hào)的模型。以下是這些可用模型的型號(hào)名稱、大致的顯存要求和相對(duì)速度:
型號(hào) | 參數(shù)量 | 僅英文模型 | 多語言模型 | 所需顯存 | 相對(duì)速度 |
---|---|---|---|---|---|
tiny | 39M | tiny.en | tiny | ~1GB | ~32x |
base | 74M | base.en | base | ~1GB | ~16x |
small | 244M | small.en | small | ~2GB | ~6x |
medium | 769M | medium.en | medium | ~5GB | ~2x |
large | 1550M | N/A | large | ~10GB | 1x |
# asr.py
import whisper
import numpy as npclass ASR:"""語音識(shí)別模型類"""def __init__(self, modelType: str = "small", prompt: str = ""):""":param modelType: whisper模型類型:param prompt: 提示詞"""# 模型默認(rèn)使用cuda運(yùn)行,沒gpu跑模型很慢。# 使用device="cpu"即可改為cpu運(yùn)行self.model = whisper.load_model(modelType, device="cuda")# prompt作用就是提示模型輸出指定類型的文字# 這里使用簡(jiǎn)體中文就是告訴模型盡可能輸出簡(jiǎn)體中文的識(shí)別結(jié)果self.prompt = "簡(jiǎn)體中文" + promptdef predict(self, audio: np.ndarray) -> str:"""語音識(shí)別:param audio: 輸入的numpy音頻數(shù)組:return: 輸出識(shí)別的字符串結(jié)果"""# prompt在whisper中用法是作為transformer模型交叉注意力模塊的初始值。transformer為自回歸模型,會(huì)逐個(gè)生成識(shí)別文字,# 如果輸入的語音為空,initial_prompt的設(shè)置可能會(huì)導(dǎo)致語音識(shí)別輸出結(jié)果為initial_promptresult = self.model.transcribe(audio.astype(np.float32), initial_prompt=self.prompt)return result["text"]
4 參考
- 數(shù)字音頻基礎(chǔ)----------從PCM說起
- portaudio
- PyAudio
- pyaudio聲卡信息中hostApi
- speech_recognition
- Whisper
- Whisper語音轉(zhuǎn)文字手把手教程